昨天剛解決完OpenCV Manager的問題,今天就馬不停蹄的開始寫人臉追蹤仙畦。結(jié)合《深入OpenCV Android應(yīng)用開發(fā)》這本書加上官方給出的demo输涕,成功寫出了第一版的人臉追蹤效果(這里就不貼具體的代碼的,大家可以根據(jù)官方的demo——face-detection去試著實(shí)現(xiàn))慨畸,雖然能實(shí)現(xiàn)追蹤的效果莱坎,但是此時(shí)的頁面只有在設(shè)置橫屏?xí)r才是正常的,并且使用時(shí)需要把手機(jī)橫過來才有效果寸士,這和大家平時(shí)的使用習(xí)慣顯然是不符的檐什。于是我開始試著讓他能豎向顯示以及識別......
1. 首先試著把輸出的圖像豎起來
首先我們把頁面的android:screenOrientation="landscape"從水平改成豎向,此時(shí)的效果是顯示的內(nèi)容向左旋轉(zhuǎn)90°弱卡,然后顯示的區(qū)域是一個(gè)正方形乃正。接著我們開始試著讓圖像豎起來——
如果大家按照demo試著實(shí)現(xiàn)的話想必會讓頁面實(shí)現(xiàn)CameraBridgeViewBase.CvCameraViewListener2這個(gè)接口,此接口中需要實(shí)現(xiàn)
public Mat onCameraFrame(CvCameraViewFrame inputFrame);
這個(gè)方法婶博。根據(jù)其描述以及自己的猜測瓮具,我們可以知道這個(gè)方法是在攝像頭傳幀時(shí)被調(diào)用到。我們可以在這個(gè)方法中去處理所獲得到的幀凡人,尋找人臉的操作自然也就在這里進(jìn)行了名党。所以我們這尋找人臉的操作之前,先要將圖像旋轉(zhuǎn)回來挠轴。
Mat gray = inputFrame.gray();
rgb = inputFrame.rgba();
// 旋轉(zhuǎn)輸入幀
if (isFrontCamera) {
Core.rotate(rgb, rgb, Core.ROTATE_90_COUNTERCLOCKWISE);
Core.rotate(gray, gray, Core.ROTATE_90_COUNTERCLOCKWISE);
Core.flip(rgb, rgb, 1);
Core.flip(gray, gray, 1);
} else {
Core.rotate(rgb, rgb, Core.ROTATE_90_CLOCKWISE);
Core.rotate(gray, gray, Core.ROTATE_90_CLOCKWISE);
}
這里的rgb既是我們得到的彩色圖像传睹,gray既是灰度化之后的圖像。這里我們根據(jù)是否是前置攝像頭忠荞,對幀所對應(yīng)的矩陣進(jìn)行旋轉(zhuǎn)蒋歌,其中后置攝像頭需要將矩陣順時(shí)針旋轉(zhuǎn)90°得到豎向的圖像帅掘,而前置攝像頭則需要逆時(shí)針旋轉(zhuǎn)90°,同時(shí)進(jìn)行一次鏡像翻轉(zhuǎn)才能得到想要的圖像堂油。
PS:由于矩陣的乘法不滿足交換律修档,所以此處前置攝像頭的旋轉(zhuǎn)與翻轉(zhuǎn)順序不可更改!府框!對矩陣不太了解的小伙伴可以參考此處吱窝。
至此我們已經(jīng)將圖像豎起來了,同時(shí)也能對人臉進(jìn)行追蹤了迫靖,但是此時(shí)顯示的圖像依舊是一個(gè)以手機(jī)寬為邊長的正方形院峡。
2. 將預(yù)覽視圖擴(kuò)大到正常比例
經(jīng)過一番尋根問底,我發(fā)現(xiàn)決定幀大小的代碼是JavaCameraView里initializeCamera中的這一行
/* Select the size that fits surface considering maximum size allowed */
Size frameSize = calculateCameraFrameSize(sizes, new JavaCameraSizeAccessor(), width, height);
接著我們看這里計(jì)算幀大小的方法
protected Size calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
int calcWidth = 0;
int calcHeight = 0;
int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth) ? mMaxWidth : surfaceWidth;
int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight) ? mMaxHeight : surfaceHeight;
for (Object size : supportedSizes) {
int width = accessor.getWidth(size);
int height = accessor.getHeight(size);
if (width <= maxAllowedWidth && height <= maxAllowedHeight) {
if (height >= calcHeight && width >= calcWidth) {
calcWidth = (int) width;
calcHeight = (int) height;
}
}
}
return new Size(calcWidth, calcHeight);
}
這段代碼首先是計(jì)算了允許的最大寬高系宜,接著遍歷當(dāng)前設(shè)備支持的所有的攝像頭預(yù)覽尺寸照激,在這里的需要注意的是,得到的這些分辨率是類似這樣的:
1920x1080 1280x720 800x480 768x432 720x480 640x480 576x432 480x320
也就是說之所以會出現(xiàn)正方形的預(yù)覽情況盹牧,是因?yàn)閷?shí)際視圖想要的大小(例如1080寬 1920高)與設(shè)備實(shí)際得到的設(shè)備支持的預(yù)覽尺寸的寬高比是不符的俩垃,這也就導(dǎo)致了這里的方法最后得到的尺寸寬高比變成了1:1(也就是主流設(shè)備上的1080x1080)。
因此我們?yōu)榱俗屗衔覀兊膶?shí)際需求汰寓,將此處比較最大值的條件改成
height <= maxAllowedWidth && width <= maxAllowedHeight
這樣最后得到的分辨率既是實(shí)際想要的最大縱向?qū)捀弑?當(dāng)然這里最后得到的數(shù)值依舊是寬>高的口柳,因?yàn)樵趇nitializeCamera的后續(xù)方法里依舊需要將這個(gè)尺寸作為參數(shù)設(shè)置給Camera)。
至此如果運(yùn)行項(xiàng)目會發(fā)現(xiàn)程序會死在CameraBridgeViewBase中deliverAndDrawFrame方法的這一行
Utils.matToBitmap(modified, mCacheBitmap);
這行代碼最后通過jni調(diào)用了OpenCV底層的方法有滑,根據(jù)Log日志反饋的異常跃闹,我們查詢源碼,發(fā)現(xiàn)此處是一個(gè)斷言異常毛好,錯(cuò)誤斷言了此處的矩陣與Bitmap大小一致望艺。我們再來看看此處的Bitmap從何而來莹桅,
protected void AllocateCache() {
mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
}
我們可以看到此處的Bitmap是提前就創(chuàng)建好的灾杰,調(diào)用AllocateCache()此方法的地方正是initializeCamera(),也就是說此處Bitmap的大小是在初始化相機(jī)的方法中傳進(jìn)去的睁本,既他的寬是大于高的场靴。而此處的modified矩陣就是我們在上面第一步中旋轉(zhuǎn)好的幀所轉(zhuǎn)化而來的啡莉,也就是說他是一個(gè)縱向的圖像,如圖所示:
而Utils.matToBitmap(modified, mCacheBitmap)這個(gè)方法的作用正是把矩陣轉(zhuǎn)化為Bitmap旨剥。
至此相信很多小伙伴就已經(jīng)知道該怎么改了——
我們只需要將Bitmap的初始化方法中的寬高對換即可讓其與矩陣不再沖突
mCacheBitmap = Bitmap.createBitmap(mFrameHeight, mFrameWidth, Bitmap.Config.ARGB_8888);
Ok咧欣,這次我們跑起來就能看到豎向的并且是最大比例的預(yù)覽圖了。
由于筆者是一名初學(xué)者轨帜,若文章或代碼里有不合理的地方魄咕,還請各位大佬提出來并給些意見。