OpenCV图像处理——C++实现亚像素尺寸标定板边缘轮廓提取

前言

标定模板(Calibration Target)在机器视觉、图像测量、摄影测量以及三维重建等应用中起着重要的作用。它被用于校正相机的畸变,确定物理尺寸和像素之间的换算关系,并建立相机成像的几何模型。通过使用相机拍摄带有固定间距图案阵列的平板,并经过标定算法的计算,我们能够得到相机的几何参数,从而获得高精度的测量和重建结果。

这种标定模板通常具有固定间距的图案阵列,例如棋盘格、圆点阵列等。这些图案提供了已知尺寸的参考点,使得可以精确地计算相机的内参(内部参数)和外参(外部参数)。内参包括焦距、主点等相机内部的属性,而外参包括相机的位置和方向等外部属性。

通过对标定模板进行拍摄和相应的标定过程,可以消除图像中的畸变效应,实现对物体三维几何位置与其在图像中对应点之间的精确关联。这为各种应用场景提供了可靠的基础,包括计算机视觉任务、图像测量、摄影测量以及三维重建等。标定模板的使用有助于提高测量和重建的准确性,并确保获得可靠的结果。
这是使用的标定板是49个圆点的尺寸标定板,为是更好的演示亚像素,所拍的图像大小为320240pix,标定板的大小为130130pix。

在这里插入图片描述

边缘检测

在计算机视觉和机器视觉领域,广泛应用图像分割,这是将数字图像细分为多个子区域的过程。图像分割的目标是简化或改变图像的表示形式,使其更易于理解和分析。这通常包括定位图像中的物体和边界,生成图像子区域或轮廓线的集合。图像分割方法主要涵盖阈值处理(二值化)、聚类法、边缘检测和区域生长等。解决图像分割问题时,通常需要结合领域知识,因为没有统一的范式可以适用于所有情境。

边缘检测是一种基于灰度变化的常见图像分割方法,其核心是提取图像中不连续部分的特征。常见的边缘检测算子包括差分算子、Roberts算子、Sobel算子、Prewitt算子、Log算子以及Canny算子等。其中,Canny算子由计算机科学家John F. Canny于1986年提出,被认为是目前理论上相对最完善的边缘检测算法。
这里面使用了Canny来进行边缘检测,Canny边缘检测是一种经典的图像处理技术,用于检测图像中的边缘。这个算法由约翰·Canny于1986年提出,被广泛应用于计算机视觉和图像处理领域。以下是Canny边缘检测的基本原理和步骤:

  1. 噪声抑制: 使用高斯滤波器对图像进行平滑处理,以减少噪声的影响。这有助于提高后续边缘检测步骤的准确性。

  2. 计算梯度幅值和方向: 利用Sobel等算子计算图像中每个像素点的梯度幅值和方向。梯度表示图像中的变化率,有助于确定潜在的边缘位置。

  3. 非极大值抑制: 在图像的每个像素点上,仅保留梯度幅值最大的方向,从而细化边缘。

  4. 双阈值检测: 将梯度幅值分为高阈值和低阈值两部分。高于高阈值的像素点被认为是强边缘,低于低阈值的像素点被排除。介于两者之间的像素点被标记为弱边缘。

  5. 边缘连接: 通过强边缘的连接,将弱边缘与强边缘关联起来,形成连续的边缘。

Canny边缘检测的优势在于其高准确性、低误差率和对边缘的单一定位。

	cv::Mat cv_gray;if (cv_src.channels() > 1){cv::cvtColor(cv_src, cv_gray, cv::COLOR_BGR2GRAY);}else{cv_gray = cv_src.clone();}cv::Mat cv_edge;//1.边缘检测cv::Canny(cv_gray, cv_edge, 180, 200);cv::imshow("edge", cv_edge);cv::waitKey();

canny的结果:
在这里插入图片描述

轮廓提取

轮廓是由一系列相连的像素点组成的曲线,反映了物体的基本外形特征。常常用于形状分析以及物体的检测和识别任务。
边缘检测通过检测灰度的突变来寻找图像边界,但通常检测到的边缘是零散的片段,没有形成完整的整体。为了从背景中分离目标,需要将这些边缘像素连接起来形成轮廓。换句话说,轮廓是一种连续的曲线,而边缘未必都是连续的。边缘主要被用作图像的特征,而轮廓则更多地用于分析物体的整体形态。
cv::findContours 是OpenCV库中用于查找图像中轮廓的函数。它的基本用法是在二进制图像中找到对象的轮廓,返回一个包含轮廓点集合的列表。
然而,在使用cv::findContours时,会产生内外两个轮廓,为了减少内部,要对获取的轮廓进行进一步的处理:

*** @brief 在二值化边缘图像中检测和分离内部和外部轮廓。** @param cv_edge 二值化边缘图像。* @param inner_contours 用于存储内部轮廓的向量。*/
void contour(cv::Mat& cv_edge, std::vector<std::vector<cv::Point>>& inner_contours)
{// 存储所有检测到的轮廓的向量std::vector<std::vector<cv::Point>> contours;// 存储轮廓之间层次关系的层次向量std::vector<cv::Vec4i> hierarchy;// 在二值化边缘图像中查找轮廓cv::findContours(cv_edge, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_NONE);// 存储有效轮廓和被排除轮廓的索引的向量std::vector<int> valids;std::vector<int> excludes;// 根据层次关系减少内部轮廓edge_contour_valids(hierarchy, valids, excludes);// 调整 inner_contours 向量的大小以容纳有效轮廓inner_contours.resize(valids.size());// 将有效轮廓的索引映射到相应的轮廓,并存储在 inner_contours 中std::transform(std::execution::par,valids.begin(),valids.end(),inner_contours.begin(),[contours](int i) { return contours[i]; });// 存储外部轮廓的向量std::vector<std::vector<cv::Point>> external_contours(excludes.size());// 将被排除轮廓的索引映射到相应的轮廓,并存储在 external_contours 中std::transform(std::execution::par,excludes.begin(),excludes.end(),external_contours.begin(),[contours](int i) { return contours[i]; });
}

得到轮廓之后,要获取标定板圆前先获取轮廓的矩:

/*** @brief 计算轮廓的属性并进行阈值化,筛选符合条件的轮廓。** @param inner_contours 存储内部轮廓的向量。* @param cv_cont_and 存储轮廓属性的阈值化结果。* @param cont_center 存储轮廓中心点的向量。* @param RADIUS_MAX 最大半径阈值。*/
void contours_attribute(std::vector<cv::Point> &cont_center, std::vector<std::vector<cv::Point>> &inner_contours,cv::Mat & cv_cont_and,double RADIUS_MAX)
{// 获取轮廓的长度std::vector<double> cont_lengths;contour_length(inner_contours, cont_lengths);// 存储轮廓的半径和纵横比std::vector<double> cont_ra, cont_aspect_ratio;cv::Mat_<double> cont_length_mat(cont_lengths);// 计算轮廓中心点和其他属性for (const auto& c : inner_contours){auto M = cv::moments(c);double area = M.m00;auto centerX = int(M.m10 / area);auto centerY = int(M.m01 / area);double m20 = M.mu20 / area;double m02 = M.mu02 / area;double m11 = M.mu11 / area;double c1 = m20 - m02;double c2 = c1 * c1;double c3 = 4 * m11 * m11;cont_center.emplace_back(centerX, centerY);auto ra = sqrt(2.0 * (m20 + m02 + sqrt(c2 + c3)));auto rb = sqrt(2.0 * (m20 + m02 - sqrt(c2 + c3)));cont_ra.emplace_back(ra);cont_aspect_ratio.emplace_back(ra / rb);}// 将属性向量转为 OpenCV 的 Mat 类型cv::Mat_<double> cv_cont_radius(cont_ra);cv::Mat_<double> cv_cont_aspect_ratio(cont_aspect_ratio);// 定义阈值和计算最大轮廓长度cv::Mat cv_aspect_ratio, cv_radius, cv_cont_length;const double CONT_LENGTH_MAX = 2 * M_PI * RADIUS_MAX;// 对纵横比、半径和轮廓长度进行阈值化cv::threshold(cv_cont_aspect_ratio, cv_aspect_ratio, 0.8, 1.0, cv::ThresholdTypes::THRESH_BINARY);cv::threshold(cv_cont_radius, cv_radius, RADIUS_MAX, 1.0, cv::ThresholdTypes::THRESH_BINARY_INV);cv::threshold(cont_length_mat, cv_cont_length, CONT_LENGTH_MAX, 1.0, cv::ThresholdTypes::THRESH_BINARY_INV);// 将阈值结果按位与,存储在 cv_cont_and 中cv::Mat and1;cv::bitwise_and(cv_aspect_ratio, cv_radius, and1);cv::bitwise_and(and1, cv_cont_length, cv_cont_and);
}

在这里插入图片描述

亚像素轮廓提取

使用亚像素插值,获得轮廓的亚像素级坐标:

/*** @brief 对图像进行亚像素插值,获得轮廓的亚像素级坐标。** @param image_gray 灰度图像。* @param filteredCont 过滤后的轮廓向量。* @param contSubPixFull 存储亚像素级坐标的向量的向量。*/
void sub_pix_contour(const cv::Mat& image_gray,const std::vector<std::vector<cv::Point>>& filteredCont,std::vector<std::shared_ptr<std::vector<std::shared_ptr<cv::Point2d>>>>& contSubPixFull)
{// 定义用于插值的系数std::vector<double> p_vec{ 0.004711,  0.069321,  0.245410,  0.361117,  0.245410,  0.069321,  0.004711 };std::vector<double> d1_vec{ -0.018708,  -0.125376,  -0.193091,  0.000000, 0.193091, 0.125376, 0.018708 };std::vector<double> d2_vec{ 0.055336,  0.137778, -0.056554, -0.273118, -0.056554,  0.137778,  0.055336 };// 将系数转为 Mat 类型auto p = cv::Mat_<double>(p_vec);auto d1 = cv::Mat_<double>(d1_vec);auto d2 = cv::Mat_<double>(d2_vec);// 计算图像梯度cv::Mat dx, dy, grad;cv::sepFilter2D(image_gray, dy, CV_64F, p, d1);cv::sepFilter2D(image_gray, dx, CV_64F, d1, p);cv::pow(dy.mul(dy, 1.0) + dx.mul(dx, 1.0), 0.5, grad);// 计算更高阶导数cv::Mat gy, gx, gyy, gxx, gxy;cv::sepFilter2D(grad, gy, CV_64F, p, d1);cv::sepFilter2D(grad, gx, CV_64F, d1, p);cv::sepFilter2D(grad, gyy, CV_64F, p, d2);cv::sepFilter2D(grad, gxx, CV_64F, d2, p);cv::sepFilter2D(grad, gxy, CV_64F, d1, d1);// 对每个轮廓进行亚像素插值contSubPixFull.resize(filteredCont.size());std::transform(std::execution::par,filteredCont.cbegin(),filteredCont.cend(),contSubPixFull.begin(),[&gy, &gx, &gyy, &gxx, &gxy](const std::vector<cv::Point>& cont){return sub_pix_single(gy, gx, gyy, gxx, gxy, cont);});
}

把亚像素轮廓坐标显示:

void SubPixEdge::draw_roi(cv::Mat& cv_src, std::vector<std::vector<cv::Point>>& filtered_cont,std::vector<std::shared_ptr<std::vector<std::shared_ptr<cv::Point2d>>>>& cont_sub_pix,bool show_roi)
{const int up_scale = 50;cv::Mat cv_temp = cv_src.clone();int src_up_w = up_scale * cv_temp.rows;int src_up_h = up_scale * cv_temp.cols;cv::Mat cv_src_up = cv::Mat::zeros(src_up_w, src_up_h, CV_8UC3);for (int i = 0; i < cv_temp.cols; ++i){for (int j = 0; j < cv_temp.rows; ++j){cv::Mat roi = cv_src_up(cv::Rect(i * up_scale, j * up_scale, up_scale, up_scale));roi = cv_temp.at<cv::Vec<uchar, 3>>(j, i);}}std::vector<std::vector<cv::Point>> cont;for (int i = 0; i < cont_sub_pix.size(); ++i){std::vector<cv::Point> dis_cont;for (const auto& p : *cont_sub_pix[i]){//使用 floor() 函数向下取整int x = floor((p->x + 0.5) * up_scale);int y = floor((p->y + 0.5) * up_scale);cv::drawMarker(cv_src_up, cv::Point(x, y), cv::Scalar(0, 0, 255), cv::MARKER_TILTED_CROSS, 20, 5);dis_cont.emplace_back(x, y);}cont.emplace_back(dis_cont);}cv::drawContours(cv_src_up, cont, -1, cv::Scalar(255, 0, 0), 5);cv::drawContours(cv_src, filtered_cont, -1, cv::Scalar(255, 0, 0));if (show_roi){for (int i = 0; i < filtered_cont.size(); ++i){cv::Rect rect = cv::boundingRect(filtered_cont[i]);cv::Mat crop = cv_src(rect);if (crop.empty()){continue;}int up_w = up_scale * rect.width;int up_h = up_scale * rect.height;cv::Mat cv_up = cv::Mat::zeros(up_w, up_h, CV_8UC3);for (int i = 0; i < crop.cols; ++i){for (int j = 0; j < crop.rows; ++j){cv::Mat roi = cv_up(cv::Rect(i * up_scale, j * up_scale, up_scale, up_scale));roi = crop.at<cv::Vec<uchar, 3>>(j, i);}}std::vector<cv::Point> dis_cont;for (const auto& p : *cont_sub_pix[i]){//使用 floor() 函数向下取整int x = floor(((p->x - rect.x) + 0.5) * up_scale);int y = floor(((p->y - rect.y) + 0.5) * up_scale);cv::drawMarker(cv_up, cv::Point(x, y), cv::Scalar(0, 0, 255), cv::MARKER_TILTED_CROSS, 20, 3);dis_cont.emplace_back(x, y);}std::vector<std::vector<cv::Point>> display_contour_full{ dis_cont };cv::drawContours(cv_up, display_contour_full, 0, cv::Scalar(255.0, 0.0, 0.0), 3);cv::namedWindow("upScaled", 0);cv::imshow("upScaled", cv_up);cv::waitKey();}}cv::namedWindow("src", 0);cv::imshow("src", cv_src);cv::namedWindow("sub_pix_src", 0);cv::imshow("sub_pix_src", cv_src_up);cv::waitKey();
}

左边为像素级边缘轮廓,右边为亚像素边缘轮廓:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/601036.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

进程和计划任务-------后续(二)

一、进程管理 1.进程启动---------- 前台启动与后台启动 进程需要手动启动 前台启动&#xff08;运行&#xff09;&#xff1a;通过终端启动&#xff0c;且启动后一直占据终端&#xff08;影响当先终端的操作&#xff09; 后台启动&#xff1a;可通过终端启动&#xff0c;但启…

Unity添加所有场景到BuildSettings

Unity添加所有场景到BuildSettings using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.IO; public class Tools : Editor {[MenuItem("Tools/添加所有场景到BuildSettings")]static void CheckSceneSetting(){List<string&…

java基于ssm的线上选课系统的设计与实现论文

摘 要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&#xff0c;对学生选课信息管理的提升&#x…

【AI视野·今日Robot 机器人论文速览 第六十八期】Tue, 2 Jan 2024

AI视野今日CS.Robotics 机器人学论文速览 Tue, 2 Jan 2024 Totally 12 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Edge Computing based Human-Robot Cognitive Fusion: A Medical Case Study in the Autism Spectrum Disorder Therapy Author…

【华为OD真题 Python】查找一个有向网络的头节点和尾节点

文章目录 题目描述输入输出示例1输入输出示例2输入输出备注实现代码题目描述 给定一个有向图,图中可能包含有环,图使用二维矩阵表示,每一行的第一列表示起始节点,第二列表示终止节点,如[0, 1]表示从0到1的路径。每个节点用正整数表示。求这个数据的首节点与尾节点,题目给…

java基于ssm框架的博客系统的开发论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

Mendix 创客访谈录|太迅的闪电侠如何发动闪电战

本期创客 房杰 上海太迅自动识别技术有限公司 移动开发经理 大家好&#xff0c;我叫房杰&#xff0c;软件工程专业毕业&#xff0c;目前任职于上海太迅自动识别技术有限公司&#xff0c;任职的部门是软件事业部&#xff0c;担任移动开发经理。 上海太迅是一家专注于自动识…

安装Keras用于影像分割

conda create -n tfkeras2024 python3.9.18 activate tfkeras2024 pip install tensorflow-gpu2.9.0 pip install keras pip install scipy pip install ipykernel ipython python -m ipykernel install --name tfkeras2024 删除环境conda remove -n tfkeras2024 --all

谈一谈报考高校异地研究院的利弊

这篇文章是抖音和b站上上传的同名视频的原文稿件&#xff0c;感兴趣的csdn用户可以关注我的抖音和b站账号&#xff08;GeekPower极客力量&#xff09;。同时这篇文章也为视频观众提供方便&#xff0c;可以更加冷静地分析和思考。文章同时在知乎发表。 前年国家出台文件&#x…

Jenkins修改全局maven配置后不生效解决办法、以及任务读取不同的settings.xml文件配置

一、修改Global Tool Configuration的maven配置不生效 说明&#xff1a;搭建好jenkins后&#xff0c;修改了全局的settings.xml&#xff0c;导致读取settings一直是之前配置的。 解决办法一 Jenkins在创建工作任务时&#xff0c;会读取当前配置文件内容&#xff0c;固定在这…

目标检测:深度学习引领视觉智能的未来

目标检测&#xff1a;深度学习引领视觉智能的未来 目标检测是计算机视觉领域中的一项重要任务&#xff0c;旨在从图像或视频中确定和定位特定物体的存在。这一领域的发展在很大程度上得益于深度学习技术的崛起&#xff0c;为机器在理解和处理视觉信息上带来了质的飞跃。本文将…

Scrum的工件

我们采用了Scrum进行开发方面的管理&#xff0c;那么所有的计划和工作都应该是透明的&#xff0c;这给了我们检查这些东西的机会&#xff0c;以便能够即时做出调整来适应即将发生的变化。 那么Scrum为我们设计了一些工件帮助我们检查我们的工作和计划&#xff0c;每个工件都有…

【学习笔记】[AGC060D] Same Descent Set

本来是想做点多项式调节一下&#xff0c;结果发现这玩意太肝了&#xff0c;似乎并没有起到调节作用。 设 f ( S ) f(S) f(S)表示符号为 < < <的下标集合恰好为 S S S的方案数&#xff0c;因为两个序列完全等同&#xff0c;因此答案等于 ∑ S ⊆ { 1 , 2 , . . . , n …

如何在SpringBoot中优雅地重试调用第三方API?

1.引言 在实际的应用中,我们经常需要调用第三方API来获取数据或执行某些操作。然而,由于网络不稳定、第三方服务异常等原因,API调用可能会失败。为了提高系统的稳定性和可靠性,我们通常会考虑实现重试机制。 2.重试机制的必要性 第三方API调用可能面临各种不可预测的问题…

【面试高频算法解析】算法练习5 深度优先搜索

前言 本专栏旨在通过分类学习算法&#xff0c;使您能够牢固掌握不同算法的理论要点。通过策略性地练习精选的经典题目&#xff0c;帮助您深度理解每种算法&#xff0c;避免出现刷了很多算法题&#xff0c;还是一知半解的状态 专栏导航 二分查找回溯&#xff08;Backtracking&…

【华为OD真题 Python】精准核酸检测

文章目录 题目描述输入描述输出描述示例1输入输出说明备注代码实现题目描述 为了达到新冠疫情精准防控的需要,为了避免全员核酸检测带来的浪费,需要精准圈定可能被感染的人群。 现在根据传染病流调以及大数据分析,得到了每个人之间在时间、空间上是否存在轨迹的交叉。 现…

C#,快速排序算法(Quick Sort)的非递归实现与数据可视化

排序算法是编程的基础。 常见的四种排序算法是&#xff1a;简单选择排序、冒泡排序、插入排序和快速排序。其中的快速排序的优势明显&#xff0c;一般使用递归方式实现&#xff0c;但遇到数据量大的情况则无法适用。实际工程中一般使用“非递归”方式实现。 快速排序(Quick Sor…

【kettle】pdi/data-integration 集成kerberos认证连接hdfs、hive或spark thriftserver

一、背景 kerberos认证是比较底层的认证&#xff0c;掌握好了用起来比较简单。 kettle完成kerberos认证后会存储认证信息在jvm中&#xff0c;之后直接连接hive就可以了无需提供额外的用户信息。 spark thriftserver本质就是通过hive jdbc协议连接并运行spark sql任务。 二、…

2020年认证杯SPSSPRO杯数学建模A题(第二阶段)听音辨位全过程文档及程序

2020年认证杯SPSSPRO杯数学建模 A题 听音辨位 原题再现&#xff1a; 把若干 (⩾ 1) 支同样型号的麦克风固定安装在一个刚性的枝形架子上 (架子下面带万向轮&#xff0c;在平地上可以被水平推动或旋转&#xff0c;但不会歪斜)&#xff0c;这样的设备称为一个麦克风树。不同的麦…

bootstrap5实现宠物商店网站 Cat-Master

一、需求分析 宠物商店网站是指专门为宠物商店或宠物用品商家而建立的在线平台。这种网站的功能通常旨在提供以下服务&#xff1a; 产品展示&#xff1a;宠物商店网站通常会展示宠物食品、玩具、床上用品、健康护理产品等各种宠物用品的图片和详细信息。这样&#xff0c;潜在的…