[HI3516DV300開發筆記]HiSi NNIE + opencv解算openpose人體關鍵點輸出


本章博客上接:

《[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

接下來的博客先嚐試解析並顯示


事情總有一個過程,遇到問題想辦法解決問題,事物發展總是螺旋上升的

不因暫時的困頓而苦惱,不因一時的得手而自喜

流水不爭先,爭的是滔滔不絕


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章