本章博客上接:
《[HI3516DV300開發筆記]opencv移植與使用》
https://blog.csdn.net/abc517789065/article/details/103574974
有兩種做事方式,一種是每一步都要做到完善;另外一種是走一步看一步,打通全程再回頭整理。以前我是前一種,但後來發現,這種思路在我身上其實是拖延症。
按照道理來說,openCV移植好了,我應該新建一套工程代碼來整合HISI SVP NNIE demo的C與openCV C++進行混合編程,但是那樣太花時間。還是儘快拿到結果爲好。
所以在openCV移植到開發板完畢的情況下,先利用openCV的C接口對openpose模型的推理輸出進行處理,儘快解算出NNIE的輸出數據。
<0> 編譯路徑
我的HISI SDK SVP路徑是:...\Hi3516CV500_SDK_V2.0.1.1\smp
NNIE Sample路徑是:...\Hi3516CV500_SDK_V2.0.1.1\smp\a7_linux\mpp\sample\svp\nnie
<1> 添加openCV頭文件到HISI SDK sample目錄
將openCV交叉編譯後生成的include文件夾下的opencv & opencv2目錄拷貝到HISI SDK路徑:
...\Hi3516CV500_SDK_V2.0.1.1\smp\a7_linux\mpp\include
修改..\Hi3516CV500_SDK_V2.0.1.1\smp\a7_linux\mpp\Makefile.linux.param 文件中的 INC_FLAGS:
由 INC_FLAGS := -I$(REL_INC) 改爲 -I$(REL_INC)/opencv/ -I$(REL_INC)/opencv2/
<2> 添加openCV庫文件到HISI SDK sample目錄
將openCV交叉編譯後生成的lib文件夾下的所有庫文件拷貝到:
...\sdk_hisi3516dv300\Hi3516CV500_SDK_V2.0.1.1\smp\a7_linux\mpp\lib下
向 ..\Hi3516CV500_SDK_V2.0.1.1\smp\a7_linux\mpp\Makefile.linux.param 文件中添加:
CFLAGS += -L $(REL_LIB) -lopencv_world -lrt -ldl
<3> 測試
在sample_nnie.c文件內添加頭文件引用#include<opencv/cv.h>;然後隨便找個地方新建變量 CvMat img;
make成功即可。
通過調用opencv的C接口實現功能,頭文件引入例如 #include <opencv/cv.h> 即可
上篇博客說過,爲了實現人體2D關鍵點的結算,需要對25個關鍵點的46*46的置信圖進行解析
所謂置信圖,就是一組概率數據,超過預設閾值且概率局部最大的位置就是人體關鍵點,一張圖片的一種關鍵點可能有多個(多個人)
置信圖先後經過高斯濾波,二值化,邊沿查找,找到圖片中超過閾值的最可能是人體關鍵點的位置,結算出座標
具體的說,以這張圖(PC端用python+opencv檢測顯示)爲例:
橙色點標示nose,在46*46*25的置信圖數據中,第1個46*46的矩陣就是nose的置信圖
通過對置信圖矩陣進行平滑濾波,二值化,邊沿查找等操作,最終會找出三個局部最大值點,通過座標轉換(置信圖是46*46而輸入是368*368),就是圖示的三個nose位置:
Keypoints - Nose : [(36, 22, 0.86816400000000005), (23, 15, 0.93554700000000002), (14, 15, 0.87597700000000001)]
(36, 22) (23, 15) (14, 15)是在46*46置信圖內的位置
Keypoints - Nose : [(291, 179, 0.84146020312500003), (188, 123, 0.89069948828125001), (115, 124, 0.8388971328125)]
(291,179) (188, 123) (115, 124)是在368*368顯示圖(上圖)中標點的位置
結算部分代碼:
<0> 生成置信圖Mat並賦值
CvMat *pFloat32Mat = NULL;
CvMat *pUint8Mat = NULL;
pFloat32Mat = cvCreateMat(46, 46, CV_32FC1); //創建Mat 存儲置信圖
pUint8Mat = cvCreateMat(46, 46, CV_8UC1); //創建Mat 存儲二值圖
<1> 將NNIE的輸出賦值到Mat
for(n = 0; n < pstNnieParam->astSegData[u32SegIdx].astDst[u32NodeIdx].u32Num; n++){
//取出輸出數據的前46*46*25個關鍵點置信圖矩陣 i是通道計數
for(i = 0;i < /*u32Chn*/25; i++){
for(j = 0; j < u32Height; j++){
for(k = 0; k < u32Width; k++){
//將置信圖矩陣賦值到pFloat32Mat
cvmSet(pFloat32Mat, j, k, (HI_FLOAT)(*(ps32ResultAddr + k) * 0.000244));
}
ps32ResultAddr += u32Stride/sizeof(HI_U32);
}
//其它代碼....
}
<2> 高斯濾波(平滑),二值化置信矩陣,並統計每個通道大於閾值的點的個數
//對置信圖進行高斯濾波
cvSmooth(pFloat32Mat, pFloat32Mat, CV_GAUSSIAN, 3, 3, 0, 0);
//對置信圖進行二值化,閾值是0.1
cvThreshold(pFloat32Mat, pUint8Mat, 0.1, 1, CV_THRESH_BINARY);
highConfidencePointCnt = 0;
//遍歷二值化後的置信圖
for(y = 0; y < 46; y++){
for(x = 0; x < 46; x++){
uint8Num = *((HI_U8*)(pUint8Mat->data.ptr + pUint8Mat->step * (y) + (sizeof(HI_U8))*(x)));
//檢測到此時置信圖不爲0的點,也即是概率大於0.1的高可能性點
if(uint8Num != 0){
//統計高置信概率點的個數
printf("(%d, %d, %f) ", x, y, cvmGet(pFloat32Mat, y, x));
highConfidencePointCnt++;
}
}
}
<3> 提取關鍵點
提取關鍵點用的方法是通過第<2>步中找到的圖像邊沿,找到每個邊沿中置信度最高的點:
typedef struct __NNIE_OPENPOSE_KEY_POINT_RESULT__{
HI_BOOL isResultUpdating; //是否正在檢測中
HI_BOOL isPointDetected; //是否檢測到人體關鍵點
HI_U8 pointNum[25];
POINT_S raw_point[25][20]; //25個人體關鍵點,每張圖每個人體關鍵點假設最大20個
}OPENPOSE_BODYKEYPOINT_RESULT_s;
static OPENPOSE_BODYKEYPOINT_RESULT_s openpose_keypoint_result;
openpose_keypoint_result.raw_point[i]是第i類人體關鍵點在46*46的置信圖中的座標位置;
i對應的順序:鼻子,脖子,右肩膀,右肘,右手腕......:
const HI_U8* openpose_body_keypoint_str[25]= {
"Nose", "Neck", "R-Sho", "R-Elb", "R-Wr", "L-Sho", "L-Elb", "L-Wr",
"R-Hip", "R-Knee", "R-Ank", "L-Hip", "L-Knee", "L-Ank", "R-Eye",
"L-Eye", "R-Ear", "L-Ear", "LBigToe", "LSmallToe", "LHeel", "RBigToe",
"RSmallToe", "RHeel", "Background",
};
提取過程:
//提取關鍵點
openpose_keypoint_result.pointNum[i] = 0;
for(; pContOurs != NULL; pContOurs = pContOurs->h_next){
HI_FLOAT confidenceRate = 0, confidenceRateMax = 0;
//查找某個邊沿內所有點中概率最大點
for(x = 0; x < pContOurs->total; x++){
CvPoint *pt = (CvPoint*)cvGetSeqElem(pContOurs, x);
confidenceRate = cvmGet(pFloat32Mat, pt->y, pt->x); //置信度
//找出位於同一邊緣的最大置信度點
if(confidenceRate > confidenceRateMax){
openpose_keypoint_result.raw_point[i][openpose_keypoint_result.pointNum[i]].s32X = pt->x;
openpose_keypoint_result.raw_point[i][openpose_keypoint_result.pointNum[i]].s32Y = pt->y;
confidenceRateMax = confidenceRate;
}
}
OursNum++;
openpose_keypoint_result.pointNum[i]++;
}
<4> 座標結算的問題
從46*46的置信圖獲取到人體關鍵點的位置後,需要顯示出來,但是我用的HISI的VO是1080P的,分辨率1920*1080
因此,若是某個點的位置移動了1個像素,顯示上會跳動相當大的位置,出現顯示檢測點不斷跳動的問題:
綠色是推理的鼻子位置,藍色是脖子,紅色是雙肩,紫色是雙肘,視頻懶得傳,GIF勉強看下座標跳動的問題:
解決這個問題的辦法應該有一些,大概可能用cvDrawContours繪製某個邊沿,再用cvMoments找到形心可以?
因爲是新手,也是基本靠猜靠試。
我懶得去試了,因爲我認爲目前這已經不是主要矛盾,數據對就可以。
還是前面說的那個思路:先打通流程,細節慢慢完善。同樣的,越到後面,細節的決定作用越重要且越難處理;速度,流程,都是還有很多可以優化的。但他的前提是到了後面的階段。
目前爲止,移植openpose已經可以提取到關鍵點。
由於實際的檢測圖片是368*368,輸出是46*46,因此放大到顯示時,顯示的檢測位置會有較明顯跳動
檢測單人且單人位於圖像主要位置時,效果還不錯(其實做這個的初衷已經得到部分實現)
檢測多人時,則效果不理想
除了關鍵點之外,openpose的輸出還包括人體親和度矩陣PAFs
接下來的博客先嚐試解析並顯示
事情總有一個過程,遇到問題想辦法解決問題,事物發展總是螺旋上升的
不因暫時的困頓而苦惱,不因一時的得手而自喜
流水不爭先,爭的是滔滔不絕