本文是一個小系列的第四篇,MCU部署OpenCV的“進階篇”,已經(jīng)發(fā)表了“先跑篇”、"配置篇"和“實戰(zhàn)篇”稍后會陸續(xù)還有“優(yōu)化篇”,帶您牽手OpenCV,進入OpenCV的廣闊世界。
上一期小編帶著大家簡單過了一下OpenCV的API的基本使用方法,并最終在MCU上實際跑了下。相信大家看的一定不是很過癮,本期小編將和大家一起完全從0開始構建一個基于MCUXPresso的OpenCV測試工程,并部署到MCU上。
提到這兒,可能有朋友會提出質(zhì)疑:上期不是說過了,基于HelloWorld例程,把那5個可愛的庫一股腦放進去就可以了嗎?
可能事情并不是這么簡單喲!不然,小編可就有湊字+湊篇幅的嫌疑了啊。還請聽小編娓娓道來。
我們知道OpenCV是基于C++編寫的項目,而我們所常用的Hello_World例程實際上是一個C工程。這樣一來,原生的Hello_World的C工程是無法兼容,所編譯出的OpenCV庫工程的,或者說,工程本身不能支持構建C++工程。
因此,在開始部署之前,要針對性地對工程本身進行一些小小的改造,以添加對于C++的支持。
具體步驟如下:
一 所謂站在巨人的肩膀看得遠,先做一些準備工作:
-
SDK代碼包:2.11.0 for i.MX RT1170
-
MCUXPresso IDE:11.5.0
-
選取參考例程:SDK_rootoardsevkmimxrt1170demo_appshello_world_demo_cm7
-
準備好5個靜態(tài)庫:libopencv_world, libopenjp2, libjpeg-turbo, libpng, zlib
二通過Quickstart Panel導入hello world例程:
三犀利的操作:添加C++支持,找到工程目錄下的.project文件,并添加:
修改好之后,使用MCUXPresso IDE重新打開工程即可開啟C++屬性。
四此時的C++工程屬性還是空的,首先是針對MCU C++ Compiler的配置,包括頭文件以及預編譯符號的添加:
頭文件路徑如下:
"${workspace_loc:/${ProjName}/drivers}"
"${workspace_loc:/${ProjName}/board}"
"${workspace_loc:/${ProjName}/source}"
"${workspace_loc:/${ProjName}/utilities}"
"${workspace_loc:/${ProjName}/drivers}"
"${workspace_loc:/${ProjName}/device}"
"${workspace_loc:/${ProjName}/component/uart}"
"${workspace_loc:/${ProjName}/component/lists}"
"${workspace_loc:/${ProjName}/startup}"
"${workspace_loc:/${ProjName}/xip}"
"${workspace_loc:/${ProjName}/CMSIS}"
"${workspace_loc:/${ProjName}/utilities}"
"${workspace_loc:/${ProjName}/device}"
"your_cv_pathopencvuild"
" your_cv_path opencvinclude"
" your_cv_path opencvmodulescoreinclude"
" your_cv_path opencvmodulesimgcodecsinclude"
" your_cv_path opencvmodulesimgprocinclude"
" your_cv_path opencvmodulesworldinclude"
" your_cv_path opencvmoduleshighguiinclude"
" your_cv_path opencvmodulesfeatures2dinclude"
" your_cv_path opencvmodulesmlinclude"
" your_cv_path opencvmodulesvideoinclude"
預編譯符號:
OPENCV_DISABLE_THREAD_SUPPORT=1
__NEWLIB__
CPU_MIMXRT1176DVMAA
CPU_MIMXRT1176DVMAA_cm7
XIP_BOOT_HEADER_DCD_ENABLE=1
USE_SDRAM
DATA_SECTION_IS_CACHEABLE=1
SDK_DEBUGCONSOLE=1
XIP_EXTERNAL_FLASH=1
XIP_BOOT_HEADER_ENABLE=1
PRINTF_FLOAT_ENABLE=0
SCANF_FLOAT_ENABLE=0
PRINTF_ADVANCED_ENABLE=0
SCANF_ADVANCED_ENABLE=0
FSL_SDK_DRIVER_QUICK_ACCESS_ENABLE=1
MCUXPRESSO_SDK
CR_INTEGER_PRINTF
__MCUXPRESSO
__USE_CMSIS
DEBUG
接下來是MCU C++ Linker的配置,包括所引用的庫名字以及庫搜索路徑:
要注意,庫的搜索路徑就是存放上面那5個庫的位置。
五 開啟C++編程模式,問:C文件切換成C++文件需要幾步?答:只需一步!重命名hello_world.c->hello_world.cc即可。內(nèi)容可以保存不變。
六導入測試數(shù)據(jù),包括壓縮格式(jpeg,PNG)或是其他未經(jīng)壓縮的原始數(shù)據(jù)。
考慮到MCU平臺一般沒有片上的文件系統(tǒng),或者說沒有集成文件系統(tǒng)。那么我們的測試數(shù)據(jù)就要以RO data的形式直接集成到鏡像中。
為了實現(xiàn)這一需求,介紹給大家一個很好用的匯編指令:.incbin,顧名思義,指令本身就好像在隱隱地告訴我們,我就是用來include bin文件的,快點用我。
既然是匯編指令,就要新建一個匯編文件到我們的工程中。新建匯編文件放到哪里,沒有特殊要求,但是最好放到和hello_world.cc文件同一級目錄下:
添枝加葉:
.global img_start
.global img_end
img_start:
.incbin "data/lena.jpg"
img_end:
具體測試圖片的名字可以任意指定,只不過要注意一點。如果想要使用相對路徑的話,要保證存儲圖片的位置要和hello_world.cc的位置一致。
七至此,MCUXPresso工程就準備完畢了,下一步就是編寫測試代碼。要注意,因為我們已經(jīng)切換到了C++編程模式,就要順著C++的脾氣來。
比較重要的一條是:如果不想重命名hello_world.c,說:我就看.c尾綴舒服。沒問題,但是請不要忘了,在聲明函數(shù)的時候,不要忘了用extern “C”來修飾。否則,會有千千萬萬個link error向你撲面而來。
接下來開始正式編寫測試代碼:
一包含頭文件,你只需要一行即可,如此和諧友善:
#include "opencv2opencv.hpp"
二OpenCV使用cv::Mat來表征數(shù)據(jù),首先我們要聲明并初始化cv::Mat實例。
考慮到?jīng)]有片上文件系統(tǒng)的支持,上文也提到直接使用.incbin導入圖片數(shù)據(jù)。這里,我們就可以使用在匯編文件中所定義的符號對這些數(shù)據(jù)進行訪問。如果是壓縮后的圖片,需要首先進行解碼操作;如果是源數(shù)據(jù)的話,可以直接使用:
extern uint8_t img_start[];
extern uint8_t img_end[];
#define IMG_LEN (img_end - img_start)
// compressed data
std::vector data(img_start, img_start + IMG_LEN);
cv::Mat img = cv::Mat(data), IMREAD_UNCHANGED);
// raw data, need to aware the shape, and also the depth, such as rgb == CV_8UC3, equal to
// each pixel has 3 items, and each item is 8bits
Mat img(Size(480, 360), CV_8UC3);
memcpy(img.data, img_start, IMG_LEN);
三尋找物體輪廓并畫出:
vector> contours;
vector hierarchy;
findContours(dst, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// To display the contours
Mat resultImage = Mat ::zeros(dst.size(),CV_8U);
drawContours(resultImage, contours, -1, Scalar(255, 0, 255));
四接下來是一個更加復雜的任務,尋找矩形:
參考代碼如下:
// returns sequence of squares detected on the image.
static void findSquares( const Mat& image, vector >& squares )
{
squares.clear();
Mat pyr, timg, gray0(image.size(), CV_8U), gray;
// down-scale and upscale the image to filter out the noise
pyrDown(image, pyr, Size(image.cols/2, image.rows/2));
pyrUp(pyr, timg, image.size());
vector > contours;
for( int c = 0; c < 3; c++ )
{
int ch[] = {c, 0};
mixChannels(&timg, 1, &gray0, 1, ch, 1);
// try several threshold levels
for( int l = 0; l < N; l++ )
{
if( l == 0 )
{
Canny(gray0, gray, 0, thresh, 5);
dilate(gray, gray, Mat(), Point(-1,-1));
}
else
{
gray = gray0 >= (l+1)*255/N;
}
findContours(gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
vector approx;
// test each contour
for( size_t i = 0; i < contours.size(); i++ )
{
// approximate contour with accuracy proportional to the contour perimeter
approxPolyDP(contours[i], approx, arcLength(contours[i], true)*0.02, true);
// square contours should have 4 vertices after approximation
if( approx.size() == 4 &&
fabs(contourArea(approx)) > 1000 &&
isContourConvex(approx) )
{
double maxCosine = 0;
for( int j = 2; j < 5; j++ )
{
// find the maximum cosine of the angle between joint edges
double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
maxCosine = MAX(maxCosine, cosine);
}
if( maxCosine < 0.3 )
squares.push_back(approx);
}
}
}
}
}
五編碼圖像,這里我們選擇利用調(diào)試器將編碼后的數(shù)據(jù)從內(nèi)存download到我們的PC上。關于如何在MCUXPress中進行數(shù)據(jù)保存的操作,在上一篇文章中已有介紹。
std::vector decoded_img;
cv::imencode(".jpeg", img, decoded_img);
uchar *data = decoded_img.data();
不過,這里有個小坑要提醒給大家,在傳遞編碼格式時候,請不要忘記那個人見人愛的句點“.” 。也就是說,編碼格式要寫成.jpeg而不是jpeg。小編可是在這上面吃過虧的。
至此,本期小編就給大家介紹了如何從0開始新建一個MCUXPresso工程,并編寫OpenCV測試代碼進行測試。感興趣的小伙伴們快動起手來吧!
審核編輯 :李倩
-
mcu
+關注
關注
146文章
17171瀏覽量
351423 -
C++
+關注
關注
22文章
2110瀏覽量
73685 -
OpenCV
+關注
關注
31文章
635瀏覽量
41373
原文標題:這個秋天,OpenCV和MCU更配喲(進階篇)
文章出處:【微信號:NXP_SMART_HARDWARE,微信公眾號:恩智浦MCU加油站】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論