Android 使用OpenCV实现实时人脸识别,并绘制到SurfaceView上

1. 前言

上篇文章 我们已经通过一个简单的例子,在Android Studio中接入了OpenCV
之前我们也 在Visual Studio上,使用OpenCV实现人脸识别 中实现了人脸识别的效果。
接着,我们就可以将OpenCV的人脸识别效果移植到Android中了。

1.1 环境说明

  • 操作系统 : windows 10 64
  • Android Studio版本 : Android Studio Giraffe | 2022.3.1
  • OpenCV版本 : OpenCV-4.8.0 (2023年7月最新版)

1.2 实现效果

先来看下实现效果,识别到的人脸会用红框框出来。

在这里插入图片描述
接下来我们来一步步实现上述的效果。

2. 前置操作

2.1 添加权限

<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
)

2.2 新建FaceDetectionActivity

新建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.ConstraintLayoutandroid:background="@color/black"android:layout_width="match_parent"android:layout_height="match_parent"><SurfaceViewandroid: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><LinearLayoutandroid:layout_width="match_parent"android:orientation="horizontal"android:layout_height="wrap_content"><Buttonandroid:text="切换摄像头"android:onClick="switchCamera"android:layout_width="wrap_content"android:layout_height="wrap_content" /></LinearLayout>
</RelativeLayout>

2.3 添加JNI方法

然后修改FaceDetectionActivity为如下代码,这里增加了三个JNI方法

  • init : 初始化OpenCV人脸识别
  • setSurface : 设置SurfaceView
  • postData : 发送视频帧数据
class FaceDetectionActivity : AppCompatActivity() {private lateinit var binding: ActivityFaceDetectionBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityFaceDetectionBinding.inflate(layoutInflater)setContentView(binding.root)//这里省略了申请权限的代码...}//初始化OpenCVexternal fun init(path: String?)//向OpenCV发送一帧的图像数据external fun postData(data: ByteArray?, width: Int, height: Int, cameraId: Int)//设置SurfaceViewexternal 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) {
}

2.4 实现相机预览功能

这里用到了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)
}

3. 初始化OpenCV

3.1 配置OpenCV

接着,我们不要忘了配置OpenCV,这部分详见我的另一篇博客 : Android Studio 接入OpenCV最简单的例子 : 实现灰度图效果

3.2 赋值级联分类器文件

配置好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").absolutePathinit(path)}

4. 实现CascadeDetectorAdapter

这里我们需要将我的另一篇博客中的 在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;

5. 实现init方法

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();
}

5. 设置Surface

Android NDK 中,ANativeWindow 是一个C/C++接口,它提供了一种在 CC++ 代码中访问 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);
}

6. 处理数据并实现人脸识别

处理图像数据的部分我们在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) {//待实现的代码
}

6.1 将图片转化为Mat

参数中的jbyteArray data_,是从Android Java层中获取到的。
然后转化为jbyte *data这个字节数组,接着将其转为一个Mat矩阵。
Mat是是OpenCV最基本的数据结构。它用于存储图像数据。
需要注意的是,由于传入的数据是YUV420,每个像素占1.5byte
所以这个图像的宽度需要传入实际宽度*1.5,宽度不变。
这里的CV_8UC1是单通道的意思,就是说无论是Y分量还是UV分量,都存储在同一个通道里。

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); 

6.2 将YUV转为RGBA

接着,需要将YUV格式转化为RGBA格式,方便后续的操作。
这里的COLOR_YUV2RGBA_NV21表示原始是NV21YUV格式,将其转为RGBA格式。

cvtColor(src, src, COLOR_YUV2RGBA_NV21);

6.3 对图像做翻转和镜像

由于手机摄像头硬件安装在手机里时,和屏幕的方向并不是一致的,所以需要将摄像头拍摄的画面进行旋转。

  • 如果是前置摄像头 : 需要将画面逆时针旋转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);
}

6.4 转为灰度图并进行直方图均衡化处理

接着需要对图像进行灰度和直方图均衡化处理,以便提高人脸识别的准确性和可靠性,这部分和VS实现上是一样。

Mat gray;
//转为灰度图
cvtColor(src, gray, COLOR_RGBA2GRAY);
//直方图均衡化
equalizeHist(gray, gray);

6.5 进行人脸检测

接着就可以调用tracker->process来建人脸检测了。
检测完成后,接着调用tracker->getObjects将检测的人脸位置赋值给faces

std::vector<Rect> faces;
tracker->process(gray);
tracker->getObjects(faces);

6.6 将人脸用红框框出来

接着,将识别到的人脸,用红色的矩形框绘制出来,rectangle方法就是用来绘制一个矩形框的方法。

for (Rect face : faces) {rectangle(src, face, Scalar(255, 0, 0));
}

6.7 将图像渲染到SurfaceView上

6.7.1 设置窗口缓冲区

ANativeWindow_setBuffersGeometry是设置Android Native窗口的缓冲区的大小和像素格式。

if (window) {ANativeWindow_setBuffersGeometry(window, src.cols, src.rows, WINDOW_FORMAT_RGBA_8888);//后续代码在这里编写...
}

6.7.2 将图像数据填充到窗口的缓冲区

这里是个while循环,会不断地将图像数据(RGBA),填充到窗口的缓冲区,最后调用ANativeWindow_unlockAndPost提交刷新,图像就渲染到SurfaceView上了。

ANativeWindow_Buffer window_buffer;
do {//如果 lock 失败,直接 breakif (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) * 4int 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);

6.8 回收资源

最后,别忘了回收资源

src.release();
gray.release();
env->ReleaseByteArrayElements(data_, data, 0);

7. 运行项目

我们可以看到效果如下,至此我们就完成在Android上,使用OpenCV实现实时的人脸识别了。

在这里插入图片描述

8. 本文源码下载

Android和Windows下,使用OpenCV实现人脸识别 示例 Demo

9. OpenCV系列文章

Visual Studio 2022 cmake配置opencv开发环境_opencv visualstudio配置_氦客的博客-CSDN博客
在Visual Studio上,使用OpenCV实现人脸识别_氦客的博客-CSDN博客
Android Studio 接入OpenCV,并实现灰度图效果_氦客的博客-CSDN博客
Android 使用OpenCV实现实时人脸识别,并绘制到SurfaceView上_氦客的博客-CSDN博客

❤️ 如果觉得这篇博文写的不错,对你有所帮助,帮忙点个赞👍
⭐ 这是对我持续输出高质量博文的最好鼓励。😄

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/71074.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java Web】统一处理异常

一个异常处理的ControllerAdvice类。它用于处理Controller注解的控制器中发生的异常。 具体代码功能如下&#xff1a; 导入相关类和方法。声明一个Logger对象&#xff0c;用于日志记录。使用ExceptionHandler注解标记handleException方法&#xff0c;用于处理所有异常。 -嘛在…

【文末送书】全栈开发流程——后端连接数据源(二)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

2023年海外推广怎么做?

答案是&#xff1a;2023海外推广可以选择谷歌SEO谷歌Ads双向运营。 理解当地文化 成功的海外推广首先是建立在对当地文化的深入了解和尊重的基础上。 本土化策略 为了更好地与当地用户互动&#xff0c;你的品牌、产品或服务需要与他们的文化和生活方式紧密相连。 例如&…

javaee spring aop 的五种通知方式

spring配置文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xmlns:aop"http://www.springframework.…

好用免费的Chat GPT(亲测有用)

1、MindLink麦灵 MindLink麦灵 点进登录后 普通用户可以提问100次 2、你问我答 你问我答 无限次数的。 3、灵感 灵感 点击链接后会提示你如何下载使用。 这个有win版和mac版&#xff0c;点击登陆后&#xff0c;每日都会有30次GPT3/3.5的提问。 4、WebTab 在浏览器插件中…

软文推广效果怎么样?这篇揭晓答案

软文推广是一种常用的网络营销手段&#xff0c;它通过以文章形式发布关于产品、服务或品牌的信息&#xff0c;来引起受众的兴趣和关注。相较于直接宣传广告&#xff0c;软文推广更注重内容的质量和吸引力&#xff0c;能够更好地传递信息并提升用户转化率。本文伯乐网络传媒将探…

易点易动库存管理系统与ERP系统打通,帮助企业实现低值易耗品管理

现今,企业管理日趋复杂,无论是核心经营还是辅助环节,都需要依靠信息化手段来提升效率。而低值易耗品作为企业日常运营中的必需品,其管理也面临诸多挑战。传统做法效率低下,容易出错。如何通过信息化手段实现低值易耗品的高效管理,成为许多企业必顾及的一个课题。 易点易动作为…

【广州华锐互动】VR景区云游:打造沉浸式网上虚拟旅游体验

VR景区云游体验是一种全新的旅游体验方式&#xff0c;通过虚拟现实技术&#xff0c;让游客在家中就能身临其境地游览各大景区。这种展示方式不仅节省了游客的时间和金钱&#xff0c;还能让他们在未出发前就对景区有更深入的了解。 通过虚拟现实技术&#xff0c;用户可以在景区内…

.NET之后,再无大创新

回想起来&#xff0c;2001年发布的.NET已经是距离最近的一次软件开发技术的整体创新了&#xff0c;后续的新技术就没有在各个端都这么成功的了。.NET是Windows平台下软件开发技术的巨大变革。在此之前&#xff0c;有VB、C&#xff08;MFC&#xff09;、JSP&#xff0c;在此之后…

前端使用elementui开发后台管理系统的常用功能(持续更新)

前言&#xff1a;本次的文章完全是自己开发中遇到的一些问题&#xff0c;经过不断的修改终于完成的一些功能&#xff0c;当个快捷的查看手册吧~ elementui开发后台管理系统常用功能 高级筛选的封装elementui的表格elementui的表格实现跨页多选回显elementui的表单elementui的日…

[LeetCode周赛复盘] 第 112场双周赛20230903

[LeetCode周赛复盘] 第 112场双周赛20230903 一、本周周赛总结2839. 判断通过操作能否让字符串相等 I1. 题目描述2. 思路分析3. 代码实现 2840. 判断通过操作能否让字符串相等 II1. 题目描述2. 思路分析3. 代码实现 2841. 几乎唯一子数组的最大和1. 题目描述2. 思路分析3. 代码…

如何修复老照片?老照片修复翻新的方法

老旧照片&#xff0c;尤其是黑白照片&#xff0c;往往因为年代久远、保存方式不当等原因而出现褪色、污损、划痕等问题&#xff0c;会比较难以修复&#xff0c;就算是技术精湛的专业修复师&#xff0c;也是需要投入极大时间精力的&#xff0c;效果也是不可预料的。 修复老照片…

【JUC系列-04】精通Synchronized底层的实现原理

JUC系列整体栏目 内容链接地址【一】深入理解JMM内存模型的底层实现原理https://zhenghuisheng.blog.csdn.net/article/details/132400429【二】深入理解CAS底层原理和基本使用https://blog.csdn.net/zhenghuishengq/article/details/132478786【三】熟练掌握Atomic原子系列基本…

AlmaLinux 经济收益增加,红帽 RHEL 源码限制不成威胁

导读红帽在两个月前发布公告表示&#xff0c;将限制对 Red Hat Enterprise Linux (RHEL) 源代码的访问&#xff0c;未来 CentOS Stream 将成为公共 RHEL 相关源代码发布的唯一仓库。对于这一决策&#xff0c;AlmaLinux OS Foundation 主席 Benny Vasquez 则向 SiliconANGLE 表示…

IIS WebDAV配置,https绑定及asp设置

IIS支持标准CGI&#xff0c;因此可以用程序语言针对STDIN和STDOUT开发。 IIS CGI配置和CGI程序FreeBasic, VB6, VC 简单样例_Mongnewer的博客-CSDN博客 IIS支持脚本解释CGI&#xff0c;因此可以用脚本语言针对STDIN和STDOUT开发。 IIS perl python cbrother php脚本语言配置…

开始投简历了

歇了好长时间&#xff0c;也该开始找点事情折腾了。 第一周基本上是没有什么太多的消息&#xff0c;大部分情况就是收到回复的邮件说你很优秀&#xff0c;希望下次合作这种礼节性的拒绝邮件。 给人有点感觉都是在忽悠&#xff0c;有点感觉现在的公司一边到处拒绝&#xff0c;…

草图大师SketchUp Pro 2023 for Mac

SketchUp Pro 2023 for Mac&#xff08;草图大师&#xff09;是一款专业的三维建模软件&#xff0c;由Trimble Inc.开发。它可以用于创建、修改和分享3D模型&#xff0c;包括建筑、家具、景观等。 SketchUp Pro 2023 for Mac提供了简单易学的用户界面和强大的工具集&#xff0…

【Kubernetes理论篇】2023年最新CKA考题+解析

文章目录 第一题&#xff1a;RBAC授权访问控制第二题&#xff1a;Node节点维护第三题&#xff1a;K8S集群版本升级第四题&#xff1a;ETCD数据库备份恢复第五题&#xff1a;NetworkPolicy网络策略第六题&#xff1a;Service四层负载第七题&#xff1a;Ingress七层负载第八题&am…

【Redis】Redis 通用命令、键的过期策略

文章目录 一、基础命令SET 和 GET 二、全局命令KEYSEXISTSDELEXPIRE 和 TTL经典面试题&#xff1a;Redis 中 key 的过期策略是怎么实现的TYPE Redis 有许多种数据结构&#xff0c;但是这些数据结构的 key 的类型都是字符串类型的&#xff08;所以说&#xff0c;Redis 不同的数据…

leetcode 1002. 查找共用字符

2023.9.6 个人感觉这题难度不止简单&#xff0c;考察到的东西还是挺多的。 首先理解题意&#xff0c;可以将题意转化为&#xff1a;求字符串数组中 各字符串共同出现的字符的最小值。 分为三步做&#xff1a; 构造一个哈希表hash&#xff0c;初始化第一个字符串的字母出现频率…