Android?利用OpenCV制作人臉檢測(cè)APP
前言
本篇文章手把手教大家使用OpenCV來(lái)實(shí)現(xiàn)一個(gè)能在安卓手機(jī)上運(yùn)行的人臉檢測(cè)APP。其實(shí)不僅僅是能檢測(cè)人臉,還能檢測(cè)鼻子,嘴巴,眼睛和耳朵?;瞬簧倬?xiě)這篇文章,希望點(diǎn)贊收藏關(guān)注。
無(wú)圖無(wú)真相,先把APP運(yùn)行的結(jié)果給大家看看。

如上圖所示,APP運(yùn)行后,點(diǎn)擊“選擇圖片”,從手機(jī)中選擇一張圖片,然后點(diǎn)擊“處理”,APP會(huì)將人臉用矩形給框起來(lái),同時(shí)把鼻子也給檢測(cè)出來(lái)了。由于目的是給大家做演示,所以APP設(shè)計(jì)得很簡(jiǎn)單,而且也只實(shí)現(xiàn)了檢測(cè)人臉和鼻子,沒(méi)有實(shí)現(xiàn)對(duì)其他五官的檢測(cè)。而且這個(gè)APP也只能檢測(cè)很簡(jiǎn)單的圖片,如果圖片中背景太復(fù)雜就無(wú)法檢測(cè)出人臉。
下面我將一步一步教大家如何實(shí)現(xiàn)上面的APP!
第一步:下載并安裝Android studio
為了保證大家能下載到和我相同版本的Android Studio,我把安裝包上傳了到微云。下載地址
下載后,一路點(diǎn)擊下一步就安裝好了。當(dāng)然,安裝過(guò)程中要聯(lián)網(wǎng),所以可能會(huì)中途失敗,如果失敗了,重試幾次,如果還是有問(wèn)題,那么可能要開(kāi)啟VPN。
第二步:下載SDK tools
打開(kāi)Android studio后,點(diǎn)擊“File”->“Settings”

點(diǎn)擊“Appearance & Behavior”->“System Settings”->“Android SDK”->“SDK Tools”。

然后選中“NDK”和“CMake”,點(diǎn)擊“OK”。下載這兩個(gè)工具可能要花一點(diǎn)時(shí)間,如果失敗了請(qǐng)重試或開(kāi)啟VPN。
第三步:新建一個(gè)Android APP項(xiàng)目
點(diǎn)擊“File”->“New”->“New Project”

選中“Empty Activity”,點(diǎn)擊“Next”

“Language”選擇“Java”,Minimum SDK選擇“API 21”。點(diǎn)擊“Finish”

第四步:下載Opencv
下載后解壓。
第五步:導(dǎo)入OpenCV
將opencv-4.5.4-android-sdk\OpenCV-android-sdk下面的sdk復(fù)制到你在第三步創(chuàng)建的Android項(xiàng)目下面。就是第三步圖中的D:\programming\MyApplication下面。然后將sdk文件夾改名為openCVsdk。

選擇“Project”->“settings.gradle”。在文件中添加include ‘:openCVsdk'

選擇“Project”->“openCVsdk”->“build.gradle”。
將apply plugin: 'kotlin-android'改為//apply plugin: ‘kotlin-android'
將compileSdkVersion和minSdkVersion,targetSdkVersion改為31,21,31。

點(diǎn)擊“File”->“Project Structure”

點(diǎn)擊“Dependencies”->“app”->“+”->“Module Dependency”

選中“openCVsdk”,點(diǎn)擊“OK”,以及母窗口的“OK”

在Android項(xiàng)目文件夾的app\src里面創(chuàng)建一個(gè)新文件夾jniLibs,然后把openCV文件夾的opencv-4.5.4-android-sdk\OpenCV-android-sdk\sdk\native\staticlibs里面的東西都copy到j(luò)niLibs文件夾中。

下載分類器。解壓后,將下圖中的文件都復(fù)制到項(xiàng)目文件夾的app\src\main\res\raw文件夾下。

第六步:添加代碼
雙擊“Project”->“app”-》“main”-》“res”下面的“activity_main.xml”。然后點(diǎn)擊右上角的“code”。

然后將里面的代碼都換成下面的代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
>
<Button
android:id="@+id/select_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="選擇圖片" />
<Button
android:id="@+id/process_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="處理" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
雙擊“Project”->“app”-》“main”-》“java”-》“com.example…”下面的“MainActivity”。然后把里面的代碼都換成下面的代碼(保留原文件里的第一行代碼)
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.imgproc.Imgproc;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.objdetect.CascadeClassifier;
import android.content.Context;
public class MainActivity extends AppCompatActivity {
private double max_size = 1024;
private int PICK_IMAGE_REQUEST = 1;
private ImageView myImageView;
private Bitmap selectbp;
private static final String TAG = "OCVSample::Activity";
private static final Scalar FACE_RECT_COLOR = new Scalar(0, 255, 0, 255);
public static final int JAVA_DETECTOR = 0;
public static final int NATIVE_DETECTOR = 1;
private Mat mGray;
private File mCascadeFile;
private CascadeClassifier mJavaDetector,mNoseDetector;
private int mDetectorType = JAVA_DETECTOR;
private float mRelativeFaceSize = 0.2f;
private int mAbsoluteFaceSize = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
staticLoadCVLibraries();
myImageView = (ImageView)findViewById(R.id.imageView);
myImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
Button selectImageBtn = (Button)findViewById(R.id.select_btn);
selectImageBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// makeText(MainActivity.this.getApplicationContext(), "start to browser image", Toast.LENGTH_SHORT).show();
selectImage();
}
private void selectImage() {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent,"選擇圖像..."), PICK_IMAGE_REQUEST);
}
});
Button processBtn = (Button)findViewById(R.id.process_btn);
processBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// makeText(MainActivity.this.getApplicationContext(), "hello, image process", Toast.LENGTH_SHORT).show();
convertGray();
}
});
}
private void staticLoadCVLibraries() {
boolean load = OpenCVLoader.initDebug();
if(load) {
Log.i("CV", "Open CV Libraries loaded...");
}
}
private void convertGray() {
Mat src = new Mat();
Mat temp = new Mat();
Mat dst = new Mat();
Utils.bitmapToMat(selectbp, src);
Imgproc.cvtColor(src, temp, Imgproc.COLOR_BGRA2BGR);
Log.i("CV", "image type:" + (temp.type() == CvType.CV_8UC3));
Imgproc.cvtColor(temp, dst, Imgproc.COLOR_BGR2GRAY);
Utils.matToBitmap(dst, selectbp);
myImageView.setImageBitmap(selectbp);
mGray = dst;
mJavaDetector = loadDetector(R.raw.lbpcascade_frontalface,"lbpcascade_frontalface.xml");
mNoseDetector = loadDetector(R.raw.haarcascade_mcs_nose,"haarcascade_mcs_nose.xml");
if (mAbsoluteFaceSize == 0) {
int height = mGray.rows();
if (Math.round(height * mRelativeFaceSize) > 0) {
mAbsoluteFaceSize = Math.round(height * mRelativeFaceSize);
}
}
MatOfRect faces = new MatOfRect();
if (mJavaDetector != null) {
mJavaDetector.detectMultiScale(mGray, faces, 1.1, 2, 2, // TODO: objdetect.CV_HAAR_SCALE_IMAGE
new Size(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());
}
Rect[] facesArray = faces.toArray();
for (int i = 0; i < facesArray.length; i++) {
Log.e(TAG, "start to detect nose!");
Mat faceROI = mGray.submat(facesArray[i]);
MatOfRect noses = new MatOfRect();
mNoseDetector.detectMultiScale(faceROI, noses, 1.1, 2, 2,
new Size(30, 30));
Rect[] nosesArray = noses.toArray();
Imgproc.rectangle(src,
new Point(facesArray[i].tl().x + nosesArray[0].tl().x, facesArray[i].tl().y + nosesArray[0].tl().y) ,
new Point(facesArray[i].tl().x + nosesArray[0].br().x, facesArray[i].tl().y + nosesArray[0].br().y) ,
FACE_RECT_COLOR, 3);
Imgproc.rectangle(src, facesArray[i].tl(), facesArray[i].br(), FACE_RECT_COLOR, 3);
}
Utils.matToBitmap(src, selectbp);
myImageView.setImageBitmap(selectbp);
}
private CascadeClassifier loadDetector(int rawID,String fileName) {
CascadeClassifier classifier = null;
try {
// load cascade file from application resources
InputStream is = getResources().openRawResource(rawID);
File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
mCascadeFile = new File(cascadeDir, fileName);
FileOutputStream os = new FileOutputStream(mCascadeFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
is.close();
os.close();
Log.e(TAG, "start to load file: " + mCascadeFile.getAbsolutePath());
classifier = new CascadeClassifier(mCascadeFile.getAbsolutePath());
if (classifier.empty()) {
Log.e(TAG, "Failed to load cascade classifier");
classifier = null;
} else
Log.i(TAG, "Loaded cascade classifier from " + mCascadeFile.getAbsolutePath());
cascadeDir.delete();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
}
return classifier;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {
Uri uri = data.getData();
try {
Log.d("image-tag", "start to decode selected image now...");
InputStream input = getContentResolver().openInputStream(uri);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(input, null, options);
int raw_width = options.outWidth;
int raw_height = options.outHeight;
int max = Math.max(raw_width, raw_height);
int newWidth = raw_width;
int newHeight = raw_height;
int inSampleSize = 1;
if (max > max_size) {
newWidth = raw_width / 2;
newHeight = raw_height / 2;
while ((newWidth / inSampleSize) > max_size || (newHeight / inSampleSize) > max_size) {
inSampleSize *= 2;
}
}
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
selectbp = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri), null, options);
myImageView.setImageBitmap(selectbp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
第七步:連接手機(jī)運(yùn)行程序
首先要打開(kāi)安卓手機(jī)的開(kāi)發(fā)者模式,每個(gè)手機(jī)品牌的打開(kāi)方式不一樣,你自行百度一下就知道了。例如在百度中搜索“小米手機(jī)如何開(kāi)啟開(kāi)發(fā)者模式”。
然后用數(shù)據(jù)線將手機(jī)和電腦連接起來(lái)。成功后,Android studio里面會(huì)顯示你的手機(jī)型號(hào)。如下圖中顯示的是“Xiaomi MI 8 UD”,本例中的開(kāi)發(fā)手機(jī)是小米手機(jī)。

點(diǎn)擊上圖中的“Run”-》“Run ‘a(chǎn)pp'”就可以將APP運(yùn)行到手機(jī)上面了,注意手機(jī)屏幕要處于打開(kāi)狀態(tài)。你自拍的圖片可以檢測(cè)不成功,可以下載我的測(cè)試圖片試試。

到此這篇關(guān)于Android 利用OpenCV制作人臉檢測(cè)APP的文章就介紹到這了,更多相關(guān)Android OpenCV 人臉檢測(cè)APP內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中GPS坐標(biāo)轉(zhuǎn)換為高德地圖坐標(biāo)詳解
最近因?yàn)楣拘枨?,在做GPS定位,并且將獲得的坐標(biāo)顯示在高德地圖上,但是實(shí)際效果跟我們期望的是有偏差的。通過(guò)查閱資料,才知道有地球坐標(biāo)、火星坐標(biāo)之說(shuō)。下面這篇文章就詳細(xì)介紹了Android中GPS坐標(biāo)轉(zhuǎn)換為高德地圖坐標(biāo)的方法,需要的朋友可以參考下。2017-01-01
Android從0到完整項(xiàng)目(1)使用Android studio 創(chuàng)建項(xiàng)目詳解
本篇文章主要介紹了Android從0到完整項(xiàng)目(1)使用Android studio 創(chuàng)建項(xiàng)目詳解,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07
android高仿小米時(shí)鐘(使用Camera和Matrix實(shí)現(xiàn)3D效果)
這篇文章主要介紹了android高仿小米時(shí)鐘(使用Camera和Matrix實(shí)現(xiàn)3D效果),非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-01-01
Android 字符串中某個(gè)字段可點(diǎn)擊和設(shè)置顏色的方法
在android開(kāi)發(fā)中,我們時(shí)常會(huì)遇到對(duì)字符串中某些固定的字段實(shí)現(xiàn)可點(diǎn)擊和顏色的設(shè)置,現(xiàn)粘貼處我在開(kāi)發(fā)中如何設(shè)置這些屬性的2017-07-07
Android View的事件分發(fā)機(jī)制深入分析講解
事件分發(fā)從手指觸摸屏幕開(kāi)始,即產(chǎn)生了觸摸信息,被底層系統(tǒng)捕獲后會(huì)傳遞給Android的輸入系統(tǒng)服務(wù)IMS,通過(guò)Binder把消息發(fā)送到activity,activity會(huì)通過(guò)phoneWindow、DecorView最終發(fā)送給ViewGroup。這里就直接分析ViewGroup的事件分發(fā)2023-01-01
Android 獲取IP地址的實(shí)現(xiàn)方法
這篇文章主要介紹了Android 獲取IP地址的實(shí)現(xiàn)方法的相關(guān)資料,這里提供了具體實(shí)現(xiàn)的方法及代碼,使用WIFI 和GPRS的思路,需要的朋友可以參考下2016-11-11
android原生實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳功能
這篇文章主要為大家詳細(xì)介紹了android原生實(shí)現(xiàn)多線程斷點(diǎn)續(xù)傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07

