ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

嵌入式Linux与Qt的视觉检测系统开发:从V4L2采集到部署验证

嵌入式Linux与Qt的视觉检测系统开发:从V4L2采集到部署验证 简介这是一套以嵌入式Linux与Qt为技术栈的工业视觉检测系统课程设计/毕业设计项目面向具备C基础、需要完成实战课题的高校学生与开发者。项目围绕C与Qt图形界面编写覆盖登录窗口、主窗口、MD5加密、界面布局等常见模块整体难度适中适合作为理解嵌入式GUI开发与工业检测流程的入门级完整案例。压缩包共24个文件核心包括7个cpp与7个h源码文件配合3个ui界面文件、2个pri模块配置和pro工程文件可直接还原Qt工程结构png图片用于界面视觉素材README和gitignore分别提供说明与版本控制支持包体仅34KB轻量而完整。目前已有52人学习浏览源码均经过本地编译验证按照配套文档配置环境即可运行项目由助教审定结构清晰既可用于课程设计或毕业设计参考也能帮助读者梳理Qt数据库SQL在嵌入式系统中的应用思路。1. 为什么这类产线工位上选择嵌入式Linux加Qt做视觉检测一条小批量多型号产线要增加划痕检测工控机占地又贵树莓派又扛不住工业现场于是ARM板、相机加触摸屏的组合成了常见方案。但嵌入式视觉检测比桌面端麻烦得多采集和检测算法要共享有限内存界面要在触摸屏上流畅交互还要能一键部署到产线。这个标题里的核心分工就是——嵌入式Linux负责设备驱动、内存和进程调度Qt负责界面流程与控制逻辑。适合谁做上位机视觉想接手嵌入式项目的以及刚开始搭嵌入式Linux项目的工程师都能在里面找到从交叉编译到部署验证的完整路径。2. 平台选型与嵌入式Linux镜像裁剪先定底座再写Qt代码2.1 从检测需求反推硬件参数工业视觉检测的算力消耗主要在图像处理而不是界面显示。以500万像素Bayer图为例一帧约10MB若检测节拍要求200ms一帧那么单帧内要完成缓存、预处理、定位和判定CPU主频不够时最先暴露的是“掉帧”。所以先列需求表分辨率、节拍、算法类型阈值/轮廓/模板匹配/推理、相机接口再选SoC比先看芯片手册再迁就需求更靠谱。常见平台我一般这样划分只做OK/NG和有固定工位的小目标检测i.MX6ULL级别的双核A7足够带大图测量或并行多路就上四核A53并尽可能用硬件编解码有深度学习模型再考虑带NPU的SoC或加一块USB算力棒的方案。成本相差几倍但真正的成本大头往往是开发工时选型宁高勿低尤其是相机接口带宽这一项内存和主频不够都可以靠算法优化带宽不够只能换硬件。常见方案核心适合场景注意点NXP i.MX6ULLCortex-A7 单双核小图、固定节拍、OK/NG内存建议1GB起别省全志/瑞芯微入门级Cortex-A7/A53中等分辨率测量检查MIPI-CSI驱动成熟度RK3399级别四核A53A72高清图、多工位、带推理散热和eMMC寿命要评估相机选型决定驱动工作量。GigE工业相机一般有厂商提供的Linux SDK接手时的坑是SDK与内核版本不匹配USB3免驱相机走UVC省事但要检查是否支持指定分辨率下的帧率承诺MIPI-CSI直连CMOS成本最低但驱动要自己调。无论哪种应用层都用V4L2来抽象这就意味着rootfs里必须有V4L2的内核模块和对应设备节点。2.2 Buildroot裁剪镜像里只留目标板要用的组件拿到一个新的嵌入式Linux项目我一般用Buildroot而不是Yocto从配置到出镜像路径更短产物体积也更小。首次配置时执行make ARCHarm menuconfig这个命令打开的图形界面里要确认的菜单项集中在三处。第一处是Target packages下的Qt5相关包勾qt5base后记得进去看下面还有qt5declarative、qt5multimedia等可选项界面用不到的一律不勾能省掉几十个动态库。第二处是Kernel下的V4L2、DRM相关驱动建议直接进kernel menuconfig里按“/”搜索CONFIG_VIDEO_V4L2并确认它等于y。第三处是Filesystem images选ext4配合开发调试量产再换squashfs。勾完执行make -j8完成后output/images下会出现zImage和rootfs.ext4。编译Qt库耗时在整套构建里占大头第一次构建建议先只勾BusyBox和内核确认板子能开机后再进来勾Qt增量编译排查问题范围会小很多。调试网络和串口都要提前在systemd或busybox启动脚本里配置好否则镜像烧进去发现板子起不来外部工具又没挂载的时候只能反复拔卡很浪费时间。提示Buildroot的overlay目录可以放开发板专用的配置文件比如/etc/profile、应用开机脚本这样每次重新构建镜像不会覆盖现场配置。2.3 交叉编译Qt为板子准备一套独立SDK这里说的Qt不是开发机上那个msvc版本而是目标板上的裁剪版。有些工程师从Windows转过来习惯把Windows上编译好的exe和dll直接拷贝到开发板这是行不通的。要在Linux主机上重新编译一套针对ARM架构的Qt步骤固定./configure -prefix /opt/qt-arm \ -opensource -confirm-license -release \ -xplatform linux-arm-gnueabi-g \ -no-opengl -linuxfb \ -tslib \ -nomake examples -nomake tests make -j8 make install这里几个参数直接决定后续工程能不能跑-prefix定义了ARM版Qt的安装目录整个工具链、库和头文件会集中到/opt/qt-arm发布时整目录拷贝即可-linuxfb是让Qt把帧缓冲设备作为显示后端不依赖GPU-tslib让触摸输入走tslib校准。编好后/opt/qt-arm/bin里会出现一个目标用的qmake用它构建工程链接出来的程序就是ARM架构的不再依赖开发机上的Qt安装路径。3. 图像采集与检测核心V4L2、OpenCV与数据线程3.1 用V4L2的mmap方式把相机帧读进内存V4L2的采集模式有三种read直接读、mmap内存映射、userptr用户指针。工业检测场景下mmap是默认选择把内核空间缓冲区映射到用户态省去一次拷贝对CPU占用和延迟都友好。核心流程分四步打开设备、设置格式、请求缓冲区、投递并循环取出。int fd open(/dev/video0, O_RDWR | O_NONBLOCK); struct v4l2_format fmt {}; fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.pixelformat V4L2_PIX_FMT_YUYV; // 具体格式看相机 fmt.fmt.pix.width 640; fmt.fmt.pix.height 480; ioctl(fd, VIDIOC_S_FMT, fmt); struct v4l2_requestbuffers req {}; req.count 4; // 4个缓冲区组成环形队列 req.type V4L2_BUF_TYPE_VIDEO_CAPTURE; ioctl(fd, VIDIOC_REQBUFS, req);上面的代码有几个地方要根据相机参数调整pixelformat要与相机输出一致YUYV是USB UVC相机的常见默认格式工业GigE相机如果是纯Bayer输出建议收RAW后在CPU端做demosaic这样后端算法可控性最好缓冲区数量4个是起步值如果检测耗时超过帧间隔加到8个能减小丢帧概率。设置完成后为每个缓冲区做mmap映射然后通过VIDIOC_QBUFS和VIDIOC_DQBUF循环取帧。需要特别注意的是V4L2取出来的帧是按照设定的像素格式排列的不是显卡驱动自动转成RGB。检测算法如果先做颜色校正需要额外知道相机的白平衡和增益参数如果只做灰度形态学检测直接取Y分量缓存即可省去颜色转换的开销。这是嵌入式视觉系统初期最容易漏掉的性能优化点。像素格式位深/排列适用方向备注YUYV16bit/像素通用USB相机提取Y成分做灰度检测很快UYVY16bit/像素GigE相机常用与YUYV顺序不同转RGB注意Bayer RGGB8-12bit/像素CMOS直出需demosaic轮廓检测可用灰度MJPG压缩帧低带宽场景解压耗时不推荐用于精密测量3.2 OpenCV编译选项与Mat到QImage的正确转换嵌入式检测算法大部分还是OpenCV这条线。编译OpenCV时的几个开关直接影响体积关闭GTK支持否则会拉进一整套图形库关闭不需要的模块如videoio但opencv_imgproc、opencv_core必须保留。CMake命令行一般这样写cmake .. -DCMAKE_TOOLCHAIN_FILE../platforms/linux/arm-gnueabi.toolchain.cmake \ -DWITH_GTKOFF -DWITH_QTOFF \ -DWITH_V4LON -DWITH_JPEGON -DBUILD_SHARED_LIBSON这里的-WITH_QTOFF容易被误解它指的是OpenCV内置的Qt显示窗口和你的检测界面程序没有任何关系不关掉编译会失败。-DWITH_V4LON把视频捕捉后端指向V4L2配合V4L2设备节点使用。交叉编译OpenCV一次大约一两个小时保持BUILD_SHARED_LIBS为ON后面裁剪发布包时更灵活。检测算法按标准流程走就好。尺寸测量项目常用“灰度化、高斯模糊、二值化、找轮廓、最小外接矩形”这一串cv::Mat gray, bin; cv::cvtColor(mat, gray, cv::COLOR_BGR2GRAY); cv::GaussianBlur(gray, gray, cv::Size(3, 3), 0); cv::threshold(gray, bin, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); std::vectorstd::vectorcv::Point contours; cv::findContours(bin, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); for (auto c : contours) { cv::Rect r cv::boundingRect(c); if (r.width 50 r.height 50) { // 过滤噪声 // 以像素为单位计算尺寸再换算成实际物理长度 } }参数上的经验值高斯模糊核3x3在嵌入式平台是性价比最高的核越大CPU时间成平方生长OTSU自动阈值在光照稳定的产线上能用但如果有外部光干扰最好固定阈值或改用局部自适应阈值。findContours找到的轮廓要按面积过滤因为反光、飞屑等噪声往往也会形成小轮廓。Mat转QImage是这里最容易写错的地方。Mat的step是按行对齐的QImage的bytesPerLine不一定相等即使图像尺寸一样直接用QImage的data指针构造显示在Qt界面上会出现行错位甚至崩掉。正确写法是构造时传入bytesPerLineQImage img(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_RGB888); QImage rgb img.convertToFormat(QImage::Format_RGB32, Qt::AutoColor);这段代码切入了PhotometricInterpretation/颜色位深的转换先用convertToFormat确保每个像素的数据格式与显示目标一致。之后在emit信号给UI线程时这两个QImage是隐式共享的拷贝成本远低于一张完整位图所以可以做频次较高的刷新而不卡界面。3.3 采集线程到UI线程的协作方式图像采集、检测算法都放在工作线程绝对不要放在UI线程里。UI线程里一个200ms的阻塞界面就像死机一样产线工人第一个会投诉的就是这个。线程之间的协作常见做法是生产者-消费者模型采集线程拿到帧后放入带锁的缓冲队列检测线程从队列取出并执行算法最后把QImage加检测框数据emit到UI线程。下面是一个简单的信号槽骨架class DetectWorker : public QObject { Q_OBJECT public slots: void onFrame(); // 从V4L2队列拿帧并执行检测 signals: void resultReady(QImage img, QRect rect, bool pass); }; // 主线程里连接信号槽 QObject::connect(worker, DetectWorker::resultReady, this, [this](QImage img, QRect r, bool ok){ m_view-setResult(img, r, ok); // 只做界面更新 });这里的几个坑是实打实的。第一跨线程信号槽默认走QueuedConnection传递的QImage必须可以拷贝所以不要在emit之后继续复用Mat的data指针第二如果检测线程和UI线程都要访问同一个共享状态用QMutex保护或者干脆在emit时把需要的数据全部打包成值类型第三帧率不需要等于界面刷新率检测线程完成一帧才emit一次界面自然会显示最新帧不用主动开定时器刷新。4. Qt界面层落地显示后端、检测框绘制与触摸输入4.1 选择EGLFS还是LinuxFB作为显示后端程序启动时Qt会选定一个platform插件这个插件决定它如何与显示硬件通信。常见有三个方向xcb用于运行X Window的桌面Linuxlinuxfb直接写/dev/fb0eglfs经过EGL走GPU或显示控制器。嵌入式无桌面环境里后两者的选择直接关系到帧率和是否能跑动画。没有GPU时用linuxfb是稳定做法缺点是屏幕刷新要走CPU全屏重绘时能看到明显闪烁。有GPU或至少DRM/KMS驱动时优先用eglfs由显示控制器负责合成UI动画和视频播放的流畅度好一个档次。判断板子支持哪种后端最简单的方法是看rootfs里有没有/dev/dri目录存在就说明DRM驱动已经挂载eglfs可试。启动参数通过QT_QPA_PLATFORM环境变量来切换export QT_QPA_PLATFORMeglfs export QT_QPA_EGLFS_INTEGRATIONeglfs_kms ./detect_app两个常见参数要解释一下QT_QPA_EGLFS_INTEGRATION指定eglfs的具体集成方式eglfs_kms对应DRM主设备如果开发板有多个显示接口还要配QT_QPA_EGLFS_KMS_CRTC指定输出到哪一路。这些配置看起来是细节但启动日志里如果出现qt_qpa_platform_plugin_path相关报错往往就是平台插件目录没找到而不是插件本身坏了先看QT_QPA_PLATFORM_PLUGIN_PATH指向的目录是否存在再排查其他原因。4.2 自定义检测画面控件逻辑坐标与设备坐标的映射用QLabel贴一张QImage能快速看到结果但要做叠加画框、放大镜、标尺这些交互就得自定义控件。关键概念是坐标系映射相机的真实像素坐标是一套逻辑坐标系控件在屏幕上显示的区域是设备坐标系两者有缩放比和偏移量。直接拿检测结果里的像素坐标去绘制画出来的框会偏因为图像本身在控件内做了缩放适配。实现一个简单的映射函数QRectF DetectorWidget::mapToWidget(const QRectF pixelRect) const { // m_imgRect 是图像在控件中的实际绘制区域由绘制时根据等比缩放算出 qreal sx (pixelRect.x() - m_imgRect.x()) / m_imgRect.width() * width(); qreal sy (pixelRect.y() - m_imgRect.y()) / m_imgRect.height() * height(); qreal sw pixelRect.width() / m_imgRect.width() * width(); qreal sh pixelRect.height() / m_imgRect.height() * height(); return QRectF(sx, sy, sw, sh); }映射思路先把像素坐标减掉绘图区域的左上角偏移量再按照图像绘制区域与控件尺寸的比例放大得到控件坐标。实际项目里除了缩放还可能遇到旋转和镜像那就需要组合变换矩阵Qt提供了QTransform链式乘上去就行。绘制部分直接在paintEvent里完成void DetectorWidget::paintEvent(QPaintEvent *) { QPainter p(this); p.setRenderHint(QPainter::Antialiasing, false); p.drawImage(m_imageRect, m_currentFrame); if (!m_defectRect.isEmpty()) { QRectF r mapToWidget(m_defectRect); p.setPen(QPen(Qt::green, 2)); p.setBrush(QColor(0, 255, 0, 40)); p.drawRect(r); p.drawText(r.topLeft(), QStringLiteral(NG)); } }代码里p.drawImage把当前帧画到计算好的m_imageRect然后按映射结果画绿色半透明矩形。半透明brush在paintEvent里多次调用会叠加导致颜色越来越深建议每次setBrush时都显式带透明通道的目标值。状态文字用drawText直接画在框上操作员不用看旁边表格数据。4.3 触摸校准、环境变量与多语言界面有触摸屏的板子在Qt里用触摸输入时tslib和evdevtouch两种方式并存。tslib适合电阻屏校准参数存在/etc/pointercal里Qt启动时通过TSLIB环境变量读取电容屏走evdevtouch内核输入的坐标已经是规范化的通常不需要二次校准。产线设备出于耐磨性可能还用电阻屏把tslib放rootfs里比较稳妥。启动脚本里把相关环境变量写全是部署阶段最容易漏的export TSLIB_TSDEVICE/dev/input/event2 export TSLIB_CALIBFILE/etc/pointercal export TSLIB_CONFFILE/etc/ts.conf export QT_QPA_GENERIC_PLUGINStslib:/dev/input/event2其中QT_QPA_GENERIC_PLUGINS告诉Qt把tslib作为通用输入插件加载。触摸方向反了优先检查tslib校准文件而不是改Qt代码滑动手感不好时把QT_QPA_FB_HIDEPOINTER设为1隐藏鼠标光标不少嵌入式界面默认会留一个闪烁光标客户看着很业余。Qt国际化在这类设备上经常被排到很靠后但真正交付时十有八九要支持英文。用tr包住界面字符串后执行lupdate -recursive . -ts lang_zh.ts收集词条在Qt Linguist里翻译最后lrelease生成.qm文件。程序启动时根据当前语言加载.qm中文显示不出来多半是缺少对应字库不是Qt bug需要确认QT_QPA_FONTDIR指向的目录里有中文字体文件。5. 部署验证三板斧库剪裁、启动脚本与环境变量排错5.1 用ldd和cp打造最小发布目录桌面Qt发布可以靠windeployqt一把梭ARM Linux没有对应工具替代方案是ldd加cp脚本。拿到一个编译产物后不能直接把整个rootfs塞给现场只需把应用本身、平台插件、必要的Qt库和OpenCV等第三方库拷贝到目标板arm-linux-gnueabi-readelf -d detect_app | grep NEEDED根据输出逐个拷贝。Qt的插件目录要带上platforms和imageformats两个子目录platforms里放libqeglfs.so或libqlinuxfb.soimageformats里放libqjpeg.so、libqpng.so。复制完成后在目标板上跑ldconfig -p确认链接器索引刷新。整个过程写成脚本自动做但发布一次后不建议频繁改动Qt库之间有隐式依赖少拷一个往往在启动时才暴露而且报错信息不直接指向缺失库。5.2 启动顺序与排错顺序开机自动启动直接写进systemd服务即可注意在After里加上systemd-modules-load确保显示驱动先加载。如果程序起不来排错顺序是先看QT_QPA_PLATFORM_PLUGIN_PATH指到哪里这条路对不对——这是加载platform plugin时的固定入口再看平台后端是否依赖DRM、input设备节点是否存在最后查字体目录是否能访问。这个顺序能避免乱试。qt_qpa_platform_plugin_path如果还夹着Windows的D盘路径说明这个环境变量是开发机上自动生成的残留覆盖回板子路径是第一件事。再给一个验证技巧临时把后端切回linuxfb如果EGLFS起不来但LinuxFB能起来基本就是DRM或GPU驱动问题和业务代码无关。这样能快速区分是算法代码问题还是平台适配问题生产环境里最后再切回EGLFS避免参数遗漏。本文还有配套的精品资源点击获取
返回列表