【opencv】第8章 图像轮廓与图像分割修复

8.1 查找并绘制轮廓

一个轮廓一般对应一系列的点,也就是图像中的一条曲线。其表示方法可能 根据不同的情况而有所不同。在OpenCV 中,可以用findContours()函数从二值图 像中查找轮廓

8.1.1 寻找轮廓: findContours() 函数

findContours) 函数用于在二值图像中寻找轮廓。

void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, intmethod, Point offset = Point())
  • 第一个参数,InputArray类型的image, 输入图像,即源图像,填Mat 类的 对象即可,且需为8位单通道图像。图像的非零像素被视为1,0像素值被 保留为0,所以图像为二进制。我们可以使用 compare() 、inrange()、
    threshold() 、adaptivethreshold() 、cannyO 等函数由灰度图或彩色图创建二进 制图像。此函数会在提取图像轮廓的同时修改图像的内容。
  • 第二个参数,OutputArrayOfArrays 类 型 的contours、检测到的轮廓、函数 调用后的运算结果存在这里。每个轮廓存储为一个点向量,即用point 类 型 的 vector表示。
  • 第三个参数,OutputArray 类型的hierarchy,可选的输出向量,包含图像的 拓扑信息。其作为轮廓数量的表示,包含了许多元素。每个轮廓contours[i] 对 应 4 个hierarchy 元 素hierarchy[i][0]~hierarchy[i][3], 分别表示后一 个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。如果没有对应项,
    对应的hierarchy[i] 值设置为负数。
  • 第四个参数,int 类型的mode, 轮廓检索模式,取值如表8.1所示。

表8.1 findContours函数可选的轮廓检索模式

标识符含义
RETR_EXTERNAL表示只检测最外层轮廓。对所有轮廓,设置 hierarchy[i][2]=hierarchy[i][3]=-1
RETR_LIST提取所有轮廓,并且放置在list中。检测的轮廓 不建立等级关系
RETR_CCOMP提取所有轮廓,并且将其组织为双层结构(two-level hierarchy:顶层为连通域的外围边界, 次层为孔的内层边界
RETR_TREE提取所有轮廓,并重新建立网状的轮廓结构
  • 第五个参数,int类 型 的method, 为轮廓的近似办法,取值如表8.2所示。

表8.2 findContours函数可选的轮廓近似办法

标识符含义
CHAIN_APPROX NONE获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过 1,即max(abs(xl-x2),abs(y2-y1))=1
CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向 的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
CHAIN_APPROX_TC89_L1 ,CHAIN_APPROX_TC89_KCOS使用Teh-Chinl链逼近算法中的一个

同样地,在表8.1和8.2中列出的宏之前加上”CV_” 前缀,便是OpenCV2 中可以使用的宏。如"RETR_CCOMP” 宏 的OpenCV2 版 为“CV_RETR_CCOMP"。

  • 第六个参数,Point 类 型 的 offset,每个轮廓点的可选偏移量,有默认值 Point()。对 ROI 图像中找出的轮廓,并要在整个图像中进行分析时,这个 参数便可排上用场。

findContours 经 常 与drawContours 配合使用一使用用findContours (函数检测 到图像的轮廓后,便可以用drawContours (函数将检测到的轮廓绘制出来。接下来, 让我们一起看看drawContours() 函数的用法。

8.1.2 绘 制 轮 廓 :drawContours ()函 数

drawContours() 函数用于在图像中绘制外部或内部轮廓。

void drawContours(InputoutputArray image, InputArrayofArrays contours, int contourIdx, const Scalar &color, int thickness = 1, int lineType = 8, InputArray hierarchy = noArray(0), int maxLevel = INT_MAX, Point offset = Point())
  • 第 一 个参数,InputArray类 型 的image, 目标图像,填Mat 类的对象即可。
  • 第二个参数,InputArrayOfArrays类型的contours,所有的输入轮廓。每个 轮廓存储为一个点向量,即用point类 型 的vector表示。
  • 第三个参数,int类 型 的contourldx,轮廓绘制的指示变量。如果其为负值, 则绘制所有轮廓。
  • 第四个参数,const Scalar&类型的color, 轮廓的颜色。
  • 第五个参数,int thickness,轮廓线条的粗细度,有默认值1。如果其为负 值(如 thickness=cv_filled), 便会绘制在轮廓的内部。可选为FILLED 宏(OpenCV2版为CV_FILLED)。
  • 第六个参数,int 类型的lineType,线条的类型,有默认值8。取值类型如 表8 . 3所示。

表8.3 可选线性

lineType线性含义
8(默认值)8连通线型
44连通线型
LINE_AA(OpenCV2版为CV_AA)抗锯齿线型
  • 第七个参数,InputArray 类型的hierarchy,可选的层次结构信息,有默认 值noArray()。
  • 第八个参数,int类型的maxLevel,表示用于绘制轮廓的最大等级,有默认 值INT_MAX
  • 第九个参数,Point类型的offset,可选的轮廓偏移参数,用指定的偏移量 offset=(dx,dy) 偏移需要绘制的轮廓,有默认值Point()。

下面是 一 个调用小示例。 //在白色图像上绘制黑色轮廓

Mat result(image.size(),CV_8U,cv::Scalar(255));
drawContours(result,contours,- 1,Scalar(0),3);

8.1.3 基础示例程序:轮廓查找

void Test52() {Mat srcImage = imread("image.jpg", 0); //灰度图读入imshow("src", srcImage);Mat dstImage = Mat::zeros(srcImage.rows, srcImage.cols, CV_8UC3);srcImage = srcImage > 119;imshow("mid", srcImage); //取阈值后的图像//定义轮廓和层次结构std::vector<std::vector<Point>>contours;std::vector<Vec4i>hierachy;//查找轮廓findContours(srcImage, contours, hierachy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);//遍历所有顶层的罗坤,随机颜色绘制出每个连接组件颜色int index = 0;for (; index >= 0; index = hierachy[index][0]) {Scalar color(rand() & 255, rand() & 255, rand() & 255);drawContours(dstImage, contours, index, color, FILLED, 8, hierachy);imshow("dst", dstImage);}waitKey(0);
}

在这里插入图片描述

在这里插入图片描述

8.1.4 综合示例程序:查找并绘制轮廓

除了上述这个精简版的示例程序,还为大家准备了一个更加复杂一些的关 于查找并绘制轮廓的综合示例程序。此程序利用了图像平滑技术(blur() 函数)和边缘检测技术(cannyO 函数),根据滑动条的调节,可以动态地检测出图形的 轮 廓 。

namespace test53 {Mat g_srcImage, g_grayImage;int g_nThresh = 80;int g_nThresh_max = 255;RNG g_rng(12345);Mat g_cannyMat_output;std::vector<std::vector<Point>>g_vContours;std::vector<Vec4i>g_vHierarchy;void on_ThreshChange(int, void*) {Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3); //Canny算子边缘检测findContours(g_cannyMat_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//轮廓提取Mat drawing = Mat::zeros(g_cannyMat_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); ++i) {Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//随机值drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, Point());}imshow("drawing", drawing);}void Test() {g_srcImage = imread("image.jpg");cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3)); //降噪namedWindow("window1");imshow("window1", g_srcImage);createTrackbar("value", "window1", &g_nThresh, g_nThresh_max, on_ThreshChange);on_ThreshChange(0, 0);waitKey(0);}
}void Test53() {test53::Test();
}

在这里插入图片描述
在这里插入图片描述

8.2 寻找物体的凸包

8.2.1 凸 包

凸包(Convex Hull) 是一个计算几何(图形学)中常见的概念。简单来说, 给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边型,它是 能包含点集中所有点的。理解物体形状或轮廓的一种比较有用的方法便是计算一 个物体的凸包,然后计算其凸缺陷(convexity defects)。很多复杂物体的特性能很 好地被这种缺陷表现出来。

如图8.9所示,我们用人手图来举例说明凸缺陷这一概念。手周围深色的线 描画出了凸包,A 到 H 被标出的区域是凸包的各个“缺陷”。正如看到的,这些 凸度缺陷提供了手以及手状态的特征表现的方法。

在这里插入图片描述

新版OpenCV 中 ,convexHull 函数用于寻找图像点集中的凸包,我们一起来 看一下这个函数。

8.2.2 寻找凸包:convexHullO函数

上文已经提到过,convexHullO 函数用于寻找图像点集中的凸包,其原型声明 如 下 。

void convexHull(InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true)
  • 第一个参数,InputArray类型的points,输入的二维点集,可以填Mat 类型 或者std::vector。
  • 第二个参数,OutputArray类型的hull,输出参数,函数调用后找到的凸包。
  • 第三个参数,bool 类型的clockwise,操作方向标识符。当此标识符为真时, 输出的凸包为顺时针方向。否则,就为逆时针方向。并且是假定坐标系的 x 轴指向右,y 轴指向上方。
  • 第四个参数,bool 类型的returnPoints,操作标志符,默认值true。当 标 志 符为真时,函数返回各凸包的各个点。否则,它返回凸包各点的指数。当 输出数组是std::vector 时,此标志被忽略。

8.2.3 基础示例程序:凸包检测基础

为了理解凸包检测的运用方法,下面放出一个完整的示例程序。程序中会首 先随机生成3~103个坐标值随机的彩色点,然后利用convexHull, 对由这些点链 接起来的图形求凸包。

void Test54() {Mat image(600, 600, CV_8UC3);RNG& rng = theRNG();while (1) {char key;int count = (unsigned)rng % 100 + 3;std::vector<Point>points;//随机生成坐标for (int i = 0; i < count; ++i) {Point point;point.x = rng.uniform(image.cols / 4, image.cols * 3 / 4);point.y = rng.uniform(image.rows / 4, image.rows * 3 / 4);points.push_back(point);}//检测凸包std::vector<int>hull;convexHull(Mat(points), hull, true);//绘制出随机颜色的点image = Scalar::all(0);for (int i = 0; i < count; ++i) {circle(image, points[i], 3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),FILLED,LINE_AA);}int hullcount = hull.size();Point point0 = points[hull.back()];//依次连接凸包的点for (int i = 0; i < hullcount; ++i) {Point point = points[hull[i]];line(image, point0, point, Scalar(255, 255, 255), 2, LINE_AA);point0 = point;}imshow("hull", image);key = waitKey(0);if (key == 27) break;}}

在这里插入图片描述

8.2.4 综合示例程序:寻找和绘制物体的凸包

这一节的综合示例程序,依然是结合滑动条,通过滑动条控制阈值,来得到 不同的凸包检测效果图。程序详细注释的源代码如下。

namespace test55 {Mat g_srcImage, g_grayImage;int g_nThresh = 50;int g_maxThresh = 255;RNG g_rng(12345);Mat srcImage_copy = g_srcImage.clone();Mat g_thresholdImage_output;std::vector<std::vector<Point>>g_vContours;std::vector<Vec4i>g_vHierarchy;void on_ThreshChange(int, void*) {// 二值化threshold(g_grayImage, g_thresholdImage_output, g_nThresh, 255, THRESH_BINARY);findContours(g_thresholdImage_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0)); //轮廓std::vector<std::vector<Point>>hull(g_vContours.size());for (int i = 0; i < g_vContours.size(); ++i) {convexHull(Mat(g_vContours[i]), hull[i], false);}//绘制出轮廓及凸包Mat drawing = Mat::zeros(g_thresholdImage_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); ++i) {Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));  drawContours(drawing, g_vContours, i, color, 1, 8, std::vector<Vec4i>(), 0, Point()); //轮廓drawContours(drawing, hull, i, color, 1, 8, std::vector<Vec4i>(), 0, Point()); //凸包}imshow("drawing", drawing);}void Test() {g_srcImage = imread("image.jpg");cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3));//降噪//创建窗口namedWindow("src");imshow("src", g_srcImage);//创建滚动条createTrackbar("value", "src", &g_nThresh, g_maxThresh, on_ThreshChange);on_ThreshChange(0, nullptr);waitKey(0);}
}void Test55() {test55::Test();
}

在这里插入图片描述

在这里插入图片描述

8.3 使用多边形将轮廓包围

在实际应用中,常常会有将检测到的轮廓用多边形表示出来的需求。本节就 为大家讲解如何用多边形来表示出轮廓,或者说如何根据轮廓提取出多边形。先 让我们一起学习用OpenCV 创建包围轮廓的多边形边界时会接触到的一些函数。

8.3.1 返回外部矩形边界:boundingRect(O函数

此函数计算并返回指定点集最外面(up-right)的矩形边界。 C++:Rect boundingRect(InputArray points)
其唯一的一个参数为输入的二维点集,可以是std::vector 或 Mat 类型。

8.3.2 寻找最小包围矩形:minAreaRect(函数

此函数用于对给定的2D 点集,寻找可旋转的最小面积的包围矩形。 C++:RotatedRect minAreaRect(InputArray points)
其唯一的一个参数为输入的二维点集,可以为std::vector> 或 Mat 类型。

8.3.3寻找最小包围圆形:minEnclosingCircle(函数

minEnclosingCircle函数的功能是利用一种迭代算法,对给定的2D 点集,去 寻找面积最小的可包围它们的圆形。

void minEnclosingCircle(InputArray points, Point2f &center, float &radius)
  • 第 一 个 参 数 ,InputArray 类 型 的points,输入的二维点集,可以为std::vector◇ 或Mat 类型。
  • 第二个参数,Point2f&类型的center,圆的输出圆心。
  • 第三个参数,float&类型的radius,圆的输出半径。

8.3.4 用椭圆拟合二维点集:fitEllipse ( 函 数

此函数的作用是用椭圆拟合二维点集。

RotatedRect fitEllipse(InputArray points)

其唯一的一个参数为输入的二维点集,可以为std::vector<>或Mat 类型。

8.3.5 逼近多边形曲线: approxPolyDPO 函 数

approxPolyDP函数的作用是用指定精度逼近多边形曲线。

void approxPolyDP(InputArray curve, OutputArray approxCurve,double epsilon, bool closed)
  • 第一个参数,InputArray类型的curve,输入的二维点集,可以为std::vecto 或Mat 类型。
  • 第二个参数,OutputArray类型的approxCurve,多边形逼近的结果,其类 型应该和输入的二维点集的类型 一 致。
  • 第三个参数,double类型的epsilon,逼近的精度,为原始曲线和即近似曲 线间的最大值。
  • 第四个参数,bool 类型的closed,如果其为真,则近似的曲线为封闭曲线 (第一个顶点和最后一个顶点相连),否则,近似的曲线曲线不封闭。

8.4 图像的矩

矩函数在图像分析中有着广泛的应用,如模式识别、目标分类、目标识别与 方位估计、图像编码与重构等。一个从一幅数字图形中计算出来的矩集,通常描 述了该图像形状的全局特征,并提供了大量的关于该图像不同类型的几何特性信 息,比如大小、位置、方向及形状等。图像矩的这种特性描述能力被广泛地应用 在各种图像处理、计算机视觉和机器人技术领域的目标识别与方位估计中。一阶 矩与形状有关,二阶矩显示曲线围绕直线平均值的扩展程度,三阶矩则是关于平 均值的对称性的测量。由二阶矩和三阶矩可以导出一组共7个不变矩。而不变矩 是图像的统计特性,满足平移、伸缩、旋转均不变的不变性,在图像识别领域得 到了广泛的应用。

那 么 , 在OpenCV 中,如何计算 一个图像的矩呢? 一般由 moments、 contourArea 、arcLength 这三个函数配合求取。

  • 使用moments 计算图像所有的矩(最高到3阶)
  • 使用contourArea来计算轮廓面积
  • 使用arcLength来计算轮廓或曲线长度 下面对其进行一一剖析。

8.4.1 矩的计算:momentsO函数

moments()函数用于计算多边形和光栅形状的最高达三阶的所有矩。矩用来计 算形状的重心、面积,主轴和其他形状特征,如7Hu不变量等。

Moments moments(InputArray array, bool binaryImage = false)
  • 第一个参数,InputArray类型的array,输入参数,可以是光栅图像(单通 道,8位或浮点的二维数组)或二维数组 (IN 或 N1)。
  • 第二个参数,bool类型的binaryImage,有默认值false。若此参数取 true, 则所有非零像素为1。此参数仅对于图像使用。
    需要注意的是,此参数的返回值返回运行后的结果。

8.4.2计算轮廓面积:contourArea(函数

contourArea()函数用于计算整个轮廓或部分轮廓的面积

double       contourArea(InputArray       contour,bool       oriented=false)
  • 第一个参数,InputArray类型的contour,输入的向量,二维点(轮廓顶点), 可以为std::vector 或 Mat 类型。
  • 第二个参数,bool类型的oriented,面向区域标识符。若其为true,该函数 返回一个带符号的面积值,其正负取决于轮廓的方向(顺时针还是逆时针)。 根据这个特性我们可以根据面积的符号来确定轮廓的位置。需要注意的是, 这个参数有默认值false, 表示以绝对值返回,不带符号。

8.4.3 计算轮廓长度:arcLengthO函数

arcLength(函数用于计算封闭轮廓的周长或曲线的长度。

double      arcLength(InputArray      curve,bool      closed)
  • 第一个参数,InputArray类型的curve,输入的二维点集,可以为std:vector或Mat 类型。
  • 第二个参数,bool 类型的closed, 一个用于指示曲线是否封闭的标识符, 有默认值closed, 表示曲线封闭。

8.4.4 综合示例程序:查找和绘制图像轮廓矩

学习完函数的讲解,让我们一起通过一个综合的示例程序,真正了解本节内 容的实战用法 。

namespace test56{Mat g_srcImage, g_grayImage;int g_nThresh = 100;int g_nMaxThresh = 255;RNG g_rng(12345);Mat g_cannyMat_output;std::vector<std::vector<Point>>g_vContours;std::vector<Vec4i>g_vHierarchy;//回调函数void on_ThreshChange(int, void*) {Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2); //边缘检测findContours(g_cannyMat_output, g_vContours, g_vHierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0)); //找到轮廓//计算矩std::vector<Moments>mu(g_vContours.size());for (int i = 0; i < g_vContours.size(); ++i) {mu[i] = moments(g_vContours[i], false);}//计算中心矩std::vector<Point2f>mc(g_vContours.size());for (int i = 0; i < g_vContours.size(); ++i) {mc[i] = Point2f(static_cast<float>(mu[i].m10 / mu[i].m00), static_cast<float>(mu[i].m01 / mu[i].m00));}//绘制轮廓Mat drawing = Mat::zeros(g_cannyMat_output.size(), CV_8UC3);for (int i = 0; i < g_vContours.size(); ++i) {Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, Point()); //绘制内层和外层轮廓circle(drawing, mc[i], 4, color, -1, 8, 0);//绘制圆}imshow("drawing", drawing);//计算轮廓面积std::cout << "\t";for (int i = 0; i < g_vContours.size(); ++i) {std::cout << ">Through m00 Areas[" << i << "]:" << mu[i].m00 << "Length = "<< arcLength(g_vContours[i], true)<< std::endl;std::cout << ">Through OpenCV m_00 Areas[" << i << "]" <<contourArea(g_vContours[i]) <<"Length = " << arcLength(g_vContours[i], true) << std::endl;Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));drawContours(drawing, g_vContours, i, color, 2, 8, g_vHierarchy, 0, Point());circle(drawing, mc[i], 4, color, -1, 8, 0);}}void Test() {g_srcImage = imread("image.jpg");cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3));namedWindow("srcImage");imshow("srcImage", g_srcImage);createTrackbar("value:", "srcImage", &g_nThresh, g_nMaxThresh,on_ThreshChange);waitKey(0);}}void Test56() {test56::Test();
}

在这里插入图片描述
在这里插入图片描述

8.5 分水岭算法

在许多实际运用中,我们需要分割图像,但无法从背景图像中获得有用信 息。分水岭算法 (watershed algorithm) 在这方面往往是非常有效的。此算法可 以将图像中的边缘转化成“山脉”,将均匀区域转化为“山谷”,这样有助于分 割目标。
分水岭算法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想 是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的 海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形 成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明:在每一个局部 极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深, 每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即 形成分水岭。

分水岭的计算过程是一个迭代标注过程。分水岭比较经典的计算方法是由 L.Vincent 提出的。在该算法中,分水岭计算分两个步骤: 一个是排序过程, 一个是淹没过程。首先对每个像素的灰度级进行从低到高的排序,然后在从低 到高实现淹没的过程中,对每一个局部极小值在h 阶高度的影响域采用先进先 出 (FIFO) 结构进行判断及标注。分水岭变换得到的是输入图像的集水盆图像, 集水盆之间的边界点,即为分水岭。显然,分水岭表示的是输入图像的极大值 点。

也就是说,分水岭算法首先计算灰度图像的梯度;这对图像中的“山谷”或 没有纹理的“盆地”(亮度值低的点)的形成是很有效的,也对“山头”或图像中 有主导线段的“山脉”(山脊对应的边缘)的形成有效。然后开始从用户指定点(或 者算法得到点)开始持续“灌注”盆地直到这些区域连成一片。基于这样产生的 标记就可以把区域合并到0一起,合并后的区域又通聚集的方式进行分割,好像 图像被“填充”起来一样。

8.5.1 实现分水岭算法:watershedO 函 数

函 数watershed 实现的分水岭算法是基于标记的分割算法中的 一种。在把图像 传给函数之前,我们需要大致勾画标记出图像中的期望进行分割的区域,它们被 标记为正指数。所以,每 一 个区域都会被标记为像素值1、2、3等,表示成为 一 个或者多个连接组件。这些标记的值可以使用findContours() 函 数 和drawContours()

函数由二进制的掩码检索出来。不难理解,这些标记就是即将绘制出来的分割区 域的“种子”,而没有标记清楚的区域,被置为0。在函数输出中,每 一 个标记中 的像素被设置为“种子”的值,而区域间的值被设置为- 1。

void watershed(InputArray image, InputOutputArray markers)
  • 第 一 个 参 数 ,InputArray 类 型 的src, 输入图像,即源图像,填Mat 类 的 对 象
    即可,且需为8位三通道的彩色图像。
  • 第 二 个 参 数 ,InputOutputArray 类 型 的markers, 函数调用后的运算结果存在 这里,输入/输出32位单通道图像的标记结果。即这个参数用于存放函数调用后 的输出结果,需和源图片有 一 样的尺寸和类型。

8.5.2 综合示例程序:分水岭算法

namespace test57 {Mat g_maskImage, g_srcImage;Point prevPt(-1, -1);static void on_Mouse(int event, int x, int y, int flags, void*) {if (x < 0 || x >= g_srcImage.cols || y < 0 || y >= g_srcImage.rows) {return;}if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON)) {prevPt = Point(-1, -1);}else if (event == EVENT_LBUTTONDOWN) {prevPt = Point(x, y);}else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) {Point pt(x, y);if (prevPt.x < 0) {prevPt = pt;}line(g_maskImage, prevPt, pt, Scalar::all(255), 5, 8, 0);line(g_srcImage, prevPt, pt, Scalar::all(255), 5, 8, 0);prevPt = pt;imshow("srcImage", g_srcImage);}}void Test() {g_srcImage = imread("mountain.jpg");imshow("srcImage", g_srcImage);Mat srcImage, grayImage;g_srcImage.copyTo(srcImage);cvtColor(g_srcImage, g_maskImage, COLOR_BGR2GRAY);cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR);g_maskImage = Scalar::all(0);setMouseCallback("srcImage", on_Mouse, 0);while (1) {int c = waitKey(1);  // 改为 1 来更流畅地显示if (c == 27) {break;}//恢复原图if ((char)c == '2') {g_maskImage = Scalar::all(0);srcImage.copyTo(g_srcImage);imshow("srcImage", g_srcImage);}else if ((char)c == '1') {int i, j, compCount = 0;std::vector<std::vector<Point>>contours;std::vector<Vec4i>hierarchy;//寻找轮廓findContours(g_maskImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);if (contours.empty()) {continue;}Mat maskImage(g_maskImage.size(), CV_32S); //复制掩膜maskImage = Scalar::all(0);for (int index = 0; index >= 0; index = hierarchy[index][0]) {compCount++;drawContours(maskImage, contours, index, Scalar::all(compCount), -1, 8, hierarchy, INT_MAX);}if (compCount == 0) continue;//生成随机颜色std::vector<Vec3b>colorTab;for (int i = 0; i < compCount; ++i) {uchar b = theRNG().uniform(0, 255);uchar g = theRNG().uniform(0, 255);uchar r = theRNG().uniform(0, 255);colorTab.push_back(Vec3b(b, g, r));}watershed(srcImage, maskImage); //分水岭算法//将分水岭图像遍历存入watershedImage中Mat watershedImage(maskImage.size(), CV_8UC3);for (int i = 0; i < maskImage.rows; ++i) {for (int j = 0; j < maskImage.cols; ++j) {int index = maskImage.at<int>(i, j);if (index == -1) {watershedImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0); }else if (index <= 0 || index > compCount) {watershedImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);}else {watershedImage.at<Vec3b>(i, j) = colorTab[index - 1];}}}//混合灰度图和分水岭效果图watershedImage = watershedImage * 0.5 + grayImage * 0.5;imshow("watershed transform", watershedImage);}}}
}void Test57() {test57::Test();
}

在这里插入图片描述

在这里插入图片描述

8.6 图像修补

在实际应用中,我们的图像常常会被噪声腐蚀,这些噪声或者是镜头上的灰 尘或水滴,或者是旧照片的划痕,或者由于图像的部分本身已经损坏。而“图像 修复”(Inpainting), 就是妙手回春,解决这些问题的良方。图像修复技术简单来说,就是利用那些已经被破坏区域的边缘,即边缘的颜色和结构,繁殖和混合到 损坏的图像中,以达到图像修补的目的。图8.34~8.36就是示例程序截图,演示 将图像中的字迹移除的效果。

8.6.1 实现图像修补:inpaint ( 函 数

在新版OpenCV 中,图像修补技术由inpaint 函数实现,它可以用来从扫描的 照片中清除灰尘和划痕,或者从静态图像或视频中去除不需要的物体。其原型声 明如下。

C++:void inpaint(InputArray src,InputArray inpaintMask,OutputArray
dst,double inpaintRadius,int flags)

  • 第 一 个参数,InputArray类 型 的src, 输入图像,即源图像,填Mat 类的对 象即可,且需为8位单通道或者三通道图像。
  • 第二个参数,InputArray类型的inpaintMask, 修复掩膜,为8位的单通道 图像。其中的非零像素表示需要修补的区域。
  • 第三个参数,OutputArray 类 型 的dst, 函数调用后的运算结果存在这里, 和源图片有一样的尺寸和类型。
  • 第四个参数,double 类型的 inpaintRadius,需要修补的每个点的圆形邻域, 为修复算法的参考半径。
  • 第五个参数,int 类型的flags,修补方法的标识符,可以是表8.4所示两者 之一。
标识符说明
INPAINT_NS基于Navier-Stokes方程的方法
INPAINT_TELEAAlexandru Telea方法

OpenCV2 中INPAINT_NS 和INPAINT_TELEA 标识符可以分别写作CV_ INPAINT_NS 和 CV_INPAINT_TELEA

8.6.2 综合示例程序:图像修补

函数和概念讲解完毕,下面我们依然是学习 一 个以本节所讲内容为核心的示 例程序,将本节所学内容付诸实践,融会贯通。此示例程序会先让我们在图像中 用鼠标绘制出白色的线条破坏图像,然后按下键盘按键【1】或【SPACE】 进 行 图 像修补操作。且如果对自己的绘制不够满意,可以按下键盘按键【2】恢复原始图 像。


namespace test58 {Mat g_srcImage, inpaintMask,srcImage;Point previousPoint(-1, -1);static void On_Mouse(int event,int x,int y,int flags,void*) {if (x < 0 || x >= g_srcImage.cols || y < 0 || y >= g_srcImage.rows) {return;}if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON)) {previousPoint = Point(-1, -1);}else if (event == EVENT_LBUTTONDOWN) {previousPoint = Point(x, y);}else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) {Point pt(x, y);if (previousPoint.x < 0) {previousPoint = pt;}line(inpaintMask, previousPoint, pt, Scalar::all(255), 5, 8, 0);line(g_srcImage, previousPoint, pt, Scalar::all(255), 5, 8, 0);previousPoint = pt;imshow("srcImage", g_srcImage);}}void Test() {g_srcImage = imread("sky.jpg");srcImage = g_srcImage.clone();inpaintMask = Mat::zeros(srcImage.size(), CV_8U);imshow("srcImage", g_srcImage);setMouseCallback("srcImage", On_Mouse, nullptr);while (1) {char c = waitKey(1);if (c == 27) break;if (c == '2') {inpaintMask = Scalar::all(0);srcImage.copyTo(g_srcImage);imshow("srcImage", g_srcImage);}if (c == '1') {Mat inpaintedImage;inpaint(g_srcImage, inpaintMask, inpaintedImage, 3, INPAINT_TELEA); imshow("fixed", inpaintedImage);}}}}void Test58() {test58::Test();
}

在这里插入图片描述

在这里插入图片描述

8.7 本章小结

本章中,我们先学习了查找轮并绘制轮廓,然后学习了如何寻找到物体的凸 包,接着是使用多边形来包围轮廓,以及计算一个图像的矩。在本章后面几节, 还学习了分水岭算法和图像修补操作的实现方法。

函数名称说明对应讲解章节
BoundingRect计算并返回指定点集最外面(up-right)的矩形边 界8.3.1
minAreaRect寻找可旋转的最小面积的包围矩形8.3.2
minEnclosingCircle利用一种迭代算法,对给定的2D点集,寻找面 积最小的可包围他们的圆形8.3.3
fitEllipse用椭圆拟合二维点集8.3.4
approxPolyDP用指定精度逼近多边形曲线8.3.5
moments计算多边形和光栅形状的最高达三阶的所有矩8.4.1
contourArea计算整个轮廓或部分轮廓的面积8.4.2
arcLength计算封闭轮廓的周长或曲线的长度8.4.3
watershed实现分水岭算法8.5.1
inpaint进行图像修补,从扫描的照片中清除灰尘和划痕, 或者从静态图像或视频中去除不需要的物体8.6.1

本章示例程序清单

示例程序序号程序说明对应章节
69轮廓查找8.1.3
70查找并绘制轮廓8.1.4
71凸包检测基础8.2.3
72寻找和绘制物体的凸包8.2.4
73创建包围轮廓的矩形边界8.3.6
74创建包围轮廓的圆形边界8.3.7
75使用多边形包围轮廓8.3.8
76图像轮廓矩8.4.4
77分水岭算法的使用8.5.2
78实现图像修补8.6.2

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

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

相关文章

.NET Core NPOI 导出图片到Excel指定单元格并自适应宽度

NPOI&#xff1a;支持xlsx&#xff0c;.xls&#xff0c;版本>2.5.3 XLS&#xff1a;HSSFWorkbook&#xff0c;主要前缀HSS&#xff0c; XLSX&#xff1a;XSSFWorkbook&#xff0c;主要前缀XSS&#xff0c;using NPOI.XSSF.UserModel; 1、导出Excel添加图片效果&#xff0…

SQL分类与数据类型整理

SQL分类与数据类型整理 SQL分类数据类型数值型整数型小数型布尔型字符串型日期型二进制型其他类型&#xff08;空间数据类型&#xff09; 总结 SQL&#xff0c;全称为Structured Query Language&#xff08;结构化查询语言&#xff09;&#xff0c;是一种高度专业化的计算机语言…

基于深度学习算法的AI图像视觉检测

基于人工智能和深度学习方法的现代计算机视觉技术在过去10年里取得了显著进展。如今&#xff0c;它被广泛用于图像分类、人脸识别、图像中物体的识别等。那么什么是深度学习&#xff1f;深度学习是如何应用在视觉检测上的呢&#xff1f; 什么是深度学习&#xff1f; 深度学习是…

30_Redis哨兵模式

在Redis主从复制模式中,因为系统不具备自动恢复的功能,所以当主服务器(master)宕机后,需要手动把一台从服务器(slave)切换为主服务器。在这个过程中,不仅需要人为干预,而且还会造成一段时间内服务器处于不可用状态,同时数据安全性也得不到保障,因此主从模式的可用性…

计算机网络 笔记 网络层1

网络层功能概述 主要的任务是把分组从源端传输到目的端&#xff0c;为分组交换网上的不同主句提供通信服务&#xff0c;网络层的传输单位是数据报。 主要的功能&#xff1b; 1&#xff0c;路由选择&#xff1a;路由选择指网络层根据特定算法&#xff0c;为数据包从源节点到目…

解决计算机管理无法连接远程电脑

症状 有时&#xff0c;我们会想用计算机管理&#xff08;Computer Management&#xff09;连接到一台远程Windows服务器&#xff0c;因为我们不想直接登录到远程服务器上操作。 然而&#xff0c;绝大多数情况下&#xff0c;如果你初次尝试连接&#xff0c;会得到如下的错误。 …

【Uniapp-Vue3】Prop校验与prop默认值用法及循环遍历数组对象

一、prop校验 如果我们在想要限制prop的类型&#xff0c;就可以在接收prop的时候对接收类型进行限制&#xff1a; defineProps({ 属性名:{ type:类型 } }) 需要注意类型的首字母大写 但是设置了传入参数类型限制并不能严格限制&#xff0c;只会在后台进行提示&#xff1a; 二、…

解决WordPress出现Fatal error: Uncaught TypeError: ftp_nlist()致命问题

错误背景 WordPress版本&#xff1a;wordpress-6.6.2-zh_CN WooCommerce版本&#xff1a;woocommerce.9.5.1 WordPress在安装了WooCommerce插件后&#xff0c;安装的过程中没有问题&#xff0c;在安装完成后提示&#xff1a; 此站点遇到了致命错误&#xff0c;请查看您站点管理…

qt-C++笔记之自定义继承类初始化时涉及到parents的初始化

qt-C笔记之自定义继承类初始化时涉及到parents的初始化 code review! 参考笔记 1.qt-C笔记之父类窗口、父类控件、对象树的关系 2.qt-C笔记之继承自 QWidget和继承自QObject 并通过 getWidget() 显示窗口或控件时的区别和原理 3.qt-C笔记之自定义类继承自 QObject 与 QWidget …

ElasticSearch | Elasticsearch与Kibana页面查询语句实践

关注&#xff1a;CodingTechWork 引言 在当今大数据应用中&#xff0c;Elasticsearch&#xff08;简称 ES&#xff09;以其高效的全文检索、分布式处理能力和灵活的查询语法&#xff0c;广泛应用于各类日志分析、用户行为分析以及实时数据查询等场景。通过 ES&#xff0c;用户…

Java聊天小程序

拟设计一个基于 Java 技术的局域网在线聊天系统,实现客户端与服务器之间的实时通信。系统分为客户端和服务器端两类,客户端用于发送和接收消息,服务器端负责接收客户端请求并处理消息。客户端通过图形界面提供用户友好的操作界面,服务器端监听多个客户端的连接并管理消息通…

【Redis学习 | 第5篇】Redis缓存 —— 缓存的概念 + 缓存穿透 + 缓存雪崩 + 缓存击穿

文章目录 完成任务1. 什么是缓存2. 添加商户缓存3. 缓存更新策略3.1 主动更新 4. 缓存穿透5. 缓存雪崩6. 缓存击穿6.1 使用互斥锁查询商铺信息6.2 使用逻辑过期查询商铺信息 7. 封装 Redis 工具类 完成任务 1. 什么是缓存 缓存&#xff1a;数据交换的缓冲区&#xff08;Cache…

单元测试概述入门

引入 什么是测试&#xff1f;测试的阶段划分&#xff1f; 测试方法有哪些&#xff1f; 1.什么是单元测试&#xff1f; 单元测试&#xff1a;就是针对最小的功能单元&#xff08;方法&#xff09;&#xff0c;编写测试代码对其正确性进行测试。 2.为什么要引入单元测试&#x…

微服务的配置共享

1.什么是微服务的配置共享 微服务架构中&#xff0c;配置共享是一个重要环节&#xff0c;它有助于提升服务间的协同效率和数据一致性。以下是对微服务配置共享的详细阐述&#xff1a; 1.1.配置共享的概念 配置共享是指在微服务架构中&#xff0c;将某些通用或全局的配置信息…

基于改进粒子群优化的无人机最优能耗路径规划

目录 1. Introduction2. Preliminaries2.1. Particle Swarm Optimization Algorithm2.2. Deep Deterministic Policy Gradient2.3. Calculation of the Total Output Power of the Quadcopter Battery 3.OptimalEnergyConsumptionPathPlanningBasedonPSO-DDPG3.1.ProblemModell…

HQChart使用教程30-K线图如何对接第3方数据44-DRAWPIE数据结构

HQChart使用教程30-K线图如何对接第3方数据44-DRAWPIE数据结构 效果图DRAWPIEHQChart代码地址后台数据对接说明示例数据数据结构说明效果图 DRAWPIE DRAWPIE是hqchart插件独有的绘制饼图函数,可以通过麦语法脚本来绘制一个简单的饼图数据。 饼图显示的位置固定在右上角。 下…

Wi-Fi Direct (P2P)原理及功能介绍

目录 Wi-Fi Direct &#xff08;P2P&#xff09;介绍Wi-Fi Direct P2P 概述P2P-GO&#xff08;P2P Group Owner&#xff09;工作流程 wifi-Direct使用windows11 wifi-directOpenwrtwifi的concurrent mode Linux环境下的配置工具必联wifi芯片P2P支持REF Wi-Fi Direct &#xff…

树莓派-5-GPIO的应用实验之GPIO的编码方式和SDK介绍

文章目录 1 GPIO编码方式1.1 管脚信息1.2 使用场合1.3 I2C总线1.4 SPI总线2 RPI.GPIO2.1 PWM脉冲宽度调制2.2 静态函数2.2.1 函数setmode()2.2.2 函数setup()2.2.3 函数output()2.2.4 函数input()2.2.5 捕捉引脚的电平改变2.2.5.1 函数wait_for_edge()2.2.5.2 函数event_detect…

【数据库】四、数据库管理与维护

文章目录 四、数据库管理与维护1 安全性管理2 事务概述3 并发控制4 备份与恢复管理 四、数据库管理与维护 1 安全性管理 安全性管理是指保护数据库&#xff0c;以避免非法用户进行窃取数据、篡改数据、删除数据和破坏数据库结构等操作 三个级别认证&#xff1a; 服务器级别…

Observability:将 OpenTelemetry 添加到你的 Flask 应用程序

作者&#xff1a;来自 Elastic jessgarson 待办事项列表可以帮助管理与假期计划相关的所有购物和任务。使用 Flask&#xff0c;你可以轻松创建待办事项列表应用程序&#xff0c;并使用 Elastic 作为遥测后端&#xff0c;通过 OpenTelemetry 对其进行监控。 Flask 是一个轻量级…