### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本文是《JavaCV的攝像頭實戰》系列的 ...
歡迎訪問我的GitHub
這裡分類和彙總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos
本篇概覽
-
本文是《JavaCV的攝像頭實戰》系列的第十二篇,咱們來開發一個實用功能:識別性別並顯示在預覽頁面,如下圖:
-
今天的代碼,主要功能如下圖所示:
-
如果您看過《JavaCV的攝像頭實戰》系列的其他文章,就會發現上圖中只有藍色部分是新增內容,其餘的步驟都是固定套路,《JavaCV的攝像頭實戰》系列的每一個應用玩的都是相同套路:別看步驟挺多,其實都是同一個流程
關於性別和年齡檢測
- 使用捲積神經網路推理性別和年齡的更多技術細節,這裡有更詳細的說明:
https://talhassner.github.io/home/publication/2015_CVPR - 本篇會使用已訓練好的Caffe 模型,訓練該模型的數據來自Flickr相冊,通過從 iPhone5(或更高版本)智能手機設備自動上傳組裝而成,並由其作者根據知識共用 (CC) 許可向公眾發佈,共有26580張照片,涉及2284人,這些人的年齡一共被標識成八組:(0-2、4-6、8-13、15-20、25-32、38-43、48-53、60 -)
- 關於數據源的更多詳細,請參考:https://talhassner.github.io/home/projects/Adience/Adience-data.html
- 論文地址:https://talhassner.github.io/home/projects/cnn_agegender/CVPR2015_CNN_AgeGenderEstimation.pdf
源碼下載
- 《JavaCV人臉識別三部曲》的完整源碼可在GitHub下載到,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos):
名稱 | 鏈接 | 備註 |
---|---|---|
項目主頁 | https://github.com/zq2599/blog_demos | 該項目在GitHub上的主頁 |
git倉庫地址(https) | https://github.com/zq2599/blog_demos.git | 該項目源碼的倉庫地址,https協議 |
git倉庫地址(ssh) | [email protected]:zq2599/blog_demos.git | 該項目源碼的倉庫地址,ssh協議 |
- 這個git項目中有多個文件夾,本篇的源碼在javacv-tutorials文件夾下,如下圖紅框所示:
- javacv-tutorials裡面有多個子工程,《JavaCV的攝像頭實戰》系列的代碼在simple-grab-push工程下:
準備:文件下載
- 本次實戰需要三個文件:
- 人臉檢測的模型文件:https://raw.github.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml
- 性別識別的配置文件:https://raw.githubusercontent.com/GilLevi/AgeGenderDeepLearning/master/gender_net_definitions/deploy.prototxt
- 性別識別的模型文件:https://raw.githubusercontent.com/GilLevi/AgeGenderDeepLearning/master/models/gender_net.caffemodel
- 我已將上述文件打包上傳到CSDN,您也可以在CSDN下載(無需積分):
https://download.csdn.net/download/boling_cavalry/70730586
準備:代碼介面簡介
- 編碼前,先把涉及到的所有java文件說明一下:
- AbstractCameraApplication.java:主程式的抽象類,這裡面定義了打開攝像頭、抓取每一幀、處理每一幀的基本框架,避免每個應用都把這些事情重覆做一遍
- PreviewCameraWithGenderAge.java:主程式,是AbstractCameraApplication的實現類,本次實戰的核心功能人臉檢測和性別檢測,都委托給它的成員變數detectService去完成
- DetectService.java:檢測服務的介面,裡面定義了幾個重要的api,例如初始化、處理每一幀、釋放資源等
- GenderDetectService.java:是DetectService介面的實現類,本次實戰的核心功能都寫在這個類中
- 介紹完畢,可以開始編碼了,先從最簡單的主程式開始
編碼:主程式
- 《JavaCV的攝像頭實戰之一:基礎》創建的simple-grab-push工程中已經準備好了父類AbstractCameraApplication,所以本篇繼續使用該工程,創建子類實現那些抽象方法即可
- 編碼前先回顧父類的基礎結構,如下圖,粗體是父類定義的各個方法,紅色塊都是需要子類來實現抽象方法,所以接下來,咱們以本地視窗預覽為目標實現這三個紅色方法即可:
- 新建文件PreviewCameraWithGenderAge.java,這是AbstractCameraApplication的子類,其代碼很簡單,接下來按上圖順序依次說明
- 先定義CanvasFrame類型的成員變數previewCanvas,這是展示視頻幀的本地視窗:
protected CanvasFrame previewCanvas
- 把前面創建的DetectService作為成員變數,後面檢測的時候會用到:
/**
* 檢測工具介面
*/
private DetectService detectService;
- PreviewCameraWithGenderAge的構造方法,接受DetectService的實例:
/**
* 不同的檢測工具,可以通過構造方法傳入
* @param detectService
*/
public PreviewCameraWithGenderAge(DetectService detectService) {
this.detectService = detectService;
}
- 然後是初始化操作,可見是previewCanvas的實例化和參數設置,還有檢測、識別的初始化操作:
@Override
protected void initOutput() throws Exception {
previewCanvas = new CanvasFrame("攝像頭預覽", CanvasFrame.getDefaultGamma() / grabber.getGamma());
previewCanvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
previewCanvas.setAlwaysOnTop(true);
// 檢測服務的初始化操作
detectService.init();
}
- 接下來是output方法,定義了拿到每一幀視頻數據後做什麼事情,這裡調用了detectService.convert檢測人臉並識別性別,然後在本地視窗顯示:
@Override
protected void output(Frame frame) {
// 原始幀先交給檢測服務處理,這個處理包括物體檢測,再將檢測結果標註在原始圖片上,
// 然後轉換為幀返回
Frame detectedFrame = detectService.convert(frame);
// 預覽視窗上顯示的幀是標註了檢測結果的幀
previewCanvas.showImage(detectedFrame);
}
- 最後是處理視頻的迴圈結束後,程式退出前要做的事情,先關閉本地視窗,再釋放檢測服務的資源:
@Override
protected void releaseOutputResource() {
if (null!= previewCanvas) {
previewCanvas.dispose();
}
// 檢測工具也要釋放資源
detectService.releaseOutputResource();
}
- 由於檢測有些耗時,所以兩幀之間的間隔時間要低於普通預覽:
@Override
protected int getInterval() {
return super.getInterval()/8;
}
- 至此,功能已開發完成,再寫上main方法,代碼如下,請註意AgeDetectService構造方法的三個入參,分別是前面下載的三個文件在本機的位置:
public static void main(String[] args) {
String base = "E:\\temp\\202112\\25\\opencv\\";
DetectService detectService = new GenderDetectService(
base + "haarcascade_frontalface_alt.xml",
base + "gender\\deploy.prototxt",
base + "gender\\gender_net.caffemodel");
new PreviewCameraWithGenderAge(detectService).action(1000);
}
- 主程式已經寫完,接下來是核心功能
編碼:服務介面回顧
- 本篇的核心功能是檢測性別,相關代碼被封裝在DetectService介面的實現類GenderDetectService中,這個DetectService介面是咱們的老朋友了,之前識別相關的實戰都有它的身影,再來回顧一下,如下,定義了初始化、處理原始幀、釋放資源等關鍵行為的介面:
package com.bolingcavalry.grabpush.extend;
public interface DetectService {
/**
* 根據傳入的MAT構造相同尺寸的MAT,存放灰度圖片用於以後的檢測
* @param src 原始圖片的MAT對象
* @return 相同尺寸的灰度圖片的MAT對象
*/
static Mat buildGrayImage(Mat src) {
return new Mat(src.rows(), src.cols(), CV_8UC1);
}
/**
* 初始化操作,例如模型下載
* @throws Exception
*/
void init() throws Exception;
/**
* 得到原始幀,做識別,添加框選
* @param frame
* @return
*/
Frame convert(Frame frame);
/**
* 釋放資源
*/
void releaseOutputResource();
}
- 接下來,就是DetectService介面的實現類,也就是今天實戰的核心:GenderDetectService.java
編碼:檢測服務實現
- 今天的核心功能都集中在GenderDetectService.java中,直接貼出全部源碼吧,有幾處要註意的地方稍後會提到:
package com.bolingcavalry.grabpush.extend;
import com.bolingcavalry.grabpush.Constants;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.indexer.Indexer;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_dnn.Net;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
import static org.bytedeco.opencv.global.opencv_core.NORM_MINMAX;
import static org.bytedeco.opencv.global.opencv_core.normalize;
import static org.bytedeco.opencv.global.opencv_dnn.blobFromImage;
import static org.bytedeco.opencv.global.opencv_dnn.readNetFromCaffe;
import static org.bytedeco.opencv.global.opencv_imgproc.*;
/**
* @author willzhao
* @version 1.0
* @description 音頻相關的服務
* @date 2021/12/3 8:09
*/
@Slf4j
public class GenderDetectService implements DetectService {
/**
* 每一幀原始圖片的對象
*/
private Mat grabbedImage = null;
/**
* 原始圖片對應的灰度圖片對象
*/
private Mat grayImage = null;
/**
* 分類器
*/
private CascadeClassifier classifier;
/**
* 轉換器
*/
private OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
/**
* 人臉檢測模型文件的下載地址
*/
private String classifierModelFilePath;
/**
* 性別識別proto文件的下載地址
*/
private String genderProtoFilePath;
/**
* 性別識別模型文件的下載地址
*/
private String genderModelFilePath;
/**
* 推理性別的神經網路對象
*/
private Net cnnNet;
/**
* 構造方法,在此指定proto和模型文件的下載地址
* @param classifierModelFilePath
* @param cnnProtoFilePath
* @param cnnModelFilePath
*/
public GenderDetectService(String classifierModelFilePath,
String cnnProtoFilePath,
String cnnModelFilePath) {
this.classifierModelFilePath = classifierModelFilePath;
this.genderProtoFilePath = cnnProtoFilePath;
this.genderModelFilePath = cnnModelFilePath;
}
/**
* 初始化操作,主要是創建推理用的神經網路
* @throws Exception
*/
@Override
public void init() throws Exception {
// 根據模型文件實例化分類器
classifier = new CascadeClassifier(classifierModelFilePath);
// 實例化推理性別的神經網路
cnnNet = readNetFromCaffe(genderProtoFilePath, genderModelFilePath);
}
@Override
public Frame convert(Frame frame) {
// 由幀轉為Mat
grabbedImage = converter.convert(frame);
// 灰度Mat,用於檢測
if (null==grayImage) {
grayImage = DetectService.buildGrayImage(grabbedImage);
}
// 當前圖片轉為灰度圖片
cvtColor(grabbedImage, grayImage, CV_BGR2GRAY);
// 存放檢測結果的容器
RectVector objects = new RectVector();
// 開始檢測
classifier.detectMultiScale(grayImage, objects);
// 檢測結果總數
long total = objects.size();
// 如果沒有檢測到結果,就用原始幀返回
if (total<1) {
return frame;
}
int pos_x;
int pos_y;
Mat faceMat;
//推理時的入參
Mat inputBlob;
// 推理結果
Mat prob;
// 如果有檢測結果,就根據結果的數據構造矩形框,畫在原圖上
for (long i = 0; i < total; i++) {
Rect r = objects.get(i);
// 人臉對應的Mat實例(註意:要用彩圖,不能用灰度圖!!!)
faceMat = new Mat(grabbedImage, r);
// 縮放到神經網路所需的尺寸
resize(faceMat, faceMat, new Size(Constants.CNN_PREIDICT_IMG_WIDTH, Constants.CNN_PREIDICT_IMG_HEIGHT));
// 歸一化
normalize(faceMat, faceMat, 0, Math.pow(2, frame.imageDepth), NORM_MINMAX, -1, null);
// 轉為推理時所需的的blob類型
inputBlob = blobFromImage(faceMat);
// 為神經網路設置入參
cnnNet.setInput(inputBlob, "data", 1.0, null); //set the network input
// 推理
prob = cnnNet.forward("prob");
// 根據推理結果得到在人臉上標註的內容
String lable = getDescriptionFromPredictResult(prob);
// 人臉標註的橫坐標
pos_x = Math.max(r.tl().x()-10, 0);
// 人臉標註的縱坐標
pos_y = Math.max(r.tl().y()-10, 0);
// 給人臉做標註,標註性別
putText(grabbedImage, lable, new Point(pos_x, pos_y), FONT_HERSHEY_PLAIN, 1.5, new Scalar(0,255,0,2.0));
// 給人臉加邊框時的邊框位置
int x = r.x(), y = r.y(), w = r.width(), h = r.height();
// 給人臉加邊框
rectangle(grabbedImage, new Point(x, y), new Point(x + w, y + h), Scalar.RED, 1, CV_AA, 0);
}
// 釋放檢測結果資源
objects.close();
// 將標註過的圖片轉為幀,返回
return converter.convert(grabbedImage);
}
/**
* 程式結束前,釋放人臉識別的資源
*/
@Override
public void releaseOutputResource() {
if (null!=grabbedImage) {
grabbedImage.release();
}
if (null!=grayImage) {
grayImage.release();
}
if (null!=classifier) {
classifier.close();
}
if (null!= cnnNet) {
cnnNet.close();
}
}
/**
* 根據推理結果得到在頭像上要標註的內容
* @param prob
* @return
*/
protected String getDescriptionFromPredictResult(Mat prob) {
Indexer indexer = prob.createIndexer();
// 比較兩種性別的概率,概率大的作為當前頭像的性別
return indexer.getDouble(0,0) > indexer.getDouble(0,1)
? "male"
: "female";
}
}
- 上述代碼,有以下幾處需要註意的:
- 構造方法的三個入參:classifierModelFilePath、cnnProtoFilePath、cnnModelFilePath分別是人臉檢測模型、性別檢測配置、性別檢測模型三個文件的本地存放地址
- 檢測性別靠的是捲積神經網路的推理,初始化的時候通過readNetFromCaffe方法新建神經網路對象
- convert方法被調用時,會收到攝像頭捕捉的每一幀,在這裡面先檢測出每個人臉,再拿每個人臉去神經網路進行推理
- 用神經網路的推理結果生成人臉的標註內容,這段邏輯被放入getDescriptionFromPredictResult,下一篇《年齡檢測》的實戰同樣是使用神經網路推理頭像的年齡,咱們只要寫一個GenderDetectService,並重寫getDescriptionFromPredictResult方法,裡面的邏輯改成根據推理結果得到年齡,即可輕鬆完成任務,其他類都可以維持不變
- 至此,編碼完成,接下來開始驗證
驗證
- 確保攝像頭工作正常,運行PreviewCameraWithGenderAge類的main方法
- 請群眾演員登場,讓他站在攝像頭前,如下圖,性別識別成功,且實時展示:
- 至此,本地視窗預覽集成人臉檢測和性別檢測的功能就完成了,得益於JavaCV的強大,整個過程是如此的輕鬆愉快,接下來請繼續關註欣宸原創,《JavaCV的攝像頭實戰》系列還會呈現更多豐富的應用;
- 得益於本篇所做的擴展準備,下一篇《年齡檢測》會更加簡單,一起來期待下一段輕鬆愉快的旅程吧;