1. 前言
上篇文章 我們已經(jīng)通過一個(gè)簡(jiǎn)單的例子,在Android Studio
中接入了OpenCV
。
之前我們也 在Visual Studio上,使用OpenCV實(shí)現(xiàn)人臉識(shí)別 中實(shí)現(xiàn)了人臉識(shí)別的效果。
接著,我們就可以將OpenCV
的人臉識(shí)別效果移植到Android
中了。
1.1 環(huán)境說明
- 操作系統(tǒng) :
windows 10 64
位 -
Android Studio
版本 :Android Studio Giraffe | 2022.3.1
-
OpenCV
版本 :OpenCV-4.8.0 (2023年7月最新版)
1.2 實(shí)現(xiàn)效果
先來看下實(shí)現(xiàn)效果,識(shí)別到的人臉會(huì)用紅框框出來。
接下來我們來一步步實(shí)現(xiàn)上述的效果。
2. 前置操作
2.1 添加權(quán)限
<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
,并將其設(shè)為默認(rèn)的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
為如下代碼,這里增加了三個(gè)JNI
方法
-
init
: 初始化OpenCV
人臉識(shí)別 -
setSurface
: 設(shè)置SurfaceView
-
postData
: 發(fā)送視頻幀數(shù)據(jù)
class FaceDetectionActivity : AppCompatActivity() {
private lateinit var binding: ActivityFaceDetectionBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityFaceDetectionBinding.inflate(layoutInflater)
setContentView(binding.root)
//這里省略了申請(qǐng)權(quán)限的代碼...
}
//初始化OpenCV
external fun init(path: String?)
//向OpenCV發(fā)送一幀的圖像數(shù)據(jù)
external fun postData(data: ByteArray?, width: Int, height: Int, cameraId: Int)
//設(shè)置SurfaceView
external fun setSurface(surface: Surface?)
companion object {
init {
System.loadLibrary("myopencvtest")
}
}
}
同時(shí),需要在native-lib.cpp
中添加這三個(gè)JNI
方法,這里的com_heiko_myopencvtest_FaceDetectionActivity
需要改為你實(shí)際的包名和類名。
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 實(shí)現(xiàn)相機(jī)預(yù)覽功能
這里用到了Camera1 API
,直接使用CameraHelper
這個(gè)工具類接入即可,這部分詳見我的另一篇博客 Android 使用Camera1的工具類CameraHelper快速實(shí)現(xiàn)相機(jī)預(yù)覽、拍照功能
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityFaceDetectionBinding.inflate(layoutInflater)
setContentView(binding.root)
//這里省略了申請(qǐng)權(quán)限的代碼...
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最簡(jiǎn)單的例子 : 實(shí)現(xiàn)灰度圖效果
3.2 賦值級(jí)聯(lián)分類器文件
配置好OpenCV
,我們要將模型,也就是人臉識(shí)別的級(jí)聯(lián)分類器文件haarcascade_frontalface_alt.xml
復(fù)制到asserts
文件夾下。
當(dāng)我們啟動(dòng)App
的時(shí)候,需要將該文件復(fù)制到外置存儲(chǔ)中。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//省略了其他代碼...
//Utils類可以在本文末尾復(fù)制
Utils.copyAssets(this@FaceDetectionActivity, "haarcascade_frontalface_alt.xml")
}
拷貝完成后,調(diào)用init()
方法,傳入路徑
override fun onResume() {
super.onResume()
//省略了其他代碼...
//Utils類可以在本文末尾復(fù)制
val path = Utils.getModelFile(
this@FaceDetectionActivity,
"haarcascade_frontalface_alt.xml"
).absolutePath
init(path)
}
4. 實(shí)現(xiàn)CascadeDetectorAdapter
這里我們需要將我的另一篇博客中的 在Visual Studio上,使用OpenCV實(shí)現(xiàn)人臉識(shí)別 (下面統(tǒng)稱為VS實(shí)現(xiàn)
) 中的代碼移植過來。
這里創(chuàng)建了CascadeDetectorAdapter
,實(shí)現(xiàn)了DetectionBasedTracker::IDetector
接口,和VS實(shí)現(xiàn)上代碼是一樣的。
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. 實(shí)現(xiàn)init方法
init
方法也是一樣的,聲明tracker
對(duì)象,并調(diào)用run()
方法,會(huì)啟動(dòng)一個(gè)異步線程,后面的人臉檢測(cè)會(huì)在這個(gè)異步線程進(jìn)行檢測(cè)了。(這個(gè)是保障實(shí)時(shí)人臉檢測(cè)不卡的前提)
//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);
//創(chuàng)建一個(gè)主檢測(cè)適配器
cv::Ptr<CascadeDetectorAdapter> mainDetector = makePtr<CascadeDetectorAdapter>(
makePtr<CascadeClassifier>(stdFileName));
//創(chuàng)建一個(gè)跟蹤檢測(cè)適配器
cv::Ptr<CascadeDetectorAdapter> trackingDetector = makePtr<CascadeDetectorAdapter>(
makePtr<CascadeClassifier>(stdFileName));
//創(chuàng)建跟蹤器
DetectionBasedTracker::Parameters DetectorParams;
//tracker = makePtr<DetectionBasedTracker>(mainDetector, trackingDetector, DetectorParams);
tracker= new DetectionBasedTracker(mainDetector, trackingDetector, DetectorParams);
tracker->run();
}
5. 設(shè)置Surface
在 Android NDK
中,ANativeWindow
是一個(gè)C/C++
接口,它提供了一種在 C
或 C++
代碼中訪問 Android Surface
的方式。通過使用ANativeWindow
接口,開發(fā)者可以在NDK
中直接訪問和操作Android
窗口系統(tǒng),實(shí)現(xiàn)圖形處理和渲染操作。
這里我們將SurfaceView
傳到JNI
中,方便C/C++代碼
后邊將圖像實(shí)時(shí)渲染到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. 處理數(shù)據(jù)并實(shí)現(xiàn)人臉識(shí)別
處理圖像數(shù)據(jù)的部分我們?cè)?code>postData中實(shí)現(xiàn),這部分會(huì)先對(duì)圖像進(jìn)行處理,然后進(jìn)行人臉識(shí)別,并渲染到SurfaceView
上。
extern "C"
JNIEXPORT void JNICALL
Java_com_heiko_myopencvtest_FaceDetectionActivity_postData(JNIEnv *env, jobject instance, jbyteArray data_,
jint w, jint h, jint cameraId) {
//待實(shí)現(xiàn)的代碼
}
6.1 將圖片轉(zhuǎn)化為Mat
參數(shù)中的jbyteArray data_
,是從Android Java
層中獲取到的。
然后轉(zhuǎn)化為jbyte *data
這個(gè)字節(jié)數(shù)組,接著將其轉(zhuǎn)為一個(gè)Mat
矩陣。Mat
是是OpenCV
最基本的數(shù)據(jù)結(jié)構(gòu)。它用于存儲(chǔ)圖像數(shù)據(jù)。
需要注意的是,由于傳入的數(shù)據(jù)是YUV420
,每個(gè)像素占1.5byte
,
所以這個(gè)圖像的寬度需要傳入實(shí)際寬度*1.5
,寬度不變。
這里的CV_8UC1
是單通道的意思,就是說無論是Y
分量還是U
、V
分量,都存儲(chǔ)在同一個(gè)通道里。
jbyte *data = env->GetByteArrayElements(data_, NULL);
Mat src(h + h / 2, w, CV_8UC1, data);
要獲取Mat對(duì)象中每個(gè)通道的數(shù)據(jù),可以使用OpenCV提供的函數(shù)和方法。
對(duì)于一個(gè)BGR圖像(即具有三個(gè)通道的圖像),每個(gè)通道的數(shù)據(jù)可以分別獲取并進(jìn)行處理。
以下是一些示例代碼,演示如何獲取每個(gè)通道的數(shù)據(jù):// 假設(shè)img是包含BGR圖像的Mat對(duì)象 // 獲取B通道數(shù)據(jù) Mat bChannel = img.channel(0); // 獲取G通道數(shù)據(jù) Mat gChannel = img.channel(1); //獲取R通道數(shù)據(jù) Mat rChannel = img.channel(2);
6.2 將YUV轉(zhuǎn)為RGBA
接著,需要將YUV
格式轉(zhuǎn)化為RGBA
格式,方便后續(xù)的操作。
這里的COLOR_YUV2RGBA_NV21
表示原始是NV21
的YUV
格式,將其轉(zhuǎn)為RGBA
格式。
cvtColor(src, src, COLOR_YUV2RGBA_NV21);
6.3 對(duì)圖像做翻轉(zhuǎn)和鏡像
由于手機(jī)攝像頭硬件安裝在手機(jī)里時(shí),和屏幕的方向并不是一致的,所以需要將攝像頭拍攝的畫面進(jìn)行旋轉(zhuǎn)。
- 如果是前置攝像頭 : 需要將畫面逆時(shí)針旋轉(zhuǎn)
90度
,并做左右鏡像操作 - 如果是后置攝像頭 : 需要將畫面順時(shí)針旋轉(zhuǎn)
90度
if (cameraId == 1) {
//前置攝像頭
rotate(src, src, ROTATE_90_COUNTERCLOCKWISE);
//1:左右鏡像
//0:上下鏡像
flip(src, src, 1);
} else {
//順時(shí)針旋轉(zhuǎn)90度
rotate(src, src, ROTATE_90_CLOCKWISE);
}
6.4 轉(zhuǎn)為灰度圖并進(jìn)行直方圖均衡化處理
接著需要對(duì)圖像進(jìn)行灰度和直方圖均衡化處理,以便提高人臉識(shí)別的準(zhǔn)確性和可靠性,這部分和VS實(shí)現(xiàn)上是一樣。
Mat gray;
//轉(zhuǎn)為灰度圖
cvtColor(src, gray, COLOR_RGBA2GRAY);
//直方圖均衡化
equalizeHist(gray, gray);
6.5 進(jìn)行人臉檢測(cè)
接著就可以調(diào)用tracker->process
來建人臉檢測(cè)了。
檢測(cè)完成后,接著調(diào)用tracker->getObjects
將檢測(cè)的人臉位置賦值給faces
。
std::vector<Rect> faces;
tracker->process(gray);
tracker->getObjects(faces);
6.6 將人臉用紅框框出來
接著,將識(shí)別到的人臉,用紅色的矩形框繪制出來,rectangle
方法就是用來繪制一個(gè)矩形框的方法。
for (Rect face : faces) {
rectangle(src, face, Scalar(255, 0, 0));
}
6.7 將圖像渲染到SurfaceView上
6.7.1 設(shè)置窗口緩沖區(qū)
ANativeWindow_setBuffersGeometry
是設(shè)置Android Native
窗口的緩沖區(qū)的大小和像素格式。
if (window) {
ANativeWindow_setBuffersGeometry(window, src.cols, src.rows, WINDOW_FORMAT_RGBA_8888);
//后續(xù)代碼在這里編寫...
}
6.7.2 將圖像數(shù)據(jù)填充到窗口的緩沖區(qū)
這里是個(gè)while循環(huán),會(huì)不斷地將圖像數(shù)據(jù)(RGBA
),填充到窗口的緩沖區(qū),最后調(diào)用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轉(zhuǎn)化為 uint8_t *
uint8_t *dst_data = static_cast<uint8_t *>(window_buffer.bits);
//stride : 一行多少個(gè)數(shù)據(jù) (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. 運(yùn)行項(xiàng)目
我們可以看到效果如下,至此我們就完成在Android
上,使用OpenCV
實(shí)現(xiàn)實(shí)時(shí)的人臉識(shí)別了。
8. 本文源碼下載
Android和Windows下,使用OpenCV實(shí)現(xiàn)人臉識(shí)別 示例 Demo
9. OpenCV系列文章
Visual Studio 2022 cmake配置opencv開發(fā)環(huán)境_opencv visualstudio配置_氦客的博客-CSDN博客
在Visual Studio上,使用OpenCV實(shí)現(xiàn)人臉識(shí)別_氦客的博客-CSDN博客
Android Studio 接入OpenCV,并實(shí)現(xiàn)灰度圖效果_氦客的博客-CSDN博客
Android 使用OpenCV實(shí)現(xiàn)實(shí)時(shí)人臉識(shí)別,并繪制到SurfaceView上_氦客的博客-CSDN博客文章來源:http://www.zghlxwxcb.cn/news/detail-692211.html
?? 如果覺得這篇博文寫的不錯(cuò),對(duì)你有所幫助,幫忙點(diǎn)個(gè)贊??
? 這是對(duì)我持續(xù)輸出高質(zhì)量博文的最好鼓勵(lì)。??文章來源地址http://www.zghlxwxcb.cn/news/detail-692211.html
到了這里,關(guān)于Android 使用OpenCV實(shí)現(xiàn)實(shí)時(shí)人臉識(shí)別,并繪制到SurfaceView上的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!