赞
踩
Android OpenCV Demo 预览黑屏
(下一节会实现预览的功能)
import android.Manifest; import android.app.Activity; import android.content.pm.PackageManager; import android.hardware.Camera; import android.os.Build; import android.os.Bundle; import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; public class MainActivity extends Activity implements SurfaceHolder.Callback, Camera .PreviewCallback { static { System.loadLibrary("native-lib"); } private CameraHelper cameraHelper; int cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SurfaceView surfaceView = findViewById(R.id.surfaceView); checkPermission(); surfaceView.getHolder().addCallback(this); cameraHelper = new CameraHelper(cameraId); cameraHelper.setPreviewCallback(this); } public boolean checkPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA }, 1); } return false; } @Override protected void onResume() { super.onResume(); cameraHelper.startPreview(); } @Override protected void onStop() { super.onStop(); cameraHelper.stopPreview(); } @Override public void surfaceCreated(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { //设置surface 用于显示 setSurface(holder.getSurface()); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } @Override public void onPreviewFrame(byte[] data, Camera camera) { //传输数据 postData(data, CameraHelper.WIDTH, CameraHelper.HEIGHT, cameraId); } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { cameraHelper.switchCamera(); cameraId = cameraHelper.getCameraId(); } return super.onTouchEvent(event); } }
CameraHelper类的实现如下:
import android.graphics.ImageFormat; import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.opengl.GLSurfaceView; import android.util.Log; import android.view.SurfaceHolder; public class CameraHelper implements Camera.PreviewCallback { private static final String TAG = "hugang"; public static final int WIDTH = 640; public static final int HEIGHT = 480; private int mCameraId; private Camera mCamera; private byte[] buffer; private Camera.PreviewCallback mPreviewCallback; private Camera.Size size; public CameraHelper(int cameraId) { mCameraId = cameraId; } public void switchCamera() { if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) { mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT; } else { mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK; } stopPreview(); startPreview(); } public int getCameraId() { return mCameraId; } public void stopPreview() { if (mCamera != null) { //预览数据回调接口 mCamera.setPreviewCallback(null); //停止预览 mCamera.stopPreview(); //释放摄像头 mCamera.release(); mCamera = null; } } public void startPreview() { try { //获得camera对象 mCamera = Camera.open(mCameraId); //配置camera的属性 Camera.Parameters parameters = mCamera.getParameters(); //设置预览数据格式为nv21 parameters.setPreviewFormat(ImageFormat.NV21); //这是摄像头宽、高 parameters.setPreviewSize(WIDTH, HEIGHT); // 设置摄像头 图像传感器的角度、方向 mCamera.setParameters(parameters); size = parameters.getPreviewSize(); Log.i(TAG, "----------startPreview width : " + size.width + " height: " + size.height); buffer = new byte[WIDTH * HEIGHT * 3 / 2]; //数据缓存区 mCamera.addCallbackBuffer(buffer); mCamera.setPreviewCallbackWithBuffer(this); //设置预览画面 SurfaceTexture surfaceTexture = new SurfaceTexture(11); mCamera.setPreviewTexture(surfaceTexture); mCamera.startPreview(); } catch (Exception ex) { ex.printStackTrace(); } } public void setPreviewCallback(Camera.PreviewCallback previewCallback) { mPreviewCallback = previewCallback; } @Override public void onPreviewFrame(byte[] data, Camera camera) { // data数据依然是倒的 mPreviewCallback.onPreviewFrame(data, camera); camera.addCallbackBuffer(buffer); } }
AndroidManifest.xml增加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Utils.copyAssets(this, "lbpcascade_frontalface.xml");
上面这行代码我放在了 MainActivity.onCreate 的最后一行
千万注意:一定不要把 lbpcascade_frontalface.xml 这个文件复制到 /data/data/包名/files/ 这个目录下,OpenCV读取这个目录会有问题,后面程序执行完 tracker->getObjects(faces) 后,你得到的faces永远都是empty,以至于你无法得到识别出的人脸区域。
Utils类的实现如下
import android.content.Context; import android.os.Environment; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; public class Utils { public static void copyAssets(Context context, String path) { File model = new File(path); File file = new File("/data/data/com.example.myopencv/cache/", model.getName()); if (file.exists()) { return; } try { FileOutputStream fos = new FileOutputStream(file); InputStream is = context.getAssets().open(path); int len; byte[] b = new byte[2048]; while ((len = is.read(b)) != -1) { fos.write(b, 0, len); } fos.close(); is.close(); } catch (Exception e) { e.printStackTrace(); } } }
native void init(String model);
#include <opencv2/opencv.hpp> #include <android/native_window_jni.h> using namespace cv; DetectionBasedTracker *tracker = 0; // 适配器 class CascadeDetectorAdapter : public DetectionBasedTracker::IDetector{ public: CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector): IDetector(), Detector(detector){ } void detect(const cv::Mat &image, std::vector<cv::Rect> &object){ Detector->detectMultiScale(image, object, scaleFactor, minNeighbours, 0, minObjSize, maxObjSize); } private: CascadeDetectorAdapter(); cv::Ptr<cv::CascadeClassifier> Detector; }; extern "C" JNIEXPORT void JNICALL Java_com_example_myopencv_MainActivity_init(JNIEnv *env, jobject thiz, jstring model_) { pthread_mutex_init(&mutex, 0); if (tracker) { tracker->stop(); delete tracker; tracker = 0; } const char *model = env->GetStringUTFChars(model_, 0); LOGE("============ model: %s ============", model); // new 一个分类器 // CascadeClassifier *cascadeClassifier= new CascadeClassifier(); // 创建级联分类器 Ptr<CascadeClassifier> classifier = makePtr<CascadeClassifier>(model); // 创建检测器,上面创建的级联分类器用于初始化检测器 Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(classifier); // 创建级联分类器 Ptr<CascadeClassifier> classifier_1 = makePtr<CascadeClassifier>(model); // 创建跟踪器,上面创建的级联分类器用于初始化跟踪器 Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(classifier_1); DetectionBasedTracker::Parameters params; // 创建跟踪器,由DetectionBasedTracker的构造函数可知:第一个参数为检测器,第二个参数为分类器 tracker = new DetectionBasedTracker(mainDetector, trackingDetector, params); tracker->run(); env->ReleaseStringUTFChars(model_, model); }
OpenCV初始化好之后,OpenCV 处于运行状态,接下来我们需要给它图像数据
// MainActvivty.java
native void postData(byte[] data, int w, int h, int cameraId);
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
//传输数据
postData(data, CameraHelper.WIDTH, CameraHelper.HEIGHT, cameraId);
}
#include <sys/stat.h> int index = 0; extern "C" JNIEXPORT void JNICALL Java_com_example_myopencv_MainActivity_postData(JNIEnv *env, jobject thiz, jbyteArray data, jint w, jint h, jint camera_id) { jbyte *data = env->GetByteArrayElements(data_, NULL); // 创建一张图片 // 图片高度为 h + h / 2 原因是摄像头的数据为NV21,是YVU格式的,YUV格式的图像的高度就是 h + h / 2 Mat src(h + h / 2, w, CV_8UC1, data); // 将图像格式从 NV21 转换为 RGBA // 这里利用 OpenCV 进行格式转换的效率可能比较低,可以尝试使用 libYUV ,libYUV 使用了 neon 汇编,处理像素会很快 cvtColor(src, src, COLOR_YUV2RGBA_NV21); char picture_name[100]; // 创建文件夹,用于存放图片数据 mkdir("/data/data/com.example.myopencv/cache/test/", 0777); // 将 "/data/data/com.example.myopencv/cache/test/%d.png" 赋值给 picture_name sprintf(picture_name, "/data/data/com.example.myopencv/cache/test/%d.png", index++); // 将 转换格式后的图片写入到 /sdcard/hugang/ 这个路径下 imwrite(picture_name, src); env->ReleaseByteArrayElements(data_, data, 0); }
运行代码,然后手动同步 /data/data/com.example.myopencv/cache/ 这个目录
同步后即可看到生成的图片
打开图片发现问题:
第一个问题的解决方法:
在 cvtColor(src, src, COLOR_YUV2RGBA_NV21) 之后,加入下面的代码
if (camera_id == 1) {
// camera_id == 1 说明是前置摄像头
// 需要将图片旋转 90° , 然后再进行 镜像 处理
// 可以将下面两个函数随便注释掉一个或者两个,对比观察最后生成的图片有什么区别
// 将图片旋转 90°
rotate(src, src, ROTATE_90_COUNTERCLOCKWISE);
// 镜像处理
flip(src, src, 1);
} else {
// 后置摄像头,需要顺时针旋转 90°
rotate(src, src, ROTATE_90_CLOCKWISE);
}
运行代码后,发现图片的角度确实改正了
对于第二个问题,因为我们做图像检测时,都是需要将彩色的图片变为灰度图进行处理,因此引入下面的代码:
// 将图像转换为灰度图
Mat gray_img;
cvtColor(src, gray_img, COLOR_RGBA2GRAY);
... ...
// 原来的imwrite(picture_name, src); 要改成下面这行
imwrite(picture_name, gray_img);
为了增强灰度图的对比度,我们还要在刚才的 cvtColor(src, gray_img, COLOR_RGBA2GRAY) 后面增加一行代码
// 用于增强灰度图的对比度(内部采用了直方图均衡化的方式)
equalizeHist(gray_img, gray_img);
运行后,发现灰度图的对比度确实增强了
接下来,我们需要将灰度图传给 跟踪器,让它帮我们识别人脸
extern "C" JNIEXPORT void JNICALL Java_com_example_myopencv_MainActivity_postData(JNIEnv *env, jobject thiz, jbyteArray data_, jint w, jint h, jint camera_id) { jbyte *data = env->GetByteArrayElements(data_, NULL); // 创建一张图片 // 图片高度为 h + h / 2 原因是摄像头的数据为NV21,是YVU格式的,YUV格式的图像的高度就是 h + h / 2 Mat src(h + h / 2, w, CV_8UC1, data); // 将图像格式从 NV21 转换为 RGBA // 这里利用 OpenCV 进行格式转换的效率可能比较低,可以尝试使用 libYUV ,libYUV 使用了 neon 汇编,处理像素会很快 cvtColor(src, src, COLOR_YUV2RGBA_NV21); if (camera_id == 1) { // camera_id == 1 说明是前置摄像头 // 需要将图片旋转 90° , 然后再进行 镜像 处理 // 可以将下面两个函数随便注释掉一个或者两个,对比观察最后生成的图片有什么区别 // 将图片旋转 90° rotate(src, src, ROTATE_90_COUNTERCLOCKWISE); // 镜像处理 flip(src, src, 1); } else { // 后置摄像头,需要顺时针旋转 90° rotate(src, src, ROTATE_90_CLOCKWISE); } // 将图像转换为灰度图 Mat gray_img; cvtColor(src, gray_img, COLOR_RGBA2GRAY); // 用于增强灰度图的对比度(内部采用了直方图均衡化的方式) equalizeHist(gray_img, gray_img); char picture_name[100]; // 创建文件夹,用于存放图片数据 mkdir("/data/data/com.example.myopencv/cache/test/", 0777); // 将 "/data/data/com.example.myopencv/cache/test/%d.png" 赋值给 picture_name sprintf(picture_name, "/data/data/com.example.myopencv/cache/test/%d.png", index++); // 将 转换格式后的图片写入到 /sdcard/hugang/ 这个路径下 imwrite(picture_name, gray_img); // 将灰度图传给跟踪器 tracker->process(gray_img); std::vector<Rect> faces; tracker->getObjects(faces); LOGE("========= faces size : %d =========", faces.size()); // 创建文件夹,用于存放识别出的人脸的图片 mkdir("/data/data/com.example.myopencv/cache/face_img/", 0777); for (Rect face : faces) { sprintf(picture_name, "/data/data/com.example.myopencv/cache/face_img/%d.png", index++); Mat face_rect; face_rect = gray_img(face).clone(); imwrite(picture_name, face_rect); } env->ReleaseByteArrayElements(data_, data, 0); }
运行后得到 face_img 目录下识别出的人脸的照片
注意:建议不要戴眼镜,否则很难识别出人脸
resize(face_rect, face_rect, Size(24, 24));
本节的代码就讲到这里,实际上我们的 app 打开后,预览画面一直都是黑色的,下节介绍如何显示预览画面
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。