Brille der Dämmerungssicht. Android Camera2 API aus dem Maker Part 5 Flash



In einer Zeit technologischer Durchbrüche und Errungenschaften, in der wir sehen, wie die Raketen Mask und Bezos in den Himmel rasen, bemerken wir, normale Menschen mit einer höheren technischen Ausbildung, oft nicht die Möglichkeit, einen Durchbruch nicht dort, weit im Weltraum, sondern buchstäblich hier neben uns zu erzielen nicht vom Sofa vom Tisch aufstehen .

Überzeugen Sie sich selbst, welche Entdeckung dazu führen kann, dass Sie regelmäßig einen Artikel über moderne Smartphones lesen. Ich werde die Quelle nicht angeben, um zukünftige Einnahmen nicht zu teilen.
, , . « » Google Pixel. IT RAW, HDR-, «», . Pixel 4 «Night Sight» . : , . , .

Eine andere Sache ist, dass das Gehen in der Nacht und das Schrubben auf dem Bildschirm eines Mobiltelefons selbst im Nachtmodus irgendwie unangenehm ist. Und dann fiel mein Blick versehentlich auf ein VR-Headset für ein Smartphone, das auf einem Regal lag. Der Durchbruch ist wahr geworden! Es bleibt nur, es und das in vier Beiträgen über die Android Camera2-API gesammelte Wissen zu verwenden, um das Bild vom "Nachtsichtgerät" direkt ins Auge zu lenken. Gleichzeitig haben die Hände die Freiheit, eine schwarze Katze in einem dunklen Raum zu fangen. Ohne Licht wird es natürlich nicht funktionieren, Photonen, zumindest ein wenig, aber es ist notwendig. Aber zumindest müssen wir das Niveau von Kotans Peeper im Dunkeln erreichen (oder sogar übertreffen).

Um zu lernen, wie man im Dunkeln sieht, benötigen wir:

1: ein Virtual-Reality-Headset für ein Smartphone (das billigste, das möglich ist)



2: ein Smartphone mit Unterstützung für modernes Guglofich für eine Kamera (es wird definitiv nicht das billigste sein)



3: Kenntnisse der Grundlagen der Android Camera2-API (wir haben es bereits)

Teil eins
Teil zwei
Teil drei
Teil vier

Öffnen Sie ein neues Projekt in Android Studio und beginnen Sie mit der Modellierung des Codes.

Als erstes müssen Sie die tatsächliche VR-Oberfläche zusammenbauen, die im Headset leuchtet.

Layout
<?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>

Die Ausgabe sollte ungefähr so ​​aussehen:



Wie Sie sehen können, erwiesen sich die Texturen als rechteckig, während Entwickler es in jedem VR-Spielzeug für ein Smartphone irgendwie schaffen, sie rund zu machen. Aber darauf werden wir nicht näher eingehen. Weitere Erfahrungen werden noch zeigen, dass es geht.

Dafür schreiben wir tatsächlich eine bescheidene Aktivität, in der uns alles bereits aus früheren Artikeln bekannt ist.

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();
    }
}

Ja und nicht vergessen

Manifest
<?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>


Jetzt schieben wir das Smartphone in das VR-Headset und gehen um das Haus herum. Wir genießen die Vision des Cyborgs mit einer Auflösung von 1280 x 1024 für jedes Auge. Die Empfindungen sind natürlich seltsam, mit einem Verlust der Sichttiefe, aber immer noch cool. Das einzige ist, dass es ein bisschen dunkel aussieht, aber das liegt daran, dass das vordere durchscheinende Headset-Panel stört. Daher muss vor der Kamera des Smartphones ein Loch gebohrt werden. Andererseits gibt es bei den preisgünstigsten VR-Modellen möglicherweise überhaupt kein solches Panel, und es ist wahrscheinlich, dass Sie sich nicht mit Handarbeit beschmutzen müssen.

Jetzt müssen wir nur noch die Google-Kamera-API davon überzeugen, dass wir völlig dunkel sind. Es wäre schön, den Nachtsichtmodus und damit all diese RAW-, HDR-Stapel- und Szenenerkennung durch neuronale Netze zu verwenden .

Schreiben Sie dazu einfach in die Sitzung:

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


und schrauben Sie die Belichtung und Lichtempfindlichkeit maximal ab

 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);


Oh, ich bin blind!



Dies stellt sich heraus, wenn die Katze aus dem Schlafzimmer geworfen wird, wo er verhindert, dass Menschen im Wohnzimmer Sex haben.

Aber natürlich sind dies Aufzählung und Parameter (und es gibt viele davon in der API, und hier sind nur ein paar), die Sie später empirisch optimieren müssen.

Jetzt können wir nur noch auf die Nacht warten. Natürlich nicht mondlos, mit dichter Wolkendecke irgendwo in der Taiga, sondern eine gewöhnliche Nacht mit zufällig fliegenden Photonen. Und hier ist, was passieren wird ...

Obwohl es so aussieht, als ob bei normalen Aufnahmen fast nichts sichtbar ist.



Aber moderne Kameras wirken Wunder und finden immer noch eine schwarze Katze ...



Jetzt können Sie nachts laufen, weil Sie tagsüber wegen Quarantäne nicht gehen können. Theoretisch ist es auch nachts unmöglich, aber wer wird dich sehen, wenn du mit VR-Headsets auf dem Kopf in der Dunkelheit der Nacht herumpirschst ...

All Articles