当前位置: 代码网 > it编程>App开发>Android > Android 使用OpenCV实现实时人脸识别,并绘制到SurfaceView上

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

2024年08月06日 Android 我要评论
按步骤详细介绍如何在Android上,实现实时的人脸识别功能

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

2.3 添加jni方法

然后修改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) {
}

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"
        ).absolutepath
        
        init(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);

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 失败,直接 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);

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博客

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com