赞
踩
上篇文章 我们已经通过一个简单的例子,在Android Studio
中接入了OpenCV
。
之前我们也 在Visual Studio上,使用OpenCV实现人脸识别 中实现了人脸识别的效果。
接着,我们就可以将OpenCV
的人脸识别效果移植到Android
中了。
windows 10 64
位Android Studio
版本 : Android Studio Giraffe | 2022.3.1
OpenCV
版本 : OpenCV-4.8.0 (2023年7月最新版)
先来看下实现效果,识别到的人脸会用红框框出来。
接下来我们来一步步实现上述的效果。
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
ActivityCompat.requestPermissions(
this@FaceDetectionActivity,
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO
),
1
)
新建FaceDetectionActivity
,并将其设为默认的Activity
,然后修改其XML
布局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" tools:context=".MainActivity"> <androidx.constraintlayout.widget.ConstraintLayout android:background="@color/black" android:layout_width="match_parent" android:layout_height="match_parent"> <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="w,4:3" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> <LinearLayout android:layout_width="match_parent" android:orientation="horizontal" android:layout_height="wrap_content"> <Button android:text="切换摄像头" android:onClick="switchCamera" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </RelativeLayout>
然后修改FaceDetectionActivity
为如下代码,这里增加了三个JNI
方法
init
: 初始化OpenCV
人脸识别setSurface
: 设置SurfaceView
postData
: 发送视频帧数据class FaceDetectionActivity : AppCompatActivity() { private lateinit var binding: ActivityFaceDetectionBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityFaceDetectionBinding.inflate(layoutInflater) setContentView(binding.root) //这里省略了申请权限的代码... } //初始化OpenCV external fun init(path: String?) //向OpenCV发送一帧的图像数据 external fun postData(data: ByteArray?, width: Int, height: Int, cameraId: Int) //设置SurfaceView external fun setSurface(surface: Surface?) companion object { init { System.loadLibrary("myopencvtest") } } }
同时,需要在native-lib.cpp
中添加这三个JNI
方法,这里的com_heiko_myopencvtest_FaceDetectionActivity
需要改为你实际的包名和类名。
extern "C" JNIEXPORT void JNICALL Java_com_heiko_myopencvtest_FaceDetectionActivity_init(JNIEnv *env, jobject thiz, jstring path) { } extern "C" JNIEXPORT void JNICALL Java_com_heiko_myopencvtest_FaceDetectionActivity_postData(JNIEnv *env, jobject thiz, jbyteArray data, jint width, jint height, jint camera_id) { } extern "C" JNIEXPORT void JNICALL Java_com_heiko_myopencvtest_FaceDetectionActivity_setSurface(JNIEnv *env, jobject thiz, jobject surface) { }
这里用到了Camera1 API
,直接使用CameraHelper
这个工具类接入即可,这部分详见我的另一篇博客 Android 使用Camera1的工具类CameraHelper快速实现相机预览、拍照功能
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityFaceDetectionBinding.inflate(layoutInflater)
setContentView(binding.root)
//这里省略了申请权限的代码...
val surfaceView = findViewById<SurfaceView>(R.id.surfaceView)
surfaceView.holder.addCallback(this)
cameraHelper = CameraHelper(cameraId)
cameraHelper.setPreviewCallback(this)
}
接着,我们不要忘了配置OpenCV
,这部分详见我的另一篇博客 : Android Studio 接入OpenCV最简单的例子 : 实现灰度图效果
配置好OpenCV
,我们要将模型,也就是人脸识别的级联分类器文件haarcascade_frontalface_alt.xml
复制到asserts
文件夹下。
当我们启动App
的时候,需要将该文件复制到外置存储中。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//省略了其他代码...
//Utils类可以在本文末尾复制
Utils.copyAssets(this@FaceDetectionActivity, "haarcascade_frontalface_alt.xml")
}
拷贝完成后,调用init()
方法,传入路径
override fun onResume() {
super.onResume()
//省略了其他代码...
//Utils类可以在本文末尾复制
val path = Utils.getModelFile(
this@FaceDetectionActivity,
"haarcascade_frontalface_alt.xml"
).absolutePath
init(path)
}
这里我们需要将我的另一篇博客中的 在Visual Studio上,使用OpenCV实现人脸识别 (下面统称为VS实现
) 中的代码移植过来。
这里创建了CascadeDetectorAdapter
,实现了DetectionBasedTracker::IDetector
接口,和VS实现上代码是一样的。
class CascadeDetectorAdapter : public DetectionBasedTracker::IDetector { public: CascadeDetectorAdapter(cv::Ptr<cv::CascadeClassifier> detector) : IDetector(), Detector(detector) { CV_Assert(detector); } void detect(const cv::Mat& Image, std::vector<cv::Rect>& objects) { Detector->detectMultiScale(Image, objects, scaleFactor, minNeighbours, 0, minObjSize, maxObjSize); } virtual ~CascadeDetectorAdapter() { } private: CascadeDetectorAdapter(); cv::Ptr<cv::CascadeClassifier> Detector; }; cv::Ptr<DetectionBasedTracker> tracker;
init
方法也是一样的,声明tracker
对象,并调用run()
方法,会启动一个异步线程,后面的人脸检测会在这个异步线程进行检测了。(这个是保障实时人脸检测不卡的前提)
//cv::Ptr<DetectionBasedTracker> tracker; DetectionBasedTracker *tracker = 0; extern "C" JNIEXPORT void JNICALL Java_com_heiko_myopencvtest_FaceDetectionActivity_init(JNIEnv *env, jobject thiz, jstring path) { string stdFileName = env->GetStringUTFChars(path, 0); //创建一个主检测适配器 cv::Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>( makePtr<CascadeClassifier>(stdFileName)); //创建一个跟踪检测适配器 cv::Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>( makePtr<CascadeClassifier>(stdFileName)); //创建跟踪器 DetectionBasedTracker::Parameters DetectorParams; //tracker = makePtr<DetectionBasedTracker>(mainDetector, trackingDetector, DetectorParams); tracker= new DetectionBasedTracker(mainDetector, trackingDetector, DetectorParams); tracker->run(); }
在 Android NDK
中,ANativeWindow
是一个C/C++
接口,它提供了一种在 C
或 C++
代码中访问 Android Surface
的方式。通过使用ANativeWindow
接口,开发者可以在NDK
中直接访问和操作Android
窗口系统,实现图形处理和渲染操作。
这里我们将SurfaceView
传到JNI
中,方便C/C++代码
后边将图像实时渲染到Android SurfaceView
上。
#include <android/native_window_jni.h>
ANativeWindow *window = 0;
extern "C"
JNIEXPORT void JNICALL
Java_com_heiko_myopencvtest_FaceDetectionActivity_setSurface(JNIEnv *env, jobject thiz,
jobject surface) {
if (window) {
ANativeWindow_release(window);
window = 0;
}
window = ANativeWindow_fromSurface(env, surface);
}
处理图像数据的部分我们在postData
中实现,这部分会先对图像进行处理,然后进行人脸识别,并渲染到SurfaceView
上。
extern "C"
JNIEXPORT void JNICALL
Java_com_heiko_myopencvtest_FaceDetectionActivity_postData(JNIEnv *env, jobject instance, jbyteArray data_,
jint w, jint h, jint cameraId) {
//待实现的代码
}
参数中的jbyteArray data_
,是从Android Java
层中获取到的。
然后转化为jbyte *data
这个字节数组,接着将其转为一个Mat
矩阵。
Mat
是是OpenCV
最基本的数据结构。它用于存储图像数据。
需要注意的是,由于传入的数据是YUV420
,每个像素占1.5byte
,
所以这个图像的宽度需要传入实际宽度*1.5
,宽度不变。
这里的CV_8UC1
是单通道的意思,就是说无论是Y
分量还是U
、V
分量,都存储在同一个通道里。
jbyte *data = env->GetByteArrayElements(data_, NULL);
Mat src(h + h / 2, w, CV_8UC1, data);
要获取Mat对象中每个通道的数据,可以使用OpenCV提供的函数和方法。
对于一个BGR图像(即具有三个通道的图像),每个通道的数据可以分别获取并进行处理。
以下是一些示例代码,演示如何获取每个通道的数据:// 假设img是包含BGR图像的Mat对象 // 获取B通道数据 Mat bChannel = img.channel(0); // 获取G通道数据 Mat gChannel = img.channel(1); //获取R通道数据 Mat rChannel = img.channel(2);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
接着,需要将YUV
格式转化为RGBA
格式,方便后续的操作。
这里的COLOR_YUV2RGBA_NV21
表示原始是NV21
的YUV
格式,将其转为RGBA
格式。
cvtColor(src, src, COLOR_YUV2RGBA_NV21);
由于手机摄像头硬件安装在手机里时,和屏幕的方向并不是一致的,所以需要将摄像头拍摄的画面进行旋转。
90度
,并做左右镜像操作90度
if (cameraId == 1) {
//前置摄像头
rotate(src, src, ROTATE_90_COUNTERCLOCKWISE);
//1:左右镜像
//0:上下镜像
flip(src, src, 1);
} else {
//顺时针旋转90度
rotate(src, src, ROTATE_90_CLOCKWISE);
}
接着需要对图像进行灰度和直方图均衡化处理,以便提高人脸识别的准确性和可靠性,这部分和VS实现上是一样。
Mat gray;
//转为灰度图
cvtColor(src, gray, COLOR_RGBA2GRAY);
//直方图均衡化
equalizeHist(gray, gray);
接着就可以调用tracker->process
来建人脸检测了。
检测完成后,接着调用tracker->getObjects
将检测的人脸位置赋值给faces
。
std::vector<Rect> faces;
tracker->process(gray);
tracker->getObjects(faces);
接着,将识别到的人脸,用红色的矩形框绘制出来,rectangle
方法就是用来绘制一个矩形框的方法。
for (Rect face : faces) {
rectangle(src, face, Scalar(255, 0, 0));
}
ANativeWindow_setBuffersGeometry
是设置Android Native
窗口的缓冲区的大小和像素格式。
if (window) {
ANativeWindow_setBuffersGeometry(window, src.cols, src.rows, WINDOW_FORMAT_RGBA_8888);
//后续代码在这里编写...
}
这里是个while循环,会不断地将图像数据(RGBA
),填充到窗口的缓冲区,最后调用ANativeWindow_unlockAndPost
提交刷新,图像就渲染到SurfaceView
上了。
ANativeWindow_Buffer window_buffer; do { //如果 lock 失败,直接 break if (ANativeWindow_lock(window, &window_buffer, 0)) { ANativeWindow_release(window); window = 0; break; } //将window_buffer.bits转化为 uint8_t * uint8_t *dst_data = static_cast<uint8_t *>(window_buffer.bits); //stride : 一行多少个数据 (RGBA) * 4 int dst_linesize = window_buffer.stride * 4; //一行一行拷贝 for (int i = 0; i < window_buffer.height; ++i) { memcpy(dst_data + i * dst_linesize, src.data + i * src.cols * 4, dst_linesize); } //提交刷新 ANativeWindow_unlockAndPost(window); } while (0);
最后,别忘了回收资源
src.release();
gray.release();
env->ReleaseByteArrayElements(data_, data, 0);
我们可以看到效果如下,至此我们就完成在Android
上,使用OpenCV
实现实时的人脸识别了。
Android和Windows下,使用OpenCV实现人脸识别 示例 Demo
Visual Studio 2022 cmake配置opencv开发环境_opencv visualstudio配置_氦客的博客-CSDN博客
在Visual Studio上,使用OpenCV实现人脸识别_氦客的博客-CSDN博客
Android Studio 接入OpenCV,并实现灰度图效果_氦客的博客-CSDN博客
Android 使用OpenCV实现实时人脸识别,并绘制到SurfaceView上_氦客的博客-CSDN博客
❤️ 如果觉得这篇博文写的不错,对你有所帮助,帮忙点个赞
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/566801
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。