前提・実現したいこと
Android Studio上でOpenCVを使用してカメラから取得した映像の黒要素とそれ以外に分けて出力
を行い、さらに任意の領域(xy座標を指定するなどして)の黒色ピクセルを数えて
テキストに%表示したいと考えています。
###開発・実行環境
[開発]
Android Studio 4.0.1
MacBook Pro (macOS Mojave ver10.14.6)
-言語- java
[実行]
OPPO Reno A 128GB
発生している問題・エラーメッセージ
黒要素とそれ以外に分けることはできたのですが
「任意の領域の黒色ピクセルを数える」というところが実現できていません。
「領域」というのが何を指すのか自分自身でも決めきれていません。
xy座標を指定するなどして長方形を描き、その長方形内の黒色の割合を求めれたらと思っています。
該当のソースコード
java
1package com.example.blackline; 2 3import androidx.annotation.NonNull; 4import androidx.appcompat.app.AppCompatActivity; 5import androidx.camera.core.Camera; 6import androidx.camera.core.CameraSelector; 7import androidx.camera.core.ImageAnalysis; 8import androidx.camera.core.ImageProxy; 9import androidx.camera.core.Preview; 10import androidx.camera.lifecycle.ProcessCameraProvider; 11import androidx.camera.view.PreviewView; 12import androidx.core.app.ActivityCompat; 13import androidx.core.content.ContextCompat; 14import androidx.lifecycle.LifecycleOwner; 15 16import android.content.Context; 17import android.content.pm.PackageManager; 18import android.graphics.Bitmap; 19import android.os.Bundle; 20import android.util.Log; 21import android.view.Surface; 22import android.widget.ImageView; 23import android.widget.TextView; 24 25import com.google.common.util.concurrent.ListenableFuture; 26 27import org.opencv.android.Utils; 28import org.opencv.core.Core; 29import org.opencv.core.Mat; 30import org.opencv.core.Point; 31import org.opencv.core.Scalar; 32import org.opencv.imgproc.Imgproc; 33import org.opencv.core.Rect; 34 35import java.nio.ByteBuffer; 36import java.util.concurrent.ExecutorService; 37import java.util.concurrent.Executors; 38 39import static org.opencv.core.CvType.CV_8SC3; 40import static org.opencv.core.CvType.CV_8UC1; 41import static org.opencv.imgproc.Imgproc.medianBlur; 42 43public class MainActivity extends AppCompatActivity { 44 /*** Fixed values ***/ 45 private static final String TAG = "MyApp"; 46 private int REQUEST_CODE_FOR_PERMISSIONS = 1234;; 47 private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA", "android.permission.WRITE_EXTERNAL_STORAGE"}; 48 49 /*** Views ***/ 50 private PreviewView previewView; 51 private ImageView imageView; 52 private TextView textView; 53 /*** For CameraX ***/ 54 private Camera camera = null; 55 private Preview preview = null; 56 private ImageAnalysis imageAnalysis = null; 57 private ExecutorService cameraExecutor = Executors.newSingleThreadExecutor(); 58 int i = 0; 59 int y = 0; 60 int x = 0; 61 int binary; 62 63 static { 64 System.loadLibrary("opencv_java4"); 65 } 66 @Override 67 protected void onCreate(Bundle savedInstanceState) { 68 super.onCreate(savedInstanceState); 69 setContentView(R.layout.activity_main); 70 71 previewView = findViewById(R.id.previewView); 72 imageView = findViewById(R.id.imageView); 73// textView =findViewById(R.id.textView); 74 75 if (checkPermissions()) { 76 startCamera(); 77 } else { 78 ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_FOR_PERMISSIONS); 79 } 80 } 81 82 private void startCamera() { 83 final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); 84 Context context = this; 85 cameraProviderFuture.addListener(new Runnable() { 86 @Override 87 public void run() { 88 try { 89 ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); 90 preview = new Preview.Builder().build(); 91 imageAnalysis = new ImageAnalysis.Builder().build(); 92 imageAnalysis.setAnalyzer(cameraExecutor, new MyImageAnalyzer()); 93 CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build(); 94 95 cameraProvider.unbindAll(); 96 camera = cameraProvider.bindToLifecycle((LifecycleOwner)context, cameraSelector, preview, imageAnalysis); 97 preview.setSurfaceProvider(previewView.createSurfaceProvider(camera.getCameraInfo())); 98 } catch(Exception e) { 99 Log.e(TAG, "[startCamera] Use case binding failed", e); 100 } 101 } 102 }, ContextCompat.getMainExecutor(this)); 103 } 104 105 private class MyImageAnalyzer implements ImageAnalysis.Analyzer { 106 private Mat matPrevious = null; 107 @Override 108 public void analyze(@NonNull ImageProxy image) { 109 /* Create cv::mat(RGB888) from image(NV21) */ 110 Mat matOrg = getMatFromImage(image); 111 /* Fix image rotation (it looks image in PreviewView is automatically fixed by CameraX???) */ 112 Mat mat = fixMatRotation(matOrg); 113 medianBlur(mat,mat, 5); 114// Mat mat_bin = mat.clone(); 115// Mat mat_hsv = mat.clone(); 116 // ↓ここから黒色とそれ以外に分けています↓ 117 Imgproc.cvtColor(mat,mat, Imgproc.COLOR_RGBA2BGR); 118 Imgproc.cvtColor(mat,mat, Imgproc.COLOR_BGR2HSV); 119 Core.inRange(mat, new Scalar(0,0,0), new Scalar(179,128,100),mat); 120 Imgproc.cvtColor(mat,mat, Imgproc.COLOR_GRAY2BGRA); 121// Imgproc.rectangle(mat,new Point(200,250),new Point(300,500),new Scalar(76,255,0),2); 122 Log.i(TAG, "[analyze] width = " + image.getWidth() + ", height = " + image.getHeight() + "Rotation = " + previewView.getDisplay().getRotation()); 123 Log.i(TAG, "[analyze] mat width = " + matOrg.cols() + ", mat height = " + matOrg.rows()); 124 125 /* Do some image processing */ 126// Mat matOutput = new Mat(mat.rows(), mat.cols(), mat.type()); 127// if (matPrevious == null) matPrevious = mat; 128// Core.absdiff(mat, matPrevious, matOutput); 129// matPrevious = mat; 130 131 /* Draw something for test */ 132 Imgproc.rectangle(mat, new Rect(10, 10, 100, 100), new Scalar(255, 0, 0)); 133// Imgproc.putText(mat, "mat",new Point(100,100), 10, 10, new Scalar(255, 0, 0)); 134 /* Convert cv::mat to bitmap for drawing*/ 135 Bitmap bitmap = Bitmap.createBitmap(mat.cols(),mat.rows(),Bitmap.Config.ARGB_8888); 136 Utils.matToBitmap(mat,bitmap); 137 //↑ここまで黒色とそれ以外に分けています↑ 138 /* Display the result onto ImageView */ 139 runOnUiThread(new Runnable() { 140 @Override 141 public void run() { 142 imageView.setImageBitmap(bitmap); 143 } 144 }); 145 146 /* Close the image otherwise, this function is not called next time */ 147 image.close(); 148 } 149 150 private Mat getMatFromImage(ImageProxy image) { 151 /* https://stackoverflow.com/questions/30510928/convert-android-camera2-api-yuv-420-888-to-rgb */ 152 ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); 153 ByteBuffer uBuffer = image.getPlanes()[1].getBuffer(); 154 ByteBuffer vBuffer = image.getPlanes()[2].getBuffer(); 155 int ySize = yBuffer.remaining(); 156 int uSize = uBuffer.remaining(); 157 int vSize = vBuffer.remaining(); 158 byte[] nv21 = new byte[ySize + uSize + vSize]; 159 yBuffer.get(nv21, 0, ySize); 160 vBuffer.get(nv21, ySize, vSize); 161 uBuffer.get(nv21, ySize + vSize, uSize); 162 Mat yuv = new Mat(image.getHeight() + image.getHeight() / 2, image.getWidth(), CV_8UC1); 163 yuv.put(0, 0, nv21); 164 Mat mat = new Mat(); 165 Imgproc.cvtColor(yuv, mat, Imgproc.COLOR_YUV2RGB_NV21, 3); 166 return mat; 167 } 168 169 private Mat fixMatRotation(Mat matOrg) { 170 Mat mat; 171 switch (previewView.getDisplay().getRotation()){ 172 default: 173 case Surface.ROTATION_0: 174 mat = new Mat(matOrg.cols(), matOrg.rows(), matOrg.type()); 175 Core.transpose(matOrg, mat); 176 Core.flip(mat, mat, 1); 177 break; 178 case Surface.ROTATION_90: 179 mat = matOrg; 180 break; 181 case Surface.ROTATION_270: 182 mat = matOrg; 183 Core.flip(mat, mat, -1); 184 break; 185 } 186 return mat; 187 } 188 } 189 190 private boolean checkPermissions(){ 191 for(String permission : REQUIRED_PERMISSIONS){ 192 if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){ 193 return false; 194 } 195 } 196 return true; 197 } 198 199 @Override 200 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 201// super.onRequestPermissionsResult(requestCode, permissions, grantResults); 202 if(requestCode == REQUEST_CODE_FOR_PERMISSIONS){ 203 if(checkPermissions()){ 204 startCamera(); 205 } else{ 206 Log.i(TAG, "[onRequestPermissionsResult] Failed to get permissions"); 207 this.finish(); 208 } 209 } 210 } 211}
コメントの「↓ここから黒色とそれ以外に分けています↓」 と 「↑ここまで黒色とそれ以外に分けています↑」
の部分がOpenCVを使った画像処理になります。
多分この続きに黒色ピクセルを数えるという処理を書くのが適切なんだろうとは思っています。
上記の部分以外は、カメラからの映像を出力などに使っているので気にしなくても大丈夫だと思います。
これらのプログラムをスマホに書き込むと以下のような映像が取得できます。
理想は下の白黒映像のどこかに分かりやすく「〇〇ピクセル」のように出力したいと考えています。