查看: 721|回复: 0

这个秋天,OpenCV和MCU更配哟(进阶篇)

[复制链接]
  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3298

    主题

    6545

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32004
    最后登录
    2024-4-9
    发表于 2022-11-30 13:27:14 | 显示全部楼层 |阅读模式
    这个秋天,OpenCV和MCU更配哟(进阶篇)
    上一期小编带着大家简单过了一下OpenCV的API的基本使用方法,并最终在MCU上实际跑了下。相信大家看的一定不是很过瘾,本期小编将和大家一起完全从0开始构建一个基于MCUXPresso的OpenCV测试工程,并部署到MCU上。

    提到这儿,可能有朋友会提出质疑:上期不是说过了,基于HelloWorld例程,把那5个可爱的库一股脑放进去就可以了吗?

    可能事情并不是这么简单哟!不然,小编可就有凑字+凑篇幅的嫌疑了啊。还请听小编娓娓道来。

    我们知道OpenCV是基于C++编写的项目,而我们所常用的Hello_World例程实际上是一个C工程。这样一来,原生的Hello_World的C工程是无法兼容,所编译出的OpenCV库工程的,或者说,工程本身不能支持构建C++工程。

    因此,在开始部署之前,要针对性地对工程本身进行一些小小的改造,以添加对于C++的支持。

    具体步骤如下:

    ㊀ 所谓站在巨人的肩膀看得远,先做一些准备工作:
    a.SDK代码包:2.11.0 for i.MX RT1170
    b.CUXPresso IDE:11.5.0
    c.选取参考例程:SDK_root\boards\evkmimxrt1170\demo_apps\hello_world_demo_cm7
    d.准备好5个静态库:libopencv_world, libopenjp2, libjpeg-turbo, libpng, zlib

    ㊁ 通过Quickstart Panel导入hello world例程:
    11.png
    ㊂ 犀利的操作:添加C++支持,找到工程目录下的.project文件,并添加:
    12.png
    修改好之后,使用MCUXPresso IDE重新打开工程即可开启C++属性。

    ㊃ 此时的C++工程属性还是空的,首先是针对MCU C++ Compiler的配置,包括头文件以及预编译符号的添加:
    13.png
    头文件路径如下:
    1. "${workspace_loc:/${ProjName}/drivers}"
    2. "${workspace_loc:/${ProjName}/board}"
    3. "${workspace_loc:/${ProjName}/source}"
    4. "${workspace_loc:/${ProjName}/utilities}"
    5. "${workspace_loc:/${ProjName}/drivers}"
    6. "${workspace_loc:/${ProjName}/device}"
    7. "${workspace_loc:/${ProjName}/component/uart}"
    8. "${workspace_loc:/${ProjName}/component/lists}"
    9. "${workspace_loc:/${ProjName}/startup}"
    10. "${workspace_loc:/${ProjName}/xip}"
    11. "${workspace_loc:/${ProjName}/CMSIS}"
    12. "${workspace_loc:/${ProjName}/utilities}"
    13. "${workspace_loc:/${ProjName}/device}"
    14. "your_cv_path\opencv\build"
    15. " your_cv_path \opencv\include"
    16. " your_cv_path \opencv\modules\core\include"
    17. " your_cv_path \opencv\modules\imgcodecs\include"
    18. " your_cv_path \opencv\modules\imgproc\include"
    19. " your_cv_path \opencv\modules\world\include"
    20. " your_cv_path \opencv\modules\highgui\include"
    21. " your_cv_path \opencv\modules\features2d\include"
    22. " your_cv_path \opencv\modules\ml\include"
    23. " your_cv_path \opencv\modules\video\include"
    复制代码
    预编译符号:
    1. OPENCV_DISABLE_THREAD_SUPPORT=1
    2. __NEWLIB__
    3. CPU_MIMXRT1176DVMAA
    4. CPU_MIMXRT1176DVMAA_cm7
    5. XIP_BOOT_HEADER_DCD_ENABLE=1
    6. USE_SDRAM
    7. DATA_SECTION_IS_CACHEABLE=1
    8. SDK_DEBUGCONSOLE=1
    9. XIP_EXTERNAL_FLASH=1
    10. XIP_BOOT_HEADER_ENABLE=1
    11. PRINTF_FLOAT_ENABLE=0
    12. SCANF_FLOAT_ENABLE=0
    13. PRINTF_ADVANCED_ENABLE=0
    14. SCANF_ADVANCED_ENABLE=0
    15. FSL_SDK_DRIVER_QUICK_ACCESS_ENABLE=1
    16. MCUXPRESSO_SDK
    17. CR_INTEGER_PRINTF
    18. __MCUXPRESSO
    19. __USE_CMSIS
    20. DEBUG
    复制代码
    接下来是MCU C++ Linker的配置,包括所引用的库名字以及库搜索路径:
    14.png
    要注意,库的搜索路径就是存放上面那5个库的位置。

    ㊄ 开启C++编程模式,问:C文件切换成C++文件需要几步?答:只需一步!重命名hello_world.c->hello_world.cc即可。内容可以保存不变。

    ㊅ 导入测试数据,包括压缩格式(jpeg,PNG)或是其他未经压缩的原始数据。

    考虑到MCU平台一般没有片上的文件系统,或者说没有集成文件系统。那么我们的测试数据就要以RO data的形式直接集成到镜像中。

    为了实现这一需求,介绍给大家一个很好用的汇编指令:.incbin,顾名思义,指令本身就好像在隐隐地告诉我们,我就是用来include bin文件的,快点用我。

    既然是汇编指令,就要新建一个汇编文件到我们的工程中。新建汇编文件放到哪里,没有特殊要求,但是最好放到和hello_world.cc文件同一级目录下:
    15.png

    添枝加叶:
    1. .global img_start
    2. .global img_end

    3. img_start:
    4. .incbin "data/lena.jpg"
    5. img_end:
    复制代码
    具体测试图片的名字可以任意指定,只不过要注意一点。如果想要使用相对路径的话,要保证存储图片的位置要和hello_world.cc的位置一致。

    ㊆ 至此,MCUXPresso工程就准备完毕了,下一步就是编写测试代码。
    要注意,因为我们已经切换到了C++编程模式,就要顺着C++的脾气来。

    比较重要的一条是:如果不想重命名hello_world.c,说:我就看.c尾缀舒服。没问题,但是请不要忘了,在声明函数的时候,不要忘了用extern “C”来修饰。否则,会有千千万万个link error向你扑面而来。

    接下来开始正式编写测试代码:

    ㊀ 包含头文件,你只需要一行即可,如此和谐友善:

    #include "opencv2\opencv.hpp"
    ㊁ OpenCV使用cv::Mat来表征数据,首先我们要声明并初始化cv::Mat实例。

    考虑到没有片上文件系统的支持,上文也提到直接使用.incbin导入图片数据。
    这里,我们就可以使用在汇编文件中所定义的符号对这些数据进行访问。如果是压缩后的图片,需要首先进行解码操作;如果是源数据的话,可以直接使用:
    1. extern uint8_t img_start[];
    2. extern uint8_t img_end[];
    3. #define IMG_LEN (img_end - img_start)
    4. // compressed data
    5. std::vector<char> data(img_start, img_start + IMG_LEN);
    6. cv::Mat img = cv::imdecode(cv::Mat(data), IMREAD_UNCHANGED);
    7. // raw data, need to aware the shape, and also the depth, such as rgb == CV_8UC3, equal to
    8. // each pixel has 3 items, and each item is 8bits
    9. Mat img(Size(480, 360), CV_8UC3);
    10. memcpy(img.data, img_start, IMG_LEN);
    复制代码
    ㊂ 寻找物体轮廓并画出:
    1. vector<vector<Point>> contours;
    2. vector<Vec4i> hierarchy;
    3. findContours(dst, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    4. // To display the contours
    5. Mat resultImage = Mat ::zeros(dst.size(),CV_8U);
    6. drawContours(resultImage, contours, -1, Scalar(255, 0, 255));
    复制代码
    ㊃ 接下来是一个更加复杂的任务,寻找矩形:
    16.png
    参考代码如下:
    1. // returns sequence of squares detected on the image.
    2. static void findSquares( const Mat& image, vector<vector<Point> >& squares )
    3. {
    4.     squares.clear();
    5.     Mat pyr, timg, gray0(image.size(), CV_8U), gray;
    6.     // down-scale and upscale the image to filter out the noise
    7.     pyrDown(image, pyr, Size(image.cols/2, image.rows/2));
    8.     pyrUp(pyr, timg, image.size());
    9.     vector<vector<Point> > contours;
    10.     for( int c = 0; c < 3; c++ )
    11.     {
    12.         int ch[] = {c, 0};
    13.         mixChannels(&timg, 1, &gray0, 1, ch, 1);
    14.         // try several threshold levels
    15.         for( int l = 0; l < N; l++ )
    16.         {
    17.             if( l == 0 )
    18.             {
    19.                 Canny(gray0, gray, 0, thresh, 5);
    20.                 dilate(gray, gray, Mat(), Point(-1,-1));
    21.             }
    22.             else
    23.             {
    24.                 gray = gray0 >= (l+1)*255/N;
    25.             }
    26.             findContours(gray, contours, RETR_LIST, CHAIN_APPROX_SIMPLE);
    27.             vector<Point> approx;

    28.             // test each contour
    29.             for( size_t i = 0; i < contours.size(); i++ )
    30.             {
    31.                 // approximate contour with accuracy proportional to the contour perimeter
    32.                 approxPolyDP(contours[i], approx, arcLength(contours[i], true)*0.02, true);
    33.                 // square contours should have 4 vertices after approximation
    34.                 if( approx.size() == 4 &&
    35.                     fabs(contourArea(approx)) > 1000 &&
    36.                     isContourConvex(approx) )
    37.                 {
    38.                     double maxCosine = 0;

    39.                     for( int j = 2; j < 5; j++ )
    40.                     {
    41.                         // find the maximum cosine of the angle between joint edges
    42.                         double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
    43.                         maxCosine = MAX(maxCosine, cosine);
    44.                     }
    45.                     if( maxCosine < 0.3 )
    46.                         squares.push_back(approx);
    47.                 }
    48.             }
    49.         }
    50.     }
    51. }
    复制代码
    ㊄ 编码图像,这里我们选择利用调试器将编码后的数据从内存download到我们的PC上。关于如何在MCUXPress中进行数据保存的操作,在上一篇文章中已有介绍。
    1. std::vector<uchar> decoded_img;
    2. cv::imencode(".jpeg", img, decoded_img);
    3. uchar *data = decoded_img.data();
    复制代码
    不过,这里有个小坑要提醒给大家,在传递编码格式时候,请不要忘记那个人见人爱的句点“.” 。也就是说,编码格式要写成.jpeg而不是jpeg。小编可是在这上面吃过亏的。

    至此,本期小编就给大家介绍了如何从0开始新建一个MCUXPresso工程,并编写OpenCV测试代码进行测试。感兴趣的小伙伴们快动起手来吧!







    签到签到
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-4-20 19:50 , Processed in 0.123295 second(s), 22 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表