頭部姿態估計 - OpenCV/Dlib/Ceres

来源:https://www.cnblogs.com/bemfoo/archive/2019/07/26/11253450.html
-Advertisement-
Play Games

基本思想 通過Dlib獲得當前人臉的特征點,然後通過旋轉平移標準模型的特征點進行擬合,計算標準模型求得的特征點與Dlib獲得的特征點之間的差,使用Ceres不斷迭代優化,最終得到最佳的旋轉和平移參數。 使用環境 系統環境:Ubuntu 18.04 使用語言:C++ 編譯工具:CMake 第三方工具 ...


基本思想

通過Dlib獲得當前人臉的特征點,然後通過旋轉平移標準模型的特征點進行擬合,計算標準模型求得的特征點與Dlib獲得的特征點之間的差,使用Ceres不斷迭代優化,最終得到最佳的旋轉和平移參數。

使用環境

系統環境:Ubuntu 18.04
使用語言:C++
編譯工具:CMake

第三方工具

Dlib:用於獲得人臉特征點
Ceres:用於進行非線性優化
CMinpack:用於進行非線性優化 (OPTIONAL)

源代碼

https://github.com/Great-Keith/head-pose-estimation

基礎概念

旋轉矩陣

頭部的任意姿態可以轉化為6個參數(yaw, roll, pitch, tx, ty, tz),前三個為旋轉參數,後三個為平移參數。
平移參數好理解,原坐標加上對應的變化值即可;旋轉參數需要構成旋轉矩陣,三個參數分別對應了繞y軸旋轉的角度、繞z軸旋轉的角度和繞x軸旋轉的角度。

具體代碼實現我們可以通過Dlib已經封裝好的API,rotate_around_x/y/z(angle)。該函數返回的類型是dlib::point_transform_affine3d,可以通過括弧進行三維的變形,我們將其封裝成一個rotate函數使用如下:

void rotate(std::vector<point3f>& points, const double yaw, const double pitch, const double roll) 
{
    dlib::point_transform_affine3d around_z = rotate_around_z(roll * pi / 180);
    dlib::point_transform_affine3d around_y = rotate_around_y(yaw * pi / 180);
    dlib::point_transform_affine3d around_x = rotate_around_x(pitch * pi / 180);
    for(std::vector<point3f>::iterator iter=points.begin(); iter!=points.end(); ++iter)
        *iter = around_z(around_y(around_x(*iter)));
}

[NOTE] 其中point3f是我自己定義的一個三維點坐標類型,因為Dlib中並沒有提供,而使用OpenCV中的cv::Point3f會與dlib::point定義起衝突。定義如下:

typedef dlib::vector<double, 3> point3f;

[NOTE] Dlib中的dlib::vector不是std::vector,註意二者區分。

LM演算法

這邊不進行贅訴,建議跟著推導一遍高斯牛頓法,LM演算法類似於高斯牛頓法的進階,用於迭代優化求解非線性最小二乘問題。在該程式中使用Ceres/CMinpack封裝好的API(具體使用見後文)。

三維空間到二維平面的映射

根據針孔相機模型我們可以輕鬆的得到三維坐標到二維坐標的映射:
\(X^{2d}=f_x(\frac{X^{3d}}{Z^{3d}})+c_x\)
\(Y^{2d}=f_y(\frac{Y^{3d}}{Z^{3d}})+c_y\)
[NOTE] 使用上角標來表示3維坐標還是2維坐標,下同。
其中\(f_x, f_y, c_x, c_y\)為相機的內參,我們通過OpenCV官方提供的Calibration樣例進行獲取:
例如我的電腦所獲得的結果如下:

從圖中矩陣對應關係可以獲得對應的參數值。

#define FX 1744.327628674942
#define FY 1747.838275588676
#define CX 800
#define CY 600

[NOTE] 本程式不考慮外參。

具體步驟

獲得標準模型的特征點

該部分可見前一篇文章:BFM使用 - 獲取平均臉模型的68個特征點坐標
我們將獲得的特征點保存在文件 landmarks.txt 當中。

使用Dlib獲得人臉特征點

該部分不進行贅訴,官方有給出了詳細的樣例。
具體可以參考如下樣例:

其中使用官方提供的預先訓練好的模型,下載地址:http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2

具體在代碼中使用如下:

    cv::Mat temp;
    if(!cap.read(temp))
        break;
    dlib::cv_image<bgr_pixel> img(temp);
    std::vector<rectangle> dets = detector(img);
    cout << "Number of faces detected: " << dets.size() << endl;
    std::vector<full_object_detection> shapes;
    for (unsigned long j = 0; j < dets.size(); ++j) {
        /* Use dlib to get landmarks */
        full_object_detection shape = sp(img, dets[j]);
        /* ... */
    }

其中shape.part就存放著我們通過Dlib獲得的當前人臉的特征點二維點序列。

[NOTE] 在最後CMake配置的時候,需要使用Release版本(最重要),以及增加選項USE_AVX_INSTRUCTIONSUSE_SSE2_INSTRUCTIONS/USE_SSE4_INSTRUCTIONS,否則因為Dlib的檢測耗時較長,使用攝像頭即時擬合會有嚴重的卡頓。

使用Ceres進行非線性優化

Ceres的使用官方也提供了詳細的樣例,在此我們使用的是數值差分的方法,可參考:https://github.com/ceres-solver/ceres-solver/blob/master/examples/helloworld_numeric_diff.cc

    Problem problem;
    CostFunction* cost_function = new NumericDiffCostFunction<CostFunctor, ceres::RIDDERS, LANDMARK_NUM, 6>(new CostFunctor(shape));
    problem.AddResidualBlock(cost_function, NULL, x);
    Solver::Options options;
    options.minimizer_progress_to_stdout = true;
    Solver::Summary summary;
    Solve(options, &problem, &summary);
    std::cout << summary.BriefReport() << endl;

這裡我直接使用了數值差分的方法(NumericDiffCostFunction),而不是使用自動差分(AutoDiffCostFunction),是因為自動差分的CostFunctor是通過Template實現的,利用Template來實現Jacobian矩陣的計算使用的同一個結構,這樣的話下方旋轉矩陣就不能直接通過調用Dlib提供的三維坐標旋轉介面,而是要將整個矩陣拆解開來實現(這邊暫時沒有細想到底能不能實現),因此出於簡便,使用數值差分,在準確性上是會受到影響的。
並且註意到,具體的方法使用了Ridders(ceres::RIDDERS),而不是向前差分(ceres::FORWARD)或者中分(ceres::CENTRAL),因為用後兩者進行處理的時候,LM演算法\(\beta_{k+1}=\beta_k-(J^TJ+\lambda I)^{-1}J^Tr)\)的更新項為0,無法進行迭代,暫時沒有想到原因,之前這裡也被卡了很久。
[NOTE] 源代碼中還有使用了CMinpack的版本,該版本不可用的原因也是使用了封裝最淺的lmdif1_調用(返回結果INFO=4),該版本下使用的向前差分,如果改為使用lmdif_對其中的一些參數進行調整應該是可以實現的。

CostFunctor的構建

CostFunctor的構建是Ceres,也是這個程式,最重要的部分。首先我們需要先把想要計算的式子寫出來:
\(Q=\sum_i^{LANDMARK\_NUM} \|q_i^{2d}-p_i^{2d}\|^2\)
\(Q=\sum_i^{LANDMARK\_NUM} \|q_i^{2d}-Map(R(yaw, roll, pitch)p_i^{3d}+T(t_x, t_y, t_z))\|^2\)
其中:

  • LANDMARK_NUM:該程式中為68,因為Dlib演算法獲得的特征點數為68;;
  • \(q_i^{2d}\):通過Dlib獲得的2維特征點坐標,大小為68的vector<dlib::point>
  • \(p_i^{2d}\):經過一系列變換得到的標準模型的2維特征點坐標,大小為68的vector<dlib::point>,具體計算方法是通過\(p_i^{2d}=Map(R(yaw, roll, pitch)(p_i^{3d})+T(t_x, t_y, t_z))\)
  • \(p_i^{3d}\):標準模型的三維3維特征點坐標,大小為68的vector<point3f>;
  • \(R(yaw, roll, pitch)\):旋轉矩陣;
  • \(T(t_x, t_y, t_z)\):平移矩陣;
  • \(Map()\):3維點轉2維點的映射,如上所描述通過相機內參獲得。
  • \(\|·\|\):因為是兩個2維點的差,我們使用歐幾裡得距離來作為2點的差。
    Ceres當中的CostFunctor只需要寫入平方以內的內容,因此我們如下構建:
struct CostFunctor {
public:
    CostFunctor(full_object_detection _shape){ shape = _shape; }
    bool operator()(const double* const x, double* residual) const {
        /* Init landmarks to be transformed */
        fitting_landmarks.clear();
        for(std::vector<point3f>::iterator iter=model_landmarks.begin(); iter!=model_landmarks.end(); ++iter)
            fitting_landmarks.push_back(*iter);
        transform(fitting_landmarks, x);
        std::vector<point> model_landmarks_2d;
        landmarks_3d_to_2d(fitting_landmarks, model_landmarks_2d);

        /* Calculate the energe (Euclid distance from two points) */
        for(int i=0; i<LANDMARK_NUM; i++) {
            long tmp1 = shape.part(i).x() - model_landmarks_2d.at(i).x();
            long tmp2 = shape.part(i).y() - model_landmarks_2d.at(i).y();
            residual[i] = sqrt(tmp1 * tmp1 + tmp2 * tmp2);
        }
        return true;
    }
private:
    full_object_detection shape;    /* 3d landmarks coordinates got from dlib */
};

其中的參數x是一個長度為6的數組,對應了我們要獲得的6個參數。

初始值的選定

當前並沒有多考慮這個因素,在landmark-fitting-cam程式中除了第一幀的初始值是提前設置好的以外,後續的初始值都是前一幀的最優值。
後面的表現都很好,但這第一幀確實會存在紊亂的情況。
因此後續優化可以考慮使用一個粗估計的初始值,因為對於這些迭代優化方法,初始值的選擇決定了會不會陷入局部最優的情況。

測試結果

臉部效果:
only_face
輸出工作環境:
work_place


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

-Advertisement-
Play Games
更多相關文章
  • 流量紅利正逐漸走向終結,這已經不再是什麼秘密。後互聯網時代,如何維繫住用戶群,提升用戶在平臺上的體驗是整個行業都需要考慮的事情。正是出於這一原因,現在全行業都在關註會員體系的搭建,這也是馬蜂窩 2019 年重點投入的方向之一。 面對這個全行業都在發力的會員市場,要對「馬蜂窩特色」的會員體系進行有力的 ...
  • 分散式鎖 為什麼需要有分散式鎖呢,在單點的時候synchronized 就能解決,但是服務拆分之後,每個服務都是單獨的機器,無法解決,所以出現了分散式鎖,其實也就是用各種手段,實現獲取唯一鎖,別人無法得到。 其實在做分散式鎖的前提,需要先明白,synchronized 為啥不能使用了,啥原理讓他在一 ...
  • 智能家居-1.基於esp8266的語音控制系統(開篇) 智能家居-2.基於esp8266的語音控制系統(硬體篇) 智能家居-3.基於esp8266的語音控制系統(軟體篇) 樹莓派安裝及配置 鏡像文件下載地址 https://www.raspberrypi.org/downloads/ 安裝apach ...
  • 命令行工具 1. jps JVM Process Status Tool,顯示虛擬機進程。 用法 : 參數說明 q:列印進程號 l:列印啟動類的全限定名 m:列印啟動類的 main 方法入參 v:列印指定的虛擬機參數 V:列印類名 例子 : 、`jps l` 2、jstat JVM statisti ...
  • 示例 指的是,以 Server模式啟動,初始堆1024m,最大堆1024m,初始新生代256m,最大新生代512m,列印詳細的GC日誌,並輸出到gc.$$.log。 常見虛擬機參數 client   客戶端模式。 server   服務端模式,Java8 64 ...
  • 一、持久化 --shelve 持久化工具 (1)作用:類似字典,用kv對保存數據,存取方式類似於字典 (2)例子:通過一下案例創建了一個資料庫,第二個程式我們讀取了資料庫 2.shelve特性 (1)註意:不支持多個應用並行寫入,為瞭解決這個問題,open的時候可以使用writeback=True, ...
  • 今天比較晚,所以只看了shiro的認證策略Authentication Strategy,下麵講講shiro的三種認證策略。 1.AtLeastOneSuccessfulStrategy:這個是shiro預設的認證策略,它表示如果存在多個realm來執行認證,只要其中有一個成功,那麼認證就成功(這裡 ...
  • 先說顏色: 綠色:public 黃色:protected 藍色:no modifier 紅色:private 再說形狀: 實心:method 空心:variable 實心中間有字母C:class Class右側有向右的箭頭:運行入口 再說字母: S:static F:final 常用組合: 綠圓圈: ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...