主頁 > 後端開發 > JavaCV的攝像頭實戰之八:人臉檢測

JavaCV的攝像頭實戰之八:人臉檢測

2023-06-19 07:39:15 後端開發

歡迎訪問我的GitHub

這里分類和匯總了欣宸的全部原創(含配套原始碼):https://github.com/zq2599/blog_demos

本篇概覽

  • 本文是《JavaCV的攝像頭實戰》的第八篇,前面的操作夯實了的幀和流處理的基本功,接下來開始實作一些常見的CV能力,就從本篇的人檢測別開始吧
  • OpenCV中常用的人臉檢測是基于Haar特征的級聯分類器,本篇借助JavaCV來使用該分類器實作人臉檢測

簡單的設計

  • 編碼之前先把要做的事情梳理一下:
  1. 檢測功能可能用在多個場景:視窗預覽、推流、存檔案都可能用到,所以檢測功能的代碼最好獨立出來,不要和預覽、推流這些代碼寫在一起,如下圖,檢測的介面DetectService會作為每個應用的成員變數存在:
    在這里插入圖片描述
  2. 檢測服務不僅是人臉檢測,今后還有人體檢測、物體檢測等等,所以設計一個檢測服務介面DetectService,人臉檢測、人體檢測、物體檢測這些類都是這個介面的實作,如下圖所示,對于預覽、推流、存檔案這個應用的代碼,直接使用介面的API即可,具體檢測的實作類可以在初始化的時候確定
    在這里插入圖片描述
  3. 聰明的您應該會覺得欣宸的水平過于原始:上面的設計不就是Spring的依賴注入嗎?為啥不用呢?其實這個系列的重點是JavaCV,所以保持代碼簡單吧,不引入Spring框架了
  • 總的來說,今天的要寫的代碼如下圖所示,綠色塊的AbstractCameraApplication類已在《JavaCV的攝像頭實戰之一:基礎》一文中完成,其余三個全部在本篇撰寫,包括兩個java類、一個介面:
    在這里插入圖片描述

  • 分析得差不多了,開始編碼,先寫介面DetectService

檢測服務介面DetectService

  • 新增介面DetectService.java,里面有三個方法定義:
	/**
     * 初始化操作,例如模型下載
     * @throws Exception
     */
    void init() throws Exception;

    /**
     * 得到原始幀,做檢測,添加框選
     * @param frame
     * @return
     */
    Frame convert(Frame frame);

    /**
     * 釋放資源
     */
    void releaseOutputResource();
  • 另外還有兩個靜態方法,也放在DetectService.java中,第一個是buildGrayImage,該方法會根據入參Mat的尺寸新建一個Mat物件,新建的物件用于保存灰度圖片,因為檢測時用的是灰度圖片而不是原圖:
    /**
     * 根據傳入的MAT構造相同尺寸的MAT,存放灰度圖片用于以后的檢測
     * @param src 原始圖片的MAT物件
     * @return 相同尺寸的灰度圖片的MAT物件
     */
    static Mat buildGrayImage(Mat src) {
        return new Mat(src.rows(), src.cols(), CV_8UC1);
    }
  • 第二個方法是第一個是detect,該方法非常重要:將原圖轉為灰度圖片,再用指定的分類器檢測,將檢測結果在原圖上標注出來,標注后的Mat轉為Frame物件回傳:
    /**
     * 檢測圖片,將檢測結果用矩形標注在原始圖片上
     * @param classifier 分類器
     * @param converter Frame和mat的轉換器
     * @param rawFrame 原始視頻幀
     * @param grabbedImage 原始視頻幀對應的mat
     * @param grayImage 存放灰度圖片的mat
     * @return 標注了檢測結果的視頻幀
     */
    static Frame detect(CascadeClassifier classifier,
                        OpenCVFrameConverter.ToMat converter,
                        Frame rawFrame,
                        Mat grabbedImage,
                        Mat grayImage) {

        // 當前圖片轉為灰度圖片
        cvtColor(grabbedImage, grayImage, CV_BGR2GRAY);

        // 存放檢測結果的容器
        RectVector objects = new RectVector();

        // 開始檢測
        classifier.detectMultiScale(grayImage, objects);

        // 檢測結果總數
        long total = objects.size();

        // 如果沒有檢測到結果,就用原始幀回傳
        if (total<1) {
            return rawFrame;
        }

        // 如果有檢測結果,就根據結果的資料構造矩形框,畫在原圖上
        for (long i = 0; i < total; i++) {
            Rect r = objects.get(i);
            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);
    }
  • 以上就是介面DetectService.java的全部:三個方法定義,兩個靜態方法,接下來就是介面的實作類了

人臉檢測功能的實作類

  • 前面的DetectService介面僅定義了三個方法:初始化(init)、檢測(convert)、資源釋放(releaseOutputResource),現在開發這個介面的實作類HaarCascadeDetectService.java,實作真正的人臉檢測功能
  • 完整代碼如下,核心是init方法中實體化的分類器classifier,以及負責處理每一幀的convert方法,這里面會中呼叫剛才寫的靜態方法DetectService.detect,把原始幀轉換成標注了檢測結果的幀:
package com.bolingcavalry.grabpush.extend;

import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
import java.io.File;
import java.net.URL;

/**
 * @author willzhao
 * @version 1.0
 * @description Haar檢測的實作類
 * @date 2021/12/3 8:09
 */
@Slf4j
public class HaarCascadeDetectService implements DetectService {

    /**
     * 每一幀原始圖片的物件
     */
    private Mat grabbedImage = null;

    /**
     * 原始圖片對應的灰度圖片物件
     */
    private Mat grayImage = null;

    /**
     * 分類器
     */
    private CascadeClassifier classifier;

    /**
     * 轉換器
     */
    private OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();

    /**
     * 模型檔案的下載地址
     */
    private String modelFileUrl;

    /**
     * 構造方法,在此指定模型檔案的下載地址
     * @param modelFileUrl
     */
    public HaarCascadeDetectService(String modelFileUrl) {
        this.modelFileUrl = modelFileUrl;
    }
    
    /**
     * 音頻采樣物件的初始化
     * @throws Exception
     */
    @Override
    public void init() throws Exception {
        // 下載模型檔案
        URL url = new URL(modelFileUrl);
        File file = Loader.cacheResource(url);
        
        // 模型檔案下載后的完整地址
        String classifierName = file.getAbsolutePath();

        // 根據模型檔案實體化分類器
        classifier = new CascadeClassifier(classifierName);

        if (classifier == null) {
            log.error("Error loading classifier file [{}]", classifierName);
            System.exit(1);
        }
    }

    @Override
    public Frame convert(Frame frame) {
        // 由幀轉為Mat
        grabbedImage = converter.convert(frame);

        // 灰度Mat,用于檢測
        if (null==grayImage) {
            grayImage = DetectService.buildGrayImage(grabbedImage);
        }

        // 進行人臉檢測,根據結果做處理得到預覽視窗顯示的幀
        return DetectService.detect(classifier, converter, frame, grabbedImage, grayImage);
    }

    /**
     * 程式結束前,釋放人臉檢測的資源
     */
    @Override
    public void releaseOutputResource() {
        if (null!=grabbedImage) {
            grabbedImage.release();
        }

        if (null!=grayImage) {
            grayImage.release();
        }

        if (null==classifier) {
            classifier.close();
        }
    }
}

主程式PreviewCameraWithDetect

  • 在《JavaCV的攝像頭實戰之一:基礎》一文創建的simple-grab-push工程中已經準備好了父類AbstractCameraApplication,所以本篇繼續使用該工程,創建子類實作那些抽象方法即可
  • 編碼前先回顧父類的基礎結構,如下圖,粗體是父類定義的各個方法,紅色塊都是需要子類來實作抽象方法,所以接下來,咱們以本地視窗預覽為目標實作這三個紅色方法即可:
    在這里插入圖片描述
  • 新建檔案PreviewCameraWithDetect.java,這是AbstractCameraApplication的子類,其代碼很簡單,接下來按上圖順序依次說明
  • 先定義CanvasFrame型別的成員變數previewCanvas,這是展示視頻幀的本地視窗:
protected CanvasFrame previewCanvas
  • 還要定義DetectService型別的成員變數,用于稍后的檢測操作,并在構造方法中對改成員變數賦值:
    /**
     * 檢測工具介面
     */
    private DetectService detectService;
    
    /**
     * 不同的檢測工具,可以通過構造方法傳入
     * @param detectService
     */
    public PreviewCameraWithDetect(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方法,定義了拿到每一幀視頻資料后做什么事情,這里會交給檢測服務去處理,將處理結果在本地視窗顯示:
    @Override
    protected void output(Frame frame) {
        // 原始幀先交給檢測服務處理,這個處理包括物體檢測,再將檢測結果標注在原始圖片上,
        // 然后轉換為幀回傳
        Frame detectedFrame = detectService.convert(frame);
        // 預覽視窗上顯示的幀是標注了檢測結果的幀
        previewCanvas.showImage(detectedFrame);
    }
  • 由于檢測服務也會耗時,所以這里調整每幀輸出后的等待時間,以免預覽時卡頓,請依照自己電腦CPU性能調整,我這里改為原有時長的八分之一:
    @Override
    protected int getInterval() {
        return super.getInterval()/8;
    }
  • 最后是處理視頻的回圈結束后,程式退出前要做的事情,即關閉本地視窗,另外還要呼叫檢測服務的releaseOutputResource來釋放其相關資源:
    @Override
    protected void releaseOutputResource() {
        if (null!= previewCanvas) {
            previewCanvas.dispose();
        }

        // 檢測工具也要釋放資源
        detectService.releaseOutputResource();
    }
  • 至此,用本地視窗預覽攝像頭的功能已開發完成,再寫上main方法,注意引數100表示預覽持續時間是100秒,modelFileUrl是模型檔案在GitHub上的地址(注釋掉的那個是人體的,您也可以試試):
    public static void main(String[] args) {
        String modelPath = "https://raw.github.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml";
//        String modelPath = "https://raw.github.com/opencv/opencv/master/data/haarcascades/haarcascade_upperbody.xml";
        new PreviewCameraWithDetect(new HaarCascadeDetectService(modelPath)).action(1000);
    }
  • 運行main方法即可啟動程式,如下圖,預覽視窗中如果有人像,人臉上就會出現紅框(為了不侵犯群眾演員的肖像權,手動對面部做了馬賽克處理):
    在這里插入圖片描述
  • 至此,本地視窗預覽集成人臉檢測的功能就完成了,得益于JavaCV的強大,整個程序是如此的輕松愉快,接下來請繼續關注欣宸原創,《JavaCV的攝像頭實戰》系列還會呈現更多豐富的應用;
  • 本文涉及的所有代碼都能在接下來的介紹的GitHub倉庫中找到

原始碼下載

  • 《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://www.uj5u.com/houduan/555506.html

標籤:其他

上一篇:使用 Sa-Token 實作 [記住我] 模式登錄、七天免登錄

下一篇:返回列表

標籤雲
其他(161241) Python(38240) JavaScript(25505) Java(18246) C(15237) 區塊鏈(8271) C#(7972) AI(7469) 爪哇(7425) MySQL(7256) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5875) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4603) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2436) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) HtmlCss(1968) 功能(1967) Web開發(1951) C++(1941) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • JavaCV的攝像頭實戰之八:人臉檢測

    ### 歡迎訪問我的GitHub > 這里分類和匯總了欣宸的全部原創(含配套原始碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本文是《JavaCV的攝像頭實戰》的第八 ......

    uj5u.com 2023-06-19 07:39:15 more
  • 使用 Sa-Token 實作 [記住我] 模式登錄、七天免登錄

    ### 一、需求分析 如圖所示,一般網站的登錄界面都會有一個 **`[記住我]`** 按鈕,當你勾選它登錄后,即使你關閉瀏覽器再次打開網站,也依然會處于登錄狀態,無須重復驗證密碼: ![../static/login-view.png](https://oss.dev33.cn/sa-token/d ......

    uj5u.com 2023-06-19 07:39:02 more
  • C++基礎知識總結

    > 2023/6/18 > > 本篇章記錄學習程序C++的基礎概念和代碼測驗實作,還有很多需要補充。一是還不清楚,二是還沒有學到。打算學習程序中后面再做補充。先看完《C++primer 》書之后再慢慢來添加補充 # 1.函式多載 1. 一個函式名可以實作多個功能,這取決于函式引數不同來實作判斷對應的 ......

    uj5u.com 2023-06-19 07:38:55 more
  • Java Websocket 01: 原生模式 Websocket 基礎通信

    原生模式下, 服務端通過 @ServerEndpoint 實作其對應的 @OnOpen, @OnClose, @OnMessage, @OnError 方法, 客戶端創建 WebSocketClient 實作對應的 onOpen(), onClose(), onMessage(), onError(... ......

    uj5u.com 2023-06-19 07:38:50 more
  • Scala集合

    # 集合 scala中的集合分為兩種 ,可變集合和不可變集合, 不可變集合可以安全的并發的訪問! 集合的類主要在一下兩個包中 - 可變集合包 scala.collection.mutable - 不可變集合包 scala.collection.immutable 默認的 Scala 不可變集合,就是 ......

    uj5u.com 2023-06-19 07:38:42 more
  • Go 語言之 Shutdown 關機和fvbock/endless 重啟

    # Go 語言之 Shutdown 關機和fvbock/endless 重啟 Shutdown 原始碼 ```go // Shutdown gracefully shuts down the server without interrupting any // active connections. ......

    uj5u.com 2023-06-19 07:38:33 more
  • NOI / 1.9編程基礎之順序 09:直方圖

    **描述** 給定一個非負整數陣列,統計里面每一個數的出現次數。我們只統計到陣列里最大的數。 假設 Fmax (Fmax using namespace std; int main(){ int n,x; int fmax=0;//陣列里最大的數 int a[10000]={0}; cin>>n; ......

    uj5u.com 2023-06-19 07:38:26 more
  • C++面試八股文:std::string是如何實作的?

    某日二師兄參加XXX科技公司的C++工程師開發崗位第18面: > 面試官:`std::string`用過吧? > > 二師兄:當然用過(廢話,C++程式員就沒有沒用過`std::string`的)。 > > 面試官:`std::string("hello")+"world"`、`"hello"+st ......

    uj5u.com 2023-06-19 07:38:22 more
  • 【QCustomPlot】使用方法(原始碼方式)

    使用 QCustomPlot 繪圖庫輔助開發時整理的學習筆記。本篇介紹 QCustomPlot 的一種使用方法,通過包含原始碼的方式進行使用,這也是最常用的方法,示例中使用的 QCustomPlot 版本為 Version 2.1.1。 ......

    uj5u.com 2023-06-19 07:38:18 more
  • Java 變數與基本資料型別

    # Java 變數與基本資料型別 # 1. 變數是保存特定資料型別的值。變數必須先宣告,后使用。變數表示記憶體中的一個存盤區域。變數在同一個域中不可出現相同的變數名。 ## # 2. 程式中 + 號的作用 > ## 如果兩邊都是數值,進行加法運算 > > ## 如果左右一邊有一方位字串,則做拼接字符 ......

    uj5u.com 2023-06-19 07:38:14 more