ОЧКИ СУМРАЧНОГО ЗРЕНИЯ. Android Camera2 API от чайника, часть 5 внезапная

от автора

Проживая в эпоху технологических прорывов и свершений, взирая на то, как устремляются в небо ракеты Маска и Безоса, мы, простые люди с высшим техническим образованием, часто не замечаем возможности совершить прорыв не там, далеко в космосе, а здесь рядом с нами, буквально не вставая с дивана из-стола.
Судите сами, к какому открытию может привести чтение обычной статьи о современных смартфонах. Источник приводить не буду, чтобы не делиться будущими доходами.

Вершиной вычислительной фотографии можно, пожалуй, считать ночную съемку. Примером может служить «Ночной Режим» в смартфонах Google Pixel. В нём IT гиганту пришлось задействовать съёмку в RAW, HDR-стекинг, компенсацию «смазов», распознавание сцен нейросетями. А появление второй камеры в прошлогоднем Pixel 4 сделало «Night Sight» пригодным даже для съемки звезд. В сумме это создает ощущение волшебства: глаза видят кромешную тьму, а на фотографии лёгкие сумерки. Как шутят на форумах, скоро на смартфон можно будет снять чёрную кошку в тёмной комнате и она будет чёткой.

Другое дело, что ходить ночью и тыриться в экран мобильника как-то неудобно, даже в ночном режиме. И тут мой взгляд случайно упал на VR-гарнитуру для смартфона, валявшуюся на полке. Прорыв свершился! Осталось только, используя её и накопленные за четыре поста знания о Android Camera2 API, направить изображение с «Night Sight» прямо в глаз. Заодно и руки будут свободны, чтобы поймать чёрную кошку в тёмной комнате. Совсем без света, конечно, не получится, фотонов, хоть немного, да нужно. Но по крайней мере уровня котановских гляделок в темноте мы достигнуть (а может, даже превзойти) обязаны.

Итак, чтобы научиться видеть во тьме, нам понадобится:

1: гарнитура виртуальной реальности для смартфона, (можно самую дешёвую)

2: смартфон с поддержкой современных гуглофич для камеры (ну, он точно самым дешевым не окажется)

3: знание основ Android Camera2 API ( это у нас уже есть)
часть первая
часть вторая
часть третья
часть четвертая

Открываем новый проект в Android Studio и начинаем ваять код.

Первым делом надо собрать, собственно VR поверхности, которые будут светить в гарнитуру.

Макет

<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:app="http://schemas.android.com/apk/res-auto"     xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:background="#03061B"     tools:context=".MainActivity">      <TextureView         android:id="@+id/textureView"         android:layout_width="240dp"         android:layout_height="320dp"         android:layout_marginTop="28dp"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintHorizontal_bias="0.497"         app:layout_constraintStart_toStartOf="parent"         app:layout_constraintTop_toTopOf="parent" />      <TextureView         android:id="@+id/textureView3"         android:layout_width="240dp"         android:layout_height="320dp"         android:layout_marginTop="16dp"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintStart_toStartOf="parent"         app:layout_constraintTop_toBottomOf="@+id/textureView" />      <LinearLayout         android:layout_width="165dp"         android:layout_height="40dp"         app:layout_constraintBottom_toBottomOf="parent"         app:layout_constraintEnd_toEndOf="parent"         app:layout_constraintStart_toStartOf="parent"         app:layout_constraintTop_toBottomOf="@+id/textureView3"         app:layout_constraintVertical_bias="0.838">          <Button             android:id="@+id/button1"             android:layout_width="wrap_content"             android:layout_height="36dp"             android:backgroundTint="#3F51B5"             android:text="вкл"             android:textColor="#1A87DD" />          <Button             android:id="@+id/button3"             android:layout_width="wrap_content"             android:layout_height="37dp"             android:backgroundTint="#3F51B5"             android:text="выкл"             android:textColor="#2196F3" />     </LinearLayout>   </androidx.constraintlayout.widget.ConstraintLayout>   

На выходе должно получится что-то в этом роде:

Как видим, текстуры получились прямоугольными, тогда как в любой VR игрушке для смартфона разработчики как-то ухитряются делать их круглыми. Но мы не будем на этом зацикливаться. Дальнейший опыт все равно покажет, что и так сойдёт.

Для этого, собственно, мы пишем скромную такую Activity, в которой всё уже нам знакомо по предыдущим статьям.

package com.example.twovideosurfaces; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.graphics.SurfaceTexture; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.StrictMode; import android.util.Log; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import java.util.Arrays; public class MainActivity extends AppCompatActivity  {     public static final String LOG_TAG = "myLogs";     public static Surface surface1 = null;     public static Surface surface2 = null;     CameraService[] myCameras = null;     private CameraManager mCameraManager = null;     private final int CAMERA1 = 0;     private Button mOn = null;     private Button mOff = null;     public static TextureView mImageViewUp = null;     public static TextureView mImageViewDown = null;     private HandlerThread mBackgroundThread;     private Handler mBackgroundHandler = null;     private void startBackgroundThread() {         mBackgroundThread = new HandlerThread("CameraBackground");         mBackgroundThread.start();         mBackgroundHandler = new Handler(mBackgroundThread.getLooper());     }     private void stopBackgroundThread() {         mBackgroundThread.quitSafely();         try {             mBackgroundThread.join();             mBackgroundThread = null;             mBackgroundHandler = null;         } catch (InterruptedException e) {             e.printStackTrace();         }     }     @RequiresApi(api = Build.VERSION_CODES.M)     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();         StrictMode.setThreadPolicy(policy);         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);         setContentView(R.layout.activity_main);         Log.d(LOG_TAG, "Запрашиваем разрешение");         if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED                 ||                 (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)         ) {             requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);         }         mOn = findViewById(R.id.button1);         mOff = findViewById(R.id.button3);         mImageViewUp = findViewById(R.id.textureView);         mImageViewDown = findViewById(R.id.textureView3);         mOn.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {                 if (myCameras[CAMERA1] != null) {// открываем камеру                     if (!myCameras[CAMERA1].isOpen()) myCameras[CAMERA1].openCamera();                 }             }         });         mOff.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View v) {             }         });         mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);         try {             // Получение списка камер с устройства             myCameras = new CameraService[mCameraManager.getCameraIdList().length];             for (String cameraID : mCameraManager.getCameraIdList()) {                 Log.i(LOG_TAG, "cameraID: " + cameraID);                 int id = Integer.parseInt(cameraID);                 // создаем обработчик для камеры                 myCameras[id] = new CameraService(mCameraManager, cameraID);             }         } catch (CameraAccessException e) {             Log.e(LOG_TAG, e.getMessage());             e.printStackTrace();         }     }     public class CameraService {         private String mCameraID;         private CameraDevice mCameraDevice = null;         private CameraCaptureSession mSession;         private CaptureRequest.Builder mPreviewBuilder;         public CameraService(CameraManager cameraManager, String cameraID) {             mCameraManager = cameraManager;             mCameraID = cameraID;         }         private CameraDevice.StateCallback mCameraCallback = new CameraDevice.StateCallback() {             @Override             public void onOpened(CameraDevice camera) {                 mCameraDevice = camera;                 Log.i(LOG_TAG, "Open camera  with id:" + mCameraDevice.getId());                 startCameraPreviewSession();             }             @Override             public void onDisconnected(CameraDevice camera) {                 mCameraDevice.close();                 Log.i(LOG_TAG, "disconnect camera  with id:" + mCameraDevice.getId());                 mCameraDevice = null;             }             @Override             public void onError(CameraDevice camera, int error) {                 Log.i(LOG_TAG, "error! camera id:" + camera.getId() + " error:" + error);             }         };         private void startCameraPreviewSession() {             SurfaceTexture texture = mImageViewUp.getSurfaceTexture();             texture.setDefaultBufferSize(1280, 1024);             surface1 = new Surface(texture);             SurfaceTexture texture2 = mImageViewDown.getSurfaceTexture();             surface2 = new Surface(texture2);             texture2.setDefaultBufferSize(1280, 1024);             try {                 mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);                 mPreviewBuilder.addTarget(surface1);                 mPreviewBuilder.addTarget(surface2);                 mCameraDevice.createCaptureSession(Arrays.asList(surface1,surface2),                         new CameraCaptureSession.StateCallback() {                             @Override                             public void onConfigured(CameraCaptureSession session) {                                 mSession = session;                                  try {                                     mSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler);                                 } catch (CameraAccessException e) {                                     e.printStackTrace();                                 }                             }                             @Override                             public void onConfigureFailed(CameraCaptureSession session) {                             }                         }, mBackgroundHandler);             } catch (CameraAccessException e) {                 e.printStackTrace();             }         }         public boolean isOpen() {             if (mCameraDevice == null) {                 return false;             } else {                 return true;             }         }         public void openCamera() {             try {                 if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {                     mCameraManager.openCamera(mCameraID, mCameraCallback, mBackgroundHandler);                 }             } catch (CameraAccessException e) {                 Log.i(LOG_TAG, e.getMessage());             }         }         public void closeCamera() {             if (mCameraDevice != null) {                 mCameraDevice.close();                 mCameraDevice = null;             }         }     }     @Override     public void onPause() {         if (myCameras[CAMERA1].isOpen()) {             myCameras[CAMERA1].closeCamera();         }         stopBackgroundThread();         super.onPause();     }     @Override     public void onResume() {         super.onResume();         startBackgroundThread();     } }   

Да, и не забываем про

манифест

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"     package="com.example.twovideosurfaces">     <uses-permission android:name="android.permission.CAMERA" />     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />     <uses-permission android:name="android.permission.INTERNET"/>     <application         android:allowBackup="true"         android:icon="@mipmap/ic_launcher"         android:label="@string/app_name"         android:roundIcon="@mipmap/ic_launcher_round"         android:supportsRtl="true"         android:theme="@style/Theme.AppCompat.NoActionBar"         >         <activity android:name=".MainActivity">             <intent-filter>                 <action android:name="android.intent.action.MAIN" />                 <category android:name="android.intent.category.LAUNCHER" />             </intent-filter>         </activity>     </application> </manifest> 

Теперь запихиваем смартфон в VR гарнитуру и ходим по дому, наслаждаясь зрением киборга разрешением 1280 х 1024 на каждый глаз. Ощущения, конечно, странные, с потерей глубины зрения, но всё равно прикольно. Единственно, всё выглядит слегка темновато, но это потому, что мешает передняя полупрозрачная панель гарнитуры. Поэтому в ней надо продырить соответствующее отверстие напротив камеры смартфона. Но опять же, на самых бюджетных VR моделях такой панели вообще может и не быть, и вполне вероятно, что вам и не придется осквернять себя ручным трудом.

Всё что теперь осталось — так это убедить Google camera API, что у нас тьма кромешная, и хорошо бы задействовать режим Night Vision, а вместе с ним все эти RAW, HDR-стекинг и распознавание сцен нейросетями.

Для этого всего лишь пропишем в сессии:

  mPreviewBuilder.set(CaptureRequest.CONTROL_SCENE_MODE,                                             CaptureRequest.CONTROL_SCENE_MODE_NIGHT);  

и выкрутим по максимуму экспозицию и светочувствительность

 mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE,         CaptureRequest.CONTROL_AE_MODE_OFF); mPreviewBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME,Long.valueOf("100000000"));  mPreviewBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, 30000);  

Ой, я ослеп!

Вот что, оказывается, видит кот, когда его выкидывают из спальни, где он мешает людям заниматься сексом, в гостиную.
Но конечно, это перебор и параметры (а их в API немало и здесь приведена всего парочка) надо подкрутить потом опытным путём.

Теперь нам остаётся только дождаться ночи. Не безлунной, конечно, с плотной облачностью где-нибудь в тайге, а обычной такой ночи со случайными залетными фотонами.
И вот что произойдёт…
Хотя казалось бы, при обычной съёмке практически ничего не видно.

Но современые камеры творят чудеса и таки находят черную кошку…

Теперь можно гулять по ночам, потому что днём нельзя из-за карантина. По идее и ночью тоже нельзя, но кто ж вас увидит, крадущихся во мраке ночи с VR-гарнитурами на головах…

ссылка на оригинал статьи https://habr.com/ru/post/493922/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *