0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

MCU部署OpenCV的“进阶篇”

恩智浦MCU加油站 来源:恩智浦MCU加油站 作者:恩智浦MCU加油站 2022-11-24 11:16 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

本文是一个小系列的第四篇,MCU部署OpenCV的“进阶篇”,已经发表了“先跑篇”、"配置篇"和“实战篇”稍后会陆续还有“优化篇”,带您牵手OpenCV,进入OpenCV的广阔世界。

上一期小编带着大家简单过了一下OpenCV的API的基本使用方法,并最终在MCU上实际跑了下。相信大家看的一定不是很过瘾,本期小编将和大家一起完全从0开始构建一个基于MCUXPresso的OpenCV测试工程,并部署到MCU上。

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

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

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

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

具体步骤如下:

所谓站在巨人的肩膀看得远,先做一些准备工作:

  1. SDK代码包:2.11.0 for i.MX RT1170

  2. MCUXPresso IDE:11.5.0

  3. 选取参考例程:SDK_rootoardsevkmimxrt1170demo_appshello_world_demo_cm7

  4. 准备好5个静态库:libopencv_world, libopenjp2, libjpeg-turbo, libpng, zlib

通过Quickstart Panel导入hello world例程:

034ad6c2-6b91-11ed-8abf-dac502259ad0.png

犀利的操作:添加C++支持,找到工程目录下的.project文件,并添加:

038687c6-6b91-11ed-8abf-dac502259ad0.png

修改好之后,使用MCUXPresso IDE重新打开工程即可开启C++属性。

此时的C++工程属性还是空的,首先是针对MCU C++ Compiler的配置,包括头文件以及预编译符号的添加:

03d29be8-6b91-11ed-8abf-dac502259ad0.png

头文件路径如下:

"${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的配置,包括所引用的库名字以及库搜索路径:

03fa2000-6b91-11ed-8abf-dac502259ad0.png

要注意,库的搜索路径就是存放上面那5个库的位置。

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

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

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

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

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

041fad0c-6b91-11ed-8abf-dac502259ad0.png

添枝加叶:

.global img_start
.global img_end

img_start:
 .incbin "data/lena.jpg"
img_end:

具体测试图片的名字可以任意指定,只不过要注意一点。如果想要使用相对路径的话,要保证存储图片的位置要和hello_world.cc的位置一致。

至此,MCUXPresso工程就准备完毕了,下一步就是编写测试代码。

要注意,因为我们已经切换到了C++编程模式,就要顺着C++的脾气来。

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

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

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

#include "opencv2opencv.hpp"

OpenCV使用cv::Mat来表征数据,首先我们要声明并初始化cv::Mat实例。

考虑到没有片上文件系统的支持,上文也提到直接使用.incbin导入图片数据。这里,我们就可以使在汇编文件中所定义的符号对这些数据进行访问。如果是压缩后的图片,需要首先进行解码操作;如果是源数据的话,可以直接使用:
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);
                }
            }
        }
    }
}

编码图像,这里我们选择利用调试器将编码后的数据从内存download到我们的PC上。关于如何在MCUXPress中进行数据保存的操作,在上一篇文章中已有介绍。

std::vector decoded_img;
cv::imencode(".jpeg", img, decoded_img);
uchar *data = decoded_img.data();

不过,这里有个小坑要提醒给大家,在传递编码格式时候,请不要忘记那个人见人爱的句点“.” 。也就是说,编码格式要写成.jpeg而不是jpeg。小编可是在这上面吃过亏的。

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

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • mcu
    mcu
    +关注

    关注

    147

    文章

    19240

    浏览量

    405201
  • C++
    C++
    +关注

    关注

    22

    文章

    2131

    浏览量

    77496
  • OpenCV
    +关注

    关注

    33

    文章

    652

    浏览量

    45177

原文标题:这个秋天,OpenCV和MCU更配哟(进阶篇)

文章出处:【微信号:NXP_SMART_HARDWARE,微信公众号:恩智浦MCU加油站】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    人工智能-Python深度学习进阶与应用技术:工程师高培解读

    深度学习的工程化落地,早已不是纸上谈兵的事。从卷积神经网络到Transformer,从目标检测到大模型私有化部署,技术栈不断延伸,工程师面临的知识体系也越来越庞杂。现根据中际赛威工程师培训老师的一份
    的头像 发表于 04-21 11:01 472次阅读
    人工智能-Python深度学习<b class='flag-5'>进阶</b>与应用技术:工程师高培解读

    【幸狐Omni3576边缘计算套件试用体验】YOLO26 板端部署

    【幸狐Omni3576边缘计算套件试用体验】YOLO26 板端部署 本文介绍了幸狐 Omni3576 开发板结合 OpenCV 和 Ultralytics 库实现 YOLO26 板端部署,并实现目标
    发表于 04-19 22:02

    RA MCU众测宝典 | 在RA MCU上快速部署LWIP——以CPKCOR-RA8D1为例

    makeWorks在上一文章中我们分享了在RA上快速部署LVGL的案例,但是FSP丰富强大的功能还远不止于此,除了LVGL,LWIP也早已被FSP纳入自身的功能中间件中,可以在FSP上进行快捷
    的头像 发表于 12-24 12:06 5797次阅读
    RA <b class='flag-5'>MCU</b>众测宝典 | 在RA <b class='flag-5'>MCU</b>上快速<b class='flag-5'>部署</b>LWIP——以CPKCOR-RA8D1为例

    没有专利的opencv-python 版本

    所有 官方发布的 opencv-python 核心版本(无 contrib 扩展)都无专利风险——专利问题仅存在于 opencv-contrib-python 扩展模块中的少数算法(如早期 SIFT
    发表于 12-13 12:37

    ETAS INCA软件的五个实用进阶功能

    在上一文章中我们介绍了INCA软件如何赋能高效的ECU开发及新能源挑战,本篇内容将继续深入探讨INCA的五个实用进阶功能。
    的头像 发表于 12-08 16:42 2877次阅读
    ETAS INCA软件的五个实用<b class='flag-5'>进阶</b>功能

    【离线语音】安信可VC-01/02二次开发:自定义音频播放控制

    教程 【快速上手】 安信可离线语音模组 VC-01、VC-02 系列教程 【中级入门】 安信可离线语音模组 VC-01、VC-02 系列教程 【高级进阶篇】 安信可离线语音模组 VC-01、VC-02
    的头像 发表于 09-25 16:34 1064次阅读
    【离线语音】安信可VC-01/02二次开发<b class='flag-5'>篇</b>:自定义音频播放控制

    安信可VC-01/02二次开发: PWM输出

    教程 【快速上手】 安信可离线语音模组 VC-01、VC-02 系列教程 【中级入门】 安信可离线语音模组 VC-01、VC-02 系列教程 【高级进阶篇】 安信可离线语音模组 VC-01、VC-02
    的头像 发表于 08-27 16:06 1245次阅读
    安信可VC-01/02二次开发<b class='flag-5'>篇</b>: PWM输出

    安信可VC-01/02二次开发: 事件和GPIO控制

    教程 【快速上手】 安信可离线语音模组 VC-01、VC-02 系列教程 【中级入门】 安信可离线语音模组 VC-01、VC-02 系列教程 【高级进阶篇】 安信可离线语音模组 VC-01、VC-02
    的头像 发表于 08-19 14:02 1507次阅读
    安信可VC-01/02二次开发<b class='flag-5'>篇</b>: 事件和GPIO控制

    零成本钢铁侠手套!树莓派+OpenCV 秒变手势遥控器!

    大家好,这是一个树莓派和OpenCV的连载专题。使用树莓派与OpenCV实现姿态估计和面部特征点追踪使用树莓派与OpenCV实现面部和运动追踪的云台系统使用树莓派和OpenCV实现手部
    的头像 发表于 08-16 16:16 1705次阅读
    零成本钢铁侠手套!树莓派+<b class='flag-5'>OpenCV</b> 秒变手势遥控器!

    如何使用树莓派与OpenCV实现面部和运动追踪的云台系统?

    大家好,这是一个树莓派和OpenCV的连载专题。使用树莓派与OpenCV实现姿态估计和面部特征点追踪使用树莓派与OpenCV实现面部和运动追踪的云台系统使用树莓派和OpenCV实现手部
    的头像 发表于 08-14 17:45 2765次阅读
    如何使用树莓派与<b class='flag-5'>OpenCV</b>实现面部和运动追踪的云台系统?

    如何使用树莓派+OpenCV实现姿态估计和面部特征点追踪?

    大家好,这是一个树莓派和OpenCV的连载专题。使用树莓派与OpenCV实现姿态估计和面部特征点追踪使用树莓派与OpenCV实现面部和运动追踪的云台系统使用树莓派和OpenCV实现手部
    的头像 发表于 08-13 17:44 1576次阅读
    如何使用树莓派+<b class='flag-5'>OpenCV</b>实现姿态估计和面部特征点追踪?

    如何板端编译OpenCV并搭建应用--基于瑞芯微米尔RK3576开发板

    本文将介绍基于米尔电子MYD-LR3576开发板(米尔基于瑞芯微 RK3576开发板)的板端编译OpenCV及环境搭建方案的开发测试。摘自优秀创作者-短笛君 RK3576具有如下配置: 4
    发表于 08-08 17:14

    【离线语音】安信可VC-01/02教程:中级入门

    教程 【快速上手】 安信可离线语音模组 VC-01、VC-02 系列教程 【中级入门】 安信可离线语音模组 VC-01、VC-02 系列教程 【高级进阶篇】 安信可离线语音模组 VC-01、VC-02
    的头像 发表于 07-31 09:33 1060次阅读
    【离线语音】安信可VC-01/02教程:中级入门<b class='flag-5'>篇</b>

    【Milk-V Duo S 开发板免费体验】SDK编译、人脸检测、OpenCV测试

    【Milk-V Duo S 开发板免费体验】SDK编译、人脸检测、OpenCV测试 本文介绍了 Milk-V Duo S 开发板实现 Buildroot SDK 镜像编译、基于 TDL 模型的人
    发表于 07-11 13:48

    #工作原理大揭秘 #单片机 #电路原理 #MCU#mcu程序开发

    mcu
    宇凡微电子
    发布于 :2025年06月19日 19:02:28