当前位置: 首页 > news >正文

QT opencv实现高拍仪场景识别物体轮廓的案例详解

前言

        opencv库功能非常丰富,本篇仅分享在高拍仪场景下(黑色背景板),实现识别物体轮廓描绘的功能,关键模块会以参数差异+图片方式进行讲解,方便深入理解。

环境安装

window opencv安装及引入qtcreatorhttps://blog.csdn.net/liangyuna8787/article/details/149693144?spm=1001.2014.3001.5502

参考之前分享的博文,包含了添加库以及打包的方法。

案例详细讲解

重要模块一览

函数名称作用详解用途应用场景
imread()读取图像从指定路径加载图像文件读取磁盘中的图像到内存图像处理流程的初始化阶段
cvtColor()颜色空间将图像从一种颜色空间转换为另一种BGR到灰度图转换预处理阶段,简化图像信息
GaussianBlur()图像滤波应用高斯模糊降低图像噪声平滑图像,消除细节干扰边缘检测前的预处理
Canny()边缘检测检测图像中的边缘轮廓提取图像边缘特征轮廓检测、物体识别
morphologyEx()形态学操作执行形态学闭运算操作连接断开的边缘边缘增强和连接
dilate()形态学膨胀扩大图像中的亮区区域增强边缘连续性轮廓修复和增强
findContours()轮廓检测查找图像中的所有轮廓提取图像中的连通区域物体检测、形状分析
contourArea()几何计算计算轮廓的面积评估轮廓大小轮廓筛选和分类
minAreaRect()几何分析计算轮廓的最小外接矩形获取物体的旋转矩形边界文档检测、物体定位
boundingRect()边界计算计算轮廓的垂直边界矩形获取轮廓的包围盒区域分析和裁剪
▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇
line()图形绘制在图像上绘制直线段标记检测结果结果可视化和标注
circle()图形绘制在图像上绘制圆形标记突出显示关键点特征点标记
putText()文本绘制在图像上添加文字标注显示测量结果和标识结果说明和注释
imshow()图像显示在窗口中显示图像实时查看处理结果调试和演示
drawContours()轮廓绘制图像上绘制检测到的轮廓可视化轮廓结果调试和结果展示

以上加粗的地方用于记忆该函数的特点

模块一:读取图像imread

std::string imagePath = "myimage.jpg";// 读取图片cv::Mat image = cv::imread(imagePath);if (image.empty()) {std::cout << "no can read: " << imagePath << std::endl;return -1;}

入参是cv::String类型,也是std::string类型,可见这是一个C++的库,不是QT专属库。

模块二:颜色空间cvtColor

cv::Mat gray;
cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);

第一个参数:输入图像

第二个参数:输出图像

第三个参数常用的值:

  • COLOR_BGR2GRAY:BGR 转灰度图‌
  • COLOR_BGR2HSV:BGR 转 HSV 空间‌
  • COLOR_BGR2RGB:BGR 转 RGB 空间‌
  • COLOR_RGB2BGR:RGB 转 BGR 空间

OpenCV 默认彩色图像为 BGR,COLOR_BGR2GRAY即为转灰度图,转灰度图可以简化数据及减少计算量,减少了后续处理(如降噪、边缘检测等)的计算复杂度,从而提升算法运行速度‌,是非常常用的处理方式;

对颜色敏感的场景时,使用COLOR_BGR2HSV,比如只识别指定颜色(绿色)的银行卡;

调用其他明确需要RGB的库时,需要COLOR_BGR2RGB进行转换;

原图-灰度图对比图

原图-HSV图对比图

本篇不针对指定的银行卡,所有物体通用,使用的是灰度图。

模块三:高斯滤波GaussianBlur

cv::Mat blurred;
cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);

第一个参数:输入图像

第二个参数:输出图像

第三个参数:高斯核的大小,需要是奇数,值越小,越清晰,去噪越少,值越大越模糊,去噪越多

第三四个参数:高斯函数的标准差,配合高斯核使用

原理是通过高斯核(权重矩阵)对图像进行加权平均,从而平滑图像、降噪或作为预处理步骤‌。

cv::Size(3, 3)轻度模糊,基本保留所有的轮廓,如下图,右边轮廓图中对比原图强光的位置有零零散散的噪点;

cv::Size(5, 5)中度模糊,去掉比较明显的干扰,如下图,零零散散的强光噪点基本没有了;

cv::Size(7, 7)及以上是强模糊,清除更多的噪点,比如中间的凹槽轮廓与卡片衔接,影响后续的物体轮廓描边,此时希望去掉更多的噪点,如下图,靠近卡片的凹槽轮廓噪点被抹除了。

提出都是先使用cv::Size(5, 5)来调试整体效果,需要保留更多细节时就降为3,需要祛除更多噪点就提升为7、9,并且调节标准差进行调试。

另外右边的轮廓,是调用cv::Canny边缘检测之后,生成的边缘效果,这两个函数也是经常搭配使用的。

模块四:边缘检测Canny

效果图以及在模块三中进行展示,以下介绍参数。

    cv::Mat edges;double cannyThreshold1 = 30;//50double cannyThreshold2 = 90;//150cv::Canny(blurred, edges, cannyThreshold1, cannyThreshold2);

第一个参数:输入图像

第二个参数:输出图像

第三个参数:低阈值(threshold1)‌,用于连接边缘。如果设置过高,可能会漏掉弱边缘;如果设置过低,可能会引入过多的噪声‌

第四个参数:高阈值(threshold2)‌:用于检测强边缘。如果设置过高,可能会漏掉一些重要的边缘;如果设置过低,可能会检测到过多的噪声‌

threshold1 与 threshold2,一般建议 threshold2 ≈ 2~3 × threshold1。

模块五:形态学操作morphologyEx

    //形态学操作连接断开的边缘cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7));cv::morphologyEx(edges, edges, cv::MORPH_CLOSE, kernel);

先通过cv::getStructuringElement生成形态学操作所需的结构元素,

第一个参数:输入图像
第二个参数:输出图像
第三个参数:形态学操作类型,决定了具体的处理效果
第四个参数:结构元素,用于定义形态学操作的邻域形状和大小

如下图,可以看到轮廓的线条明显连续,但是目标卡片的图像还是比较模糊

模块六:形态学膨胀dilate

cv::dilate(edges, edges, kernel); // 添加膨胀操作

结果模块五的形态学初步处理之后,目标卡片的轮廓还是比较模糊,那么就对整体轮廓进行加膨胀粗操作,效果如下图,可以看到卡片的轮廓以及很明显了。

模块七:轮廓检测findContours

    //查找轮廓std::vector<std::vector<cv::Point>> contours;cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

第一个参数:输入单通道的二值图像
第二个参数:输出轮廓的坐标点集合,及连续的坐标点代表一个轮廓
第三个参数:轮廓检索模式,如 CV_RETR_EXTERNAL(仅最外层轮廓)、CV_RETR_TREE(完整嵌套层级)等‌
第四个参数:轮廓近似方法,如 CV_CHAIN_APPROX_SIMPLE(压缩水平/垂直/对角线段)‌

经过前面6个模块的处理,目标物体的轮廓已经很清晰了,此时可以调用轮廓检测函数,获取被预处理之后的图片的所有轮廓集合(轮廓不仅仅一个,噪点处理不会的图片甚至会检测出几百个轮廓)。因为是描边,获取最外层轮廓( CV_RETR_EXTERNAL)即可。

针对符合的轮廓集合contours,可以用cv::drawContours函数把轮廓坐标描绘出来

void showimg(const string& name, const cv::Mat& curImage) {cv::Mat resizedImage;double scale = 0.2;//把图片压缩到20%cv::resize(curImage, resizedImage, cv::Size(), scale, scale);cv::imshow(name, resizedImage);
}std::vector<std::vector<cv::Point>> contours;cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);// 用绿色绘制所有轮廓cv::drawContours(result, contours, -1, cv::Scalar(0, 255, 0), 2);showimg("result", result);

模块八:获取轮廓像素面积contourArea

    // 找到面积最大的轮廓auto maxContour = *std::max_element(contours.begin(), contours.end(),[](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) {return cv::contourArea(a) < cv::contourArea(b);});

正常来说最大的轮廓区域面积就是我们需要识别的目标物体,但是需要注意的是,这个轮廓像素面积≠几何平面面积!即它不是以几何学方式得出的结果,在噪点不干扰的情况下,像素面积最大以及几何面积最大都能指向同一区域。项目允许容错的话,用contourArea即可。

(后续我会分享一篇自己构建矩形的博文)

模块九:获取最小外接矩形minAreaRect

    // 获取最小外接矩形cv::RotatedRect rotatedRect = cv::minAreaRect(maxContour);cv::Point2f vertices[4];rotatedRect.points(vertices);// 转换为整数点坐标std::vector<cv::Point> rectPoints;for (int i = 0; i < 4; i++) {rectPoints.push_back(cv::Point(static_cast<int>(vertices[i].x),static_cast<int>(vertices[i].y)));}

用于寻找能够包围给定点集的最小面积矩形,该矩形可以是旋转的,通过 RotatedRect 的 points() 方法可以获取旋转矩形的四个顶点坐标,顶点坐标是浮点型,需要转换成整型。

最终效果图

绿色显示所有的轮廓,红色是最小面积矩形的(刚好把物体框进去),黄色点是最小面积矩形的四个顶点,代码偏向于绘图,辅助调试用的。

此时如果需要衔接裁剪功能,只需要把最小面积矩形的rectPoints坐标点去处理即可。

    // 用绿色绘制所有轮廓cv::drawContours(result, contours, -1, cv::Scalar(0, 255, 0), 2);if (!rectPoints.empty() && rectPoints.size() == 4) {// 用红色绘制矩形轮廓for (int i = 0; i < 4; i++) {cv::line(result, rectPoints[i], rectPoints[(i+1)%4], cv::Scalar(0, 0, 255), 3);}// 在4个点上画黄色圆标记for (int i = 0; i < 4; i++) {cv::circle(result, rectPoints[i], 8, cv::Scalar(0, 255, 255), -1);// 标记顶点编号cv::putText(result, std::to_string(i+1),cv::Point(rectPoints[i].x + 10, rectPoints[i].y - 10),cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 255, 255), 2);}// 计算并显示面积double area = cv::contourArea(rectPoints);std::string label = cv::format("Area: %.0f", area);cv::putText(result, label, cv::Point(20, 40),cv::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0, 0, 255), 2);std::cout << "检测到纸张轮廓,面积: " << area << std::endl;for (int i = 0; i < 4; i++) {std::cout << "顶点" << i+1 << ": (" << rectPoints[i].x << ", " << rectPoints[i].y << ")" << std::endl;}} else {std::cout << "未检测到明显的纸张轮廓" << std::endl;}// 显示结果showimg("result", result);

篇尾

        三、四、五、九模块需要反复调试才能得到很好的效果,需要本篇源码的请留言。

http://www.dtcms.com/a/609784.html

相关文章:

  • 广水住房和城乡建设部网站舟山网站建设开发
  • 深度学习打卡第R4周:LSTM-火灾温度预测
  • 最好的营销策划公司做seo网站优化价格
  • 通过Rust高性能异步网络服务器的实现看Rust语言的核心优势
  • 第36节:AI集成与3D场景中的智能NPC
  • 一个基于 LayUI + .NET 开源、轻量的医院住院管理系统
  • StarRocks 4.0:让 Apache Iceberg 数据真正 Query-Ready
  • 网站建设 自己的服务器爬虫python入门
  • android抽屉DrawerLayout在2025的沉浸式兼容
  • 美颜SDK性能优化实战:GPU加速与AI人脸美型的融合开发
  • AndroidStudio历史版本下载
  • Mac抹除重装卡在激活锁?两步快速解锁
  • Java语言是编译型还是解释型| 探究Java的运行机制与性能优化
  • 网站发语音功能如何做广州比较好的网站建设公司
  • 公司网站域名更改怎么做建设行业协会网站发展的建议
  • 【ZeroRange WebRTC】Kinesis Video Streams WebRTC Data Plane WebSocket API 深度解析
  • Docker核心概念、常用命令与实战指南
  • 交换机安全基线整改方式-华为S5700系列
  • Django 接口文档生成:Swagger 与 ReDoc 全面说明
  • Docker K8s VM 简介
  • FPGA教程系列-Vivado中读取ROM中数据
  • 网站怎么添加模块鹿寨建设局网站
  • 响应式外贸网站案例国外ps网站
  • springcloud feign远程调用请求参数对象变成linkhashmap处理
  • “耐达讯自动化Profibus总线光端机在化工变频泵控制系统中的应用与价值解析”
  • centos7.2安装cacti1.2.27
  • 将 vue3 项目打包后部署在 springboot 项目运行
  • 福州短视频seo网站建筑网站首页大图
  • 阿根廷网站后缀毕业设计网站成品
  • 性能相关指标