- Регистрация
- 23.09.18
- Сообщения
- 12.347
- Реакции
- 176
- Репутация
- 0
Проживая в эпоху технологических прорывов и свершений, взирая на то, как устремляются в небо ракеты Маска и Безоса, мы, простые люди с высшим техническим образованием, часто не замечаем возможности совершить прорыв не там, далеко в космосе, а здесь рядом с нами, буквально не вставая
Судите сами, к какому открытию может привести чтение обычной статьи о современных смартфонах. Источник приводить не буду, чтобы не делиться будущими доходами.
Вершиной вычислительной фотографии можно, пожалуй, считать ночную съемку. Примером может служить «Ночной Режим» в смартфонах Google Pixel. В нём IT гиганту пришлось задействовать съёмку в RAW, HDR-стекинг, компенсацию «смазов», распознавание сцен нейросетями. А появление второй камеры в прошлогоднем Pixel 4 сделало «Night Sight» пригодным даже для съемки звезд. В сумме это создает ощущение волшебства: глаза видят кромешную тьму, а на фотографии лёгкие сумерки. Как шутят на форумах, скоро на смартфон можно будет снять чёрную кошку в тёмной комнате и она будет чёткой.
Другое дело, что ходить ночью и тыриться в экран мобильника как-то неудобно, даже в ночном режиме. И тут мой взгляд случайно упал на VR-гарнитуру для смартфона, валявшуюся на полке. Прорыв свершился! Осталось только, используя её и накопленные за четыре поста знания о Android Camera2 API, направить изображение с «Night Sight» прямо в глаз. Заодно и руки будут свободны, чтобы поймать чёрную кошку в тёмной комнате. Совсем без света, конечно, не получится, фотонов, хоть немного, да нужно. Но по крайней мере уровня котановских гляделок в темноте мы достигнуть (а может, даже превзойти) обязаны.
Итак, чтобы научиться видеть во тьме, нам понадобится:
1: гарнитура виртуальной реальности для смартфона, (можно самую дешёвую)
2: смартфон с поддержкой современных гуглофич для камеры (ну, он точно самым дешевым не окажется)
3: знание основ Android Camera2 API ( это у нас уже есть)
You must be registered for see links
You must be registered for see links
You must be registered for see links
You must be registered for see links
Открываем новый проект в Android Studio и начинаем ваять код.
Первым делом надо собрать, собственно VR поверхности, которые будут светить в гарнитуру.
Макет
На выходе должно получится что-то в этом роде:
Как видим, текстуры получились прямоугольными, тогда как в любой 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();
}
}
Да, и не забываем про
манифест
Теперь запихиваем смартфон в 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-гарнитурами на головах…