使用opencv去访问android设备摄像头, C++库是无法获取到android设备硬件的,所有需要借助Opencv对android提供的java库进行访问android设备摄像头。在opencv官方下载AndroidSDK,导入项目中就可以使用了。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!--JavaCameraView最终继承自SurfaceView,显示摄像头帧数据的展示-->
<org.opencv.android.JavaCameraView
android:id="@+id/cameraView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:camera_id="back"//后置摄像头
/>
</android.support.constraint.ConstraintLayout>
JavaCameraView继承自CameraBridgeViewBase,CameraBridgeViewBase又继承自SurfaceView,JavaCameraView是可以显示摄像头捕获到的帧数据的View,CameraBridgeViewBase类中CvCameraViewListener2接口提供了摄像头onCameraViewStarted、onCameraViewStopped以及onCameraFrame回调。我们要对摄像头捕获的每一帧数据进行操作就需要再OnCameraFrame回调中进行处理。通过一个native函数,在将摄像头的每一帧数据地址传给C++,对帧数据进行操作。代码如下:
public class CameraOpenCVActivity extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 {//
private JavaCameraView cameraView;
private Mat rgba;
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera_opencv);
cameraView = findViewById(R.id.cameraView);
cameraView.setCvCameraViewListener(this);
//权限请求
RxPermissions rxPermission = new RxPermissions(this);
rxPermission
.requestEachCombined(Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE)
.subscribe(permission -> {
if (permission.granted) {
} else if (permission.shouldShowRequestPermissionRationale) {
} else {
}
});
}
//NDK对每一帧数据进行操作
public static native void nativeRgba(long jrgba);
/**
* 当摄像机预览开始时,这个方法就会被调用。在调用该方法之后,框架将通过onCameraFrame()回调向客户端发送。
*
* @param width - 帧的宽度
* @param height - 帧的高度
*/
@Override
public void onCameraViewStarted(int width, int height) {
//定义Mat对象
rgba = new Mat(width, height, CvType.CV_8UC4);
}
/**
* 当摄像机预览由于某种原因被停止时,这个方法就会被调用。
*在调用这个方法之后,不会通过onCameraFrame()回调来传递任何帧。
*/
@Override
public void onCameraViewStopped() {
rgba.release();
}
/**
* 当需要完成框架的交付时,将调用此方法。
*返回值-是一个修改后的帧,需要在屏幕上显示。
* TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
*
* @param inputFrame
*/
@Override
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
rgba = inputFrame.rgba();
//得到当前一帧图像的内存地址
long addr = rgba.getNativeObjAddr();
//对一帧图像进行处理
nativeRgba(addr);
//得到一帧灰度图
// rgba = inputFrame.gray();
return rgba;
}
@Override
protected void onResume() {
super.onResume();
if (!OpenCVLoader.initDebug()) {
} else {
cameraView.enableView();
}
}
@Override
protected void onPause() {
super.onPause();
if (cameraView != null) {
cameraView.disableView();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (cameraView != null) {
cameraView.disableView();
}
}
}
在C++中对每一帧数据进行单通道处理,并做了SobelX和Y方向的边缘检测,代码和结果如下:
Mat &img = *(Mat*)jrgba;
Mat out;
cvtColor(img,out,COLOR_RGBA2GRAY);
Mat outimgX,outimgY,outXY;
//x方向sobel梯度
Sobel(out,outimgX,CV_8U,1,0);
//y方向sobel梯度
Sobel(out,outimgY,CV_8U,0,1);
//合并梯度
addWeighted(outimgX,0.5,outimgY,0.5,0,outXY);
uchar *ptr = img.ptr(0);
uchar *outPtr = outXY.ptr(0);
for (int i = 0; i < img.rows * img.cols; ++i) {
ptr[4*i+0] = outPtr[I];
ptr[4*i+1] = outPtr[I];
ptr[4*i+2] = outPtr[I];
}
![](https://img.haomeiwen.com/i1184731/db35da067f808120.png)
当前我使用的是后置摄像头,我们又发现了一个问题,帧数据是显示出来,但是图像是横着的,怎么正过来呢?很简单,我们既然获取到了每一帧数据的矩阵,那么我们直接就可以对矩阵进行操作。原理与代码如下:
Mat &img = *(Mat*)jrgba;
Mat imgT(height,width,CV_8UC4);
Mat imgF(height,width,CV_8UC4);
// //图片倒置
transpose(img,imgT);
/**
* 重定义图片大小
* 第三个参数dsize:size格式的图片大小
* 第四个参数dx:x方向的图片缩放比
* 第五个参数dy:y方向的缩放比
* 第六个参数:插值方式,一般情况使用默认值(线性插值)
* dsize不为0的时候,dx和dy是无效的
*
*/
resize(imgT,imgF,Size(width,height));
/**
* 翻转图片
* 第三个参数,0以x轴进行翻转,1以y轴翻转,-1以xy同时翻转
*/
flip(imgF,img,1);
![](https://img.haomeiwen.com/i1184731/c1bec51db8f1a0be.png)
哈哈!图像正过来了吧!当前是后置摄像头,那么前置摄像头会不会也是这样的解决办法呢?那我们代码撸起,看那看会如何呢?
![](https://img.haomeiwen.com/i1184731/43cbfaa57e503c23.png)
此时我们发现,用于后置摄像头相同的操作去解决,并没有达到预期的效果,很明显图像是倒置的并且左右方向也是反的。于是乎,深思之后,发现前置摄像头与后置摄像头的图像其实就相当于一个照镜子的过程,我们可以在倒置矩阵之后,再进行一次X,Y方向上的同时矩阵翻转,不就可以了,一切想明白了之后,试了试,果然达到了预期目标效果。图像与代码如下:
Mat &img = *(Mat*)jrgba;
Mat imgT(height,width,CV_8UC4);
Mat imgF(height,width,CV_8UC4);
// //图片倒置
transpose(img,imgT);
/**
* 重定义图片大小
* 第三个参数dsize:size格式的图片大小
* 第四个参数dx:x方向的图片缩放比
* 第五个参数dy:y方向的缩放比
* 第六个参数:插值方式,一般情况使用默认值(线性插值)
* dsize不为0的时候,dx和dy是无效的
*
*/
resize(imgT,imgF,Size(width,height));
/**
* 翻转图片
* 第三个参数,0以x轴进行翻转,1以y轴翻转,-1以xy同时翻转
*/
flip(imgF,img,-1);//进行XY方向同时翻转
![](https://img.haomeiwen.com/i1184731/84cd62e4f3ab1fb9.png)
哈哈!我又离人脸识别进了一步,很开森的结束了这篇博客!
网友评论