Java版人臉跟蹤三部曲之三:編碼實戰

来源:https://www.cnblogs.com/bolingcavalry/archive/2023/07/08/17533899.html
-Advertisement-
Play Games

### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 作為《Java版人臉跟蹤三部曲》系列的終 ...


歡迎訪問我的GitHub

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

本篇概覽

  • 作為《Java版人臉跟蹤三部曲》系列的終篇,本文會與大家一起寫出完整的人臉跟蹤應用代碼
  • 前文《開發設計》中,已經對人臉跟蹤的核心技術、應用主流程、異常處理等方方面面做了詳細設計,建議您簡單回顧一下
  • 接下來,自頂向下,先整體設計好主框架和關鍵類

程式主框架和關鍵類

  • 聽欣宸嘮叨了兩篇文章,終於要看具體代碼了,整體上看,最關鍵的三個類如下圖:
    在這裡插入圖片描述
  • 可見把功能、流程、知識點梳理清楚後,代碼其實並不多,而且各司其職,分工明確,接下來開始編碼,ObejctTracker負責實現跟蹤功能,就從它開始

ObejctTracker.java:跟蹤能力的提供者

  • 從前面的圖中可知,與跟蹤有關的服務都是ObejctTracker類提供的,此類涉及知識點略多,在編寫代碼前,先做一下簡單的設計
  • 從功能看,ObejctTracker會對外提供如下兩個方法:
方法名 作用 入參 返回 內部實現
createTrackedObject 主程式如果從視頻幀中首次次檢測到人臉,就會調用createTrackedObject方法,表示開始跟蹤了 mRgba:出現人臉的圖片
region:人臉在圖片中的位置
提取人臉的hue,生成直方圖
objectTracking 開始跟蹤後,主程式從攝像頭取到的每一幀圖片後,都會調用此方法,用於得到人臉在這一幀中的位置 mRgba:圖片 人臉在輸入圖片中位置 用人臉hue直方圖對輸入圖片進行計算,得到反向投影圖,在反向投影圖上做CamShift計算得到人臉位置
  • 除了上述兩個對外方法,ObejctTracker內部還要準備如下兩個輔助方法:
方法名 作用 入參 返回 內部實現
rgba2Hue 將RGB顏色空間的圖片轉為HSV,再提取出hue通道,生成直方圖 rgba:人臉圖片 List<Mat>:直方圖
lostTrace 對比objectTracking方法返回的結果與上次出現的位置,確定人有沒有跟丟 lastRect:上次出現的位置
currentRect:objectTracking方法檢測到的當前幀上的位置
true表示跟丟了,false表示沒有跟丟 對比兩個矩形的差距是否超過一個門限,正常情況下連續兩幀中的人臉差別不會太大,所以一旦差別大了就表示跟丟了,currentRect的位置上不是人臉
  • 還有幾個成員變數也很重要:
    // 每一幀圖像的反向投影圖都用這個成員變數來保存
    private Mat prob;

    // 保存最近一次確認的頭像的位置,每當新的一幀到來時,都從這個位置開始追蹤(也就是反向投影圖做CamShift計算的起始位置)
    private Rect trackRect;

    // 直方圖,在跟丟之前,每一幀圖像都要用到這個直方圖來生成反向投影
    private Mat hist;
  • 設計完成,現在可以給出完整的ObejctTracker.java源碼了:
package com.bolingcavalry.grabpush.extend;

import lombok.extern.slf4j.Slf4j;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import org.opencv.video.Video;
import java.util.Collections;
import java.util.List;
import java.util.Vector;

/**
 * @author willzhao
 * @version 1.0
 * @description TODO
 * @date 2022/1/8 21:21
 */
@Slf4j
public class ObjectTracker {

    /**
     * 上一個矩形和當前矩形的差距達到多少的時候,才算跟丟,您可以自行調整
     */
    private static final double LOST_GATE = 0.8d;

    // [0.0, 256.0]表示直方圖能表示像素值從0.0到256的像素
    private static final MatOfFloat RANGES = new MatOfFloat(0f, 256f);

    private Mat mask;

    // 保存用來追蹤的每一幀的反向投影圖
    private Mat prob;

    // 保存最近一次確認的頭像的位置,每當新的一幀到來時,都從這個位置開始追蹤(也就是反向投影圖做CamShift計算的起始位置)
    private Rect trackRect;

    // 直方圖
    private Mat hist;


    public ObjectTracker(Mat rgba) {
        hist = new Mat();
        trackRect = new Rect();
        mask = new Mat(rgba.size(), CvType.CV_8UC1);
        prob = new Mat(rgba.size(), CvType.CV_8UC1);
    }

    /**
     * 將攝像頭傳來的圖片提取出hue通道,放入hueList中
     * 將攝像頭傳來的RGB顏色空間的圖片轉為HSV顏色空間,
     * 然後檢查HSV三個通道的值是否在指定範圍內,mask中記錄了檢查結果
     * 再將hsv中的hue提取出來
     * @param rgba
     */
    private List<Mat> rgba2Hue(Mat rgba) {
        // 實例化Mat,顯然,hsv是三通道,hue是hsv三通道其中的一個,所以hue是一通道
        Mat hsv = new Mat(rgba.size(), CvType.CV_8UC3);
        Mat hue = new Mat(rgba.size(), CvType.CV_8UC1);

        // 1. 先轉換
        // 轉換顏色空間,RGB到HSV
        Imgproc.cvtColor(rgba, hsv, Imgproc.COLOR_RGB2HSV);

        int vMin = 65, vMax = 256, sMin = 55;
        //inRange函數的功能是檢查輸入數組每個元素大小是否在2個給定數值之間,可以有多通道,mask保存0通道的最小值,也就是h分量
        //這裡利用了hsv的3個通道,比較h,0~180,s,smin~256,v,min(vmin,vmax),max(vmin,vmax)。如果3個通道都在對應的範圍內,
        //則mask對應的那個點的值全為1(0xff),否則為0(0x00).
        Core.inRange(
                hsv,
                new Scalar(0, sMin, Math.min(vMin, vMax)),
                new Scalar(180, 256, Math.max(vMin, vMax)),
                mask
        );

        // 2. 再提取
        // 把hsv的數據放入hsvList中,用於稍後提取出其中的hue
        List<Mat> hsvList = new Vector<>();
        hsvList.add(hsv);

        // 準備好hueList,用於接收通道
        // hue初始化為與hsv大小深度一樣的矩陣,色調的度量是用角度表示的,紅綠藍之間相差120度,反色相差180度
        hue.create(hsv.size(), hsv.depth());

        List<Mat> hueList = new Vector<>();
        hueList.add(hue);

        // 描述如何提取:從目標的0位置提取到目的地的0位置
        MatOfInt from_to = new MatOfInt(0, 0);

        // 提取操作:將hsv第一個通道(也就是色調)的數複製到hue中,0索引數組
        Core.mixChannels(hsvList, hueList, from_to);

        return hueList;
    }

    /**
     * 當外部調用方確定了人臉在圖片中的位置後,就可以調用createTrackedObject開始跟蹤,
     * 該方法中會先生成人臉的hue的直方圖,用於給後續幀生成反向投影
     * @param mRgba
     * @param region
     */
    public void createTrackedObject(Mat mRgba, Rect region) {
        hist.release();

        //將攝像頭的視頻幀轉化成hsv,然後再提取出其中的hue通道
        List<Mat> hueList = rgba2Hue(mRgba);

        // 人臉區域的mask
        Mat tempMask = mask.submat(region);

        // histSize表示這個直方圖分成多少份(即多少個直方柱),就是 bin的個數
        MatOfInt histSize = new MatOfInt(25);
        // 只要頭像區域的數據
        List<Mat> images = Collections.singletonList(hueList.get(0).submat(region));
        // 計算頭像的hue直方圖,結果在hist中
        Imgproc.calcHist(images, new MatOfInt(0), tempMask, hist, histSize, RANGES);

        // 將hist矩陣進行數組範圍歸一化,都歸一化到0~255
        Core.normalize(hist, hist, 0, 255, Core.NORM_MINMAX);

        // 這個trackRect記錄了人臉最後一次出現的位置,後面新的幀到來時,就從trackRect位置開始做CamShift計算
        trackRect = region;
    }

    /**
     * 在開始跟蹤後,每當攝像頭新的一幀到來時,外部就會調用objectTracking,將新的幀傳入,
     * 此時,會用前面準備好的人臉hue直方圖,將新的幀計算出反向投影圖,
     * 再在反向投影圖上執行CamShift計算,找到密度最大處,即人臉在新的幀上的位置,
     * 將這個位置作為返回值,返回
     * @param mRgba 新的一幀
     * @return 人臉在新的一幀上的位置
     */
    public Rect objectTracking(Mat mRgba) {
        // 新的圖片,提取hue
        List<Mat> hueList;
        try {
           // 實測此處可能拋出異常,要註意捕獲,避免程式退出
            hueList = rgba2Hue(mRgba);
        } catch (CvException cvException) {
            log.error("cvtColor exception", cvException);
            trackRect = null;
            return null;
        }

        // 用頭像直方圖在新圖片的hue通道數據中計算反向投影。
        Imgproc.calcBackProject(hueList, new MatOfInt(0), hist, prob, RANGES, 1.0);
        // 計算兩個數組的按位連接(dst = src1 & src2)計算兩個數組或數組和標量的每個元素的逐位連接。
        Core.bitwise_and(prob, mask, prob, new Mat());

        // 在反向投影上進行CamShift計算,返回值就是密度最大處,即追蹤結果
        RotatedRect rotatedRect = Video.CamShift(prob, trackRect, new TermCriteria(TermCriteria.EPS, 10, 1));

        // 轉為Rect對象
        Rect camShiftRect = rotatedRect.boundingRect();

        // 比較追蹤前和追蹤後的數據,如果出現太大偏差,就認為追蹤失敗
        if (lostTrace(trackRect, camShiftRect)) {
            log.info("lost trace!");
            trackRect = null;
            return null;
        }

        // 將本次最終到的目標作為下次追蹤的對象
        trackRect = camShiftRect;

        return camShiftRect;
    }

    /**
     * 變化率的絕對值
     * @param last 變化前
     * @param current 變化後
     * @return
     */
    private static double changeRate(int last, int current) {
        return Math.abs((double)(current-last)/(double) last);
    }

    /**
     * 本次和上一次寬度或者高度的變化率,一旦超過閾值就認為跟蹤失敗
     * @param lastRect
     * @param currentRect
     * @return
     */
    private static boolean lostTrace(Rect lastRect, Rect currentRect) {
        // 0不能做除數,如果發現0就認跟丟了
        if (lastRect.width<1 || lastRect.height<1) {
            return true;
        }

        double widthChangeRate = changeRate(lastRect.width, currentRect.width);

        if (widthChangeRate>LOST_GATE) {
            log.info("1. lost trace, old [{}], new [{}], rate [{}]", lastRect.width, currentRect.width, widthChangeRate);
            return true;
        }

        double heightChangeRate = changeRate(lastRect.height, currentRect.height);

        if (heightChangeRate>LOST_GATE) {
            log.info("2. lost trace, old [{}], new [{}], rate [{}]", lastRect.height, currentRect.height, heightChangeRate);
            return true;
        }

        return false;
    }
}
  • 最核心的跟蹤服務已經完成,接下來要實現完整業務邏輯,即:CamShiftDetectService.java

CamShiftDetectService.java:業務邏輯的提供者

  • 有了核心能力,接下來要做的就是在業務中使用這個能力,前文已設計好完整的業務邏輯,這裡先簡單回顧一下:
    在這裡插入圖片描述
  • 可見主要業務流程可以用兩個狀態+行為來表示:
  1. 還未開始跟蹤:對每一幀做人臉檢測,一旦檢測到,就進入跟蹤狀態,並調用ObjectTracker.createTrackedObject生成人臉的hue直方圖
  2. 已處於跟蹤狀態:對每一幀圖像,都調用ObjectTracker.objectTracking去檢查人臉在圖像中的位置,直到到跟丟了為止,一旦跟丟了,就重新進入到還未開始跟蹤的狀態
  • 現在我們已經清楚了CamShiftDetectService.java要做的具體事情,接下來看看有哪些重要方法:
方法名 作用 入參 返回 內部實現
init 被主程式調用的初始化方法,在應用啟動的時候會調用一次 載入人臉檢測的模型
convert 每當主程式從攝像頭拿到新的一幀後,都會調用此方法 frame:來自攝像頭的最新一幀 被處理後的幀,會被主程式展現在預覽視窗 convert方法內部實現了前面提到的兩種狀態和行為(還未開始跟蹤、已處於跟蹤狀態)
releaseOutputResource 程式結束前,被主程式調用的釋放資源的方法 釋放一些成員變數的資源
  • 再來看看有哪些重要的成員變數,如下所示,isInTracing表示當前是否處於跟蹤狀態,classifier用於檢測人臉:
/**
     * 每一幀原始圖片的對象
     */
    private Mat grabbedImage = null;

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

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

    /**
     * 模型文件的下載地址
     */
    private String modelFilePath;

    /**
     * 存放RGBA圖片Mat
     */
    private Mat mRgba;

    /**
     * 存放灰度圖片的Mat,僅用在人臉檢測的時候
     */
    private Mat mGray;

    /**
     * 跟蹤服務類
     */
    private ObjectTracker objectTracker;

    /**
     * 表示當前是否正在跟蹤目標
     */
    private boolean isInTracing = false;
  • 現在可以給出CamShiftDetectService.java的完整代碼了:
package com.bolingcavalry.grabpush.extend;

import com.bolingcavalry.grabpush.Util;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Rect;
import org.bytedeco.opencv.opencv_core.RectVector;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
import java.io.File;
import static org.bytedeco.opencv.global.opencv_imgproc.CV_BGR2GRAY;
import static org.bytedeco.opencv.global.opencv_imgproc.cvtColor;

@Slf4j
public class CamShiftDetectService implements DetectService {

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

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

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

    /**
     * 模型文件的下載地址
     */
    private String modelFilePath;

    /**
     * 存放RGBA圖片Mat
     */
    private Mat mRgba;

    /**
     * 存放灰度圖片的Mat,僅用在人臉檢測的時候
     */
    private Mat mGray;

    /**
     * 跟蹤服務類
     */
    private ObjectTracker objectTracker;

    /**
     * 表示當前是否正在跟蹤目標
     */
    private boolean isInTracing = false;

    /**
     * 構造方法,在此指定模型文件的下載地址
     * @param modelFilePath
     */
    public CamShiftDetectService(String modelFilePath) {
        this.modelFilePath = modelFilePath;
    }

    /**
     * 音頻採樣對象的初始化
     * @throws Exception
     */
    @Override
    public void init() throws Exception {
        log.info("開始載入模型文件");
        // 模型文件下載後的完整地址
        String classifierName = new File(modelFilePath).getAbsolutePath();

        // 根據模型文件實例化分類器
        classifier = new CascadeClassifier(classifierName);

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

        log.info("模型文件載入完畢,初始化完成");
    }



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

        // 初始化灰度Mat
        if (null==mGray) {
            mGray = Util.initGrayImageMat(grabbedImage);
        }

        // 初始化RGBA的Mat
        if (null==mRgba) {
            mRgba = Util.initRgbaImageMat(grabbedImage);
        }

        // 如果未在追蹤狀態
        if (!isInTracing) {
            // 存放檢測結果的容器
            RectVector objects = new RectVector();

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

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

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

            // 當前實例是只追蹤一人,因此一旦檢測結果不等於一,就不處理,您可以根據自己業務情況修改此處
            if (total!=1) {
                objects.close();
                return frame;
            }

            log.info("start new trace");

            Rect r = objects.get(0);
            int x = r.x(), y = r.y(), w = r.width(), h = r.height();

            // 得到opencv的mat,其格式是RGBA
            org.opencv.core.Mat openCVRGBAMat = Util.buildJavacvBGR2OpenCVRGBA(grabbedImage, mRgba);

            // 在buildJavacvBGR2OpenCVRGBA方法內部,有可能在執行native方法的是否發生異常,要做針對性處理
            if (null==openCVRGBAMat) {
                objects.close();
                return frame;
            }

            // 如果第一次追蹤,要實例化objectTracker
            if (null==objectTracker) {
                objectTracker = new ObjectTracker(openCVRGBAMat);
            }

            // 創建跟蹤目標
            objectTracker.createTrackedObject(openCVRGBAMat, new org.opencv.core.Rect(x, y, w, h));
            // 根據本次檢測結果給原圖標註人臉矩形框
            Util.rectOnImage(grabbedImage, x, y, w, h);

            // 釋放檢測結果資源
            objects.close();

            // 修改標誌,表示當前正在跟蹤
            isInTracing = true;

            // 將標註過的圖片轉為幀,返回
            return converter.convert(grabbedImage);
        }

        // 代碼走到這裡,表示已經在追蹤狀態了

        // 得到opencv的mat,其格式是RGBA
        org.opencv.core.Mat openCVRGBAMat = Util.buildJavacvBGR2OpenCVRGBA(grabbedImage, mRgba);

        // 在buildJavacvBGR2OpenCVRGBA方法內部,有可能在執行native方法的是否發生異常,要做針對性處理
        if (null==openCVRGBAMat) {
            return frame;
        }

        // 基於上一次的檢測結果開始跟蹤
        org.opencv.core.Rect rotatedRect = objectTracker.objectTracking(openCVRGBAMat);

        // 如果rotatedRect為空,表示跟蹤失敗,此時要修改狀態為"未跟蹤"
        if (null==rotatedRect) {
            isInTracing = false;
            // 返回原始幀
            return frame;
        }

        // 代碼能走到這裡,表示跟蹤成功,拿到的新的一幀上的目標的位置,此時就在新位置上
//        Util.rectOnImage(grabbedImage, rotatedRect.x, rotatedRect.y, rotatedRect.width, rotatedRect.height);
        // 矩形框的整體向下放一些(總高度的五分之一),另外跟蹤得到的高度過大,畫出的矩形框把脖子也框上了,這裡改用寬度作為高度
        Util.rectOnImage(grabbedImage, rotatedRect.x, rotatedRect.y + rotatedRect.height/5, rotatedRect.width, rotatedRect.width);
        return converter.convert(grabbedImage);
    }

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

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

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

        if (null==classifier) {
            classifier.close();
        }
    }
}
  • 至此·,功能已經完成得七七八八,再來寫完主程式就可以運行了;

PreviewCameraWithCamShift.java:主程式

  • 《JavaCV的攝像頭實戰之一:基礎》創建的simple-grab-push工程中已經準備好了父類AbstractCameraApplication,所以本篇繼續使用該工程,創建子類PreviewCameraWithCamShift實現那些抽象方法即可
  • 編碼前先回顧父類的基礎結構,如下圖,粗體是父類定義的各個方法,紅色塊都是需要子類來實現抽象方法,所以接下來,咱們以本地視窗預覽為目標實現這三個紅色方法即可:
    在這裡插入圖片描述
  • 新建文件PreviewCameraWithCamShift.java,這是AbstractCameraApplication的子類,其代碼很簡單,接下來按上圖順序依次說明
  • 先定義CanvasFrame類型的成員變數previewCanvas,這是展示視頻幀的本地視窗:
protected CanvasFrame previewCanvas
  • 把前面創建的DetectService作為成員變數,後面檢測的時候會用到:
    /**
     * 檢測工具介面
     */
    private DetectService detectService;
  • PreviewCameraWithCamShift的構造方法,接受DetectService的實例:
    /**
     * 不同的檢測工具,可以通過構造方法傳入
     * @param detectService
     */
    public PreviewCameraWithCamShift(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方法,代碼如下,請註意人臉檢測所需的模型文件的路徑來自系統變數:
    public static void main(String[] args) {
        String modelFilePath = System.getProperty("model.file.path");
        log.info("模型文件本地路徑:{}", modelFilePath);
        new PreviewCameraWithCamShift(new CamShiftDetectService(modelFilePath)).action(1000);
    }

運行程式要註意的地方

  1. 下載opencv在windows環境的動態鏈接庫:https://download.csdn.net/download/boling_cavalry/75121158,我這裡下載後放在:C:\study\javacv\lib\opencv_java453.dll
  2. 人臉檢測的模型文件,在GitHub下載,地址是:https://raw.github.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml,我這裡下載後放在:C:\study\javacv\model\haarcascade_frontalface_alt.xml
  3. 運行程式的時候,不論是打包成jar,還是直接在IDEA中運行,都要添加下麵這兩個命令參數,才能確保應用載入到dll和模型文件(請按照您自己的存儲位置修改下麵參數的值):
  • -Djava.library.path=C:\study\javacv\lib
  • -Dmodel.file.path=C:\study\javacv\model\haarcascade_frontalface_alt.xml
  • 程式運行起來後,具體的效果與像《Java版人臉跟蹤三部曲之一:極速體驗》中一模一樣,這裡就不再贅述了,您自行驗證就好
  • 其實本篇不運行程式,還有一個原因就是要過年了,用來檢測人臉的群眾演員臨時漲價,要兩份盒飯,欣宸實在是負擔不起...

源碼下載

名稱 鏈接 備註
項目主頁 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工程下:
    在這裡插入圖片描述
  • 至此,《Java版人臉跟蹤三部曲》完美收官,但是《JavaCV的攝像頭實戰》系列還會繼續呈現更多精彩內容,歡迎關註;

歡迎關註博客園:程式員欣宸

學習路上,你不孤單,欣宸原創一路相伴...


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # C++ 慣用法之 Copy-Swap 拷貝交換 > 這是“C++ 慣用法”合集的第 3 篇,前面 2 篇分別介紹了 RAII 和 PIMPL 兩種慣用法: > > - [RAII: Resouce Acquistion Is Initialization](https://www.cnblogs ...
  • **本文首先介紹了Django模板系統的基礎知識,接著探討瞭如何安裝和配置Django模板系統,然後深入解析了Django模板的基本結構、標簽和過濾器的用法,闡述瞭如何在模板中展示模型數據,最後使用一個實際項目的例子來演示如何在實際開發中使用Django模板系統。** ## Django模板系統的簡 ...
  • # 1.用戶角色配置 ![image.png](https://cdn.nlark.com/yuque/0/2023/png/38371876/1688636206975-acd927ca-1559-4236-85f2-07283999d50b.png#averageHue=%23f3f2f2&cl ...
  • Java存在很多ORM框架,MyBaits框架是我們項目中使用得最多也是最願意推薦的框架,它既有數據表和Java對象映射功能,又有原生SQL的特性。在與SpringBoot集成上,和其他框架一樣,可以做到全註解化,無XML配置…… ...
  • 要用Python寫一個網站,你可以使用Python的Web框架來開發。常見的Python Web框架包括Django、Flask、Bottle等。以下是一個簡單的使用Flask框架開發的示例。 ### 1. 安裝Flask 在開始開發之前,你需要安裝Flask框架。你可以使用以下命令來安裝: ``` ...
  • ### 1.進程 進程是一個具有一定獨立功能的程式在一個數據集上的一次動態執行的過程,是操作系統進行資源分配和調度的一個獨立單位,是應用程式運行的載體。進程是一種抽象的概念,從來沒有統一的標准定義。進程一般由程式、數據集合和進程式控制制塊三部分組成。程式用於描述進程要完成的功能,是控制進程執行的指令集; ...
  • ## 1.生產者工程 - pom.xml里引入依賴 ~~~xml org.springframework.boot spring-boot-starter-amqp ~~~ - application.yml里配置基本信息 ~~~yaml spring: rabbitmq: host: localh ...
  • 哈嘍兄弟們,今天來實現採集一下最新的qcwu招聘數據。因為網站嘛,大家都爬來爬去的,人家就會經常更新,所以代碼對應的也要經常重新去寫。 對於會的人來說,當然無所謂,任他更新也攔不住,但是對於不會的小伙伴來說,網站一更新,當場自閉。 所以這期是出給不會的小伙伴的,我還錄製了視頻進行詳細講解,跟源碼一起 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...