OpenCV图像处理:亚像素尺寸边缘轮廓提取

您所在的位置:网站首页 图像轮廓提取与拍照距离的关系 OpenCV图像处理:亚像素尺寸边缘轮廓提取

OpenCV图像处理:亚像素尺寸边缘轮廓提取

2024-07-09 10:39:27| 来源: 网络整理| 查看: 265

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

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

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

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

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

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

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

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

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

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

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();

(可以尝试一下shar)

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

** * @brief 在二值化边缘图像中检测和分离内部和外部轮廓。 * * @param cv_edge 二值化边缘图像。 * @param inner_contours 用于存储内部轮廓的向量。 */ void contour(cv::Mat& cv_edge, std::vector& inner_contours) { // 存储所有检测到的轮廓的向量 std::vector contours; // 存储轮廓之间层次关系的层次向量 std::vector hierarchy; // 在二值化边缘图像中查找轮廓 cv::findContours(cv_edge, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_NONE); // 存储有效轮廓和被排除轮廓的索引的向量 std::vector valids; std::vector 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 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 &cont_center, std::vector &inner_contours, cv::Mat & cv_cont_and,double RADIUS_MAX) { // 获取轮廓的长度 std::vector cont_lengths; contour_length(inner_contours, cont_lengths); // 存储轮廓的半径和纵横比 std::vector cont_ra, cont_aspect_ratio; cv::Mat_ 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_ cv_cont_radius(cont_ra); cv::Mat_ 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& filteredCont, std::vector& contSubPixFull) { // 定义用于插值的系数 std::vector p_vec{ 0.004711, 0.069321, 0.245410, 0.361117, 0.245410, 0.069321, 0.004711 }; std::vector d1_vec{ -0.018708, -0.125376, -0.193091, 0.000000, 0.193091, 0.125376, 0.018708 }; std::vector d2_vec{ 0.055336, 0.137778, -0.056554, -0.273118, -0.056554, 0.137778, 0.055336 }; // 将系数转为 Mat 类型 auto p = cv::Mat_(p_vec); auto d1 = cv::Mat_(d1_vec); auto d2 = cv::Mat_(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& cont) { return sub_pix_single(gy, gx, gyy, gxx, gxy, cont); } ); }

 把亚像素轮廓坐标显示:

void SubPixEdge::draw_roi(cv::Mat& cv_src, std::vector& filtered_cont, std::vector& 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(j, i); } } std::vector cont; for (int i = 0; i < cont_sub_pix.size(); ++i) { std::vector 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(j, i); } } std::vector 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 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(); }

左边为像素级边缘轮廓,右边为亚像素边缘轮廓:

转载:OpenCV图像处理——C++实现亚像素尺寸标定板边缘轮廓提取_opencv 轮廓提取 c++-CSDN博客



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭