Android Studio 集成OpenCV
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
#include
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
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));