文章目录

  • 1.查找并绘制轮廓
    • 1.1 寻找轮廓:findContours() 函数
    • 1.2 绘制轮廓:drawContours() 函数
    • 1.3 示例程序:轮廓查找
    • 1.4 综合示例:查找并绘制轮廓
  • 2.寻找物体的凸包
    • 2.1 凸包
    • 2.2 寻找凸包:convexHull() 函数
    • 2.3 示例程序:凸包检测基础
    • 2.4 综合示例:寻找和绘制物体的凸包
  • 3.使用多边形将轮廓包围
    • 3.1 返回外部矩形边界:boundingRect() 函数
    • 3.2 寻找最小包围矩形:minAreaRect() 函数
    • 3.3 寻找最小包围圆形:minEnclosingCircle() 函数
    • 3.4 用椭圆拟合二维点集:fitEllipse() 函数
    • 3.5 逼近多边形曲线:approxPolyDP() 函数
    • 3.6 基础示例程序:创建包围轮廓的矩形边界
    • 3.7 基础示例程序:创建包围轮廓的圆形边界
    • 3.8 综合示例:使用多边形包围轮廓
  • 4.图像的矩
    • 4.1 矩的计算:moments() 函数
    • 4.2 计算轮廓面积:contourArea() 函数
    • 4.3 计算轮廓长度:arcLength() 函数
    • 4.4 综合示例:查找和绘制图像轮廓矩
  • 5. 分水岭算法
    • 5.1 实现分水岭算法:watershed() 函数
    • 5.2 综合示例程序:分水岭算法
  • 6.图像修补
    • 6.1 实现图像修补:inpaint() 函数
    • 6.2 综合示例:图像修补

1.查找并绘制轮廓

  一个轮廓一般对应一系列的点,也就是图像中的一条曲线。其表示方法可能根据不同的情况而有所不同。

1.1 寻找轮廓:findContours() 函数

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

void findContours(InputArray image,  OutputArrayOfArray contours,    //检测到的轮廓数组OutputArray hierarchy,        //可选的输出向量int mode,              //轮廓检索模式int method,             //轮廓的近似方法Point offset = Point()        //每个轮廓点可选的偏移量
)

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

  ● 第五个参数:int 类型的 method,为轮廓的近似办法,取值如图:

  ● 第六个参数:Point 类型的 offset,每个轮廓点的可选偏移量,有默认值 Point() ,对ROI 图像中找出的轮廓,并要在整个图像中进行分析时,这个参数便可以派上用场。
  findContours 经常与 drawContours 配合使用。
  调用示例:

 vector<vector<Point> > contours;findContours(image,contours,    //轮廓数组RETR_EXTERNAL,        //获取外层轮廓CHAIN_APPROX_NONE   //获取每个轮廓的每个像素);

1.2 绘制轮廓:drawContours() 函数

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

void drawContours(InputArray image,InputArrayOfArrays contours,  //所有的输入轮廓int contourIdx,            //轮廓绘制的指示变量const Scalar& color,     //线条颜色int thickness = 1,       //线粗int lineType = 8,      //线型InputArray hierarchy = noArray(),      //可选的层次结构信息     int maxLevel = INT_MAX,            //用于绘制轮廓的最大等级Point offset = Point()            //可选的轮廓偏移参数
)

  ● 第一个参数:InputArray 类型的 image,输入图像。
  ● 第二个参数:OutputArrayOfArrays 类型的 contours,检测到的轮廓。每个轮廓存储为一个点向量,即用 point 类型的 vector 表示。
  ● 第三个参数:int 类型的 contourIdx,轮廓绘制的指示变量。如果其值为负,则绘制所有轮廓。
  ● 第四个参数:const Scalar& 类型的 color,轮廓的颜色。
  ● 第五个参数:int 类型的 thickness,轮廓线条的粗细,有默认值 1,。如果其值为负(如 thickness = cv_filled),便会绘制在轮廓的内部。
  ● 第六个参数:int 类型的 lineType,线条的类型,有默认值 8,可选的类型如图:

  ● 第七个参数: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       //线宽为 3);

1.3 示例程序:轮廓查找

#include<opencv2/opencv.hpp>
#include<time.h>
#include<iostream>using namespace std;
using namespace cv;int main() {system("color 1A");//载入原图,且必须为二值图模式载入Mat srcImage = imread("1.jpg", 0);imshow("原始图", srcImage);Mat dstImage = Mat::zeros(srcImage.rows, srcImage.cols, CV_8UC3);//srcImage 取大于阈值 119 的那部分srcImage = srcImage > 119;imshow("取阈值后的原始图", srcImage);//定义轮廓和层次结构vector<vector<Point> > contours;vector<Vec4i> hierarchy;//查找轮廓findContours(srcImage, contours, hierarchy, RETR_CCOMP,CHAIN_APPROX_SIMPLE);//遍历所有的顶层轮廓,以随机颜色绘制出每个连接组件int index = 0;for (; index >= 0; index = hierarchy[index][0]) {Scalar color(rand() & 255, rand() & 255, rand() & 255);drawContours(dstImage, contours, index, color, FILLED, 8, hierarchy, 0);}imshow("轮廓图", dstImage);waitKey(0);return 0;
}

原图:

取阈值后:

轮廓图 :

1.4 综合示例:查找并绘制轮廓

#include<opencv2/opencv.hpp>
#include<time.h>
#include<iostream>using namespace std;
using namespace cv;#define WINDOW_NAME1 "原始窗口"
#define WINDOW_NAME2 "轮廓图"Mat g_srcImage, g_grayImage;
int g_nThresh = 80;
int g_nThresh_max = 255;
RNG g_rng(12345);
Mat g_cannyMat_output;
vector<vector<Point> > g_vContours;
vector<Vec4i> g_vHierarchy;void on_ThreshChange(int, void *) {//用 canny 算子检测边缘Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3);//寻找轮廓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(WINDOW_NAME2, drawing);
}int main() {system("color 1A");g_srcImage = imread("1.jpg");//转成灰度并模糊化降噪cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3));namedWindow(WINDOW_NAME1, 1);imshow(WINDOW_NAME1, g_srcImage);createTrackbar("canny 阈值", WINDOW_NAME1, &g_nThresh, g_nThresh_max, on_ThreshChange);on_ThreshChange(0, 0);waitKey(0);return 0;
}

程序运行图:


2.寻找物体的凸包

2.1 凸包

  凸包,简单来说,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它是能包含点集中所有点的。理解物体形状或轮廓的一种比较有效的方法就是计算一个物体的凸包,然后计算其凸缺陷。很多复杂物体的特性能很好地被这种缺陷表现出来。
  如图所示,我们用人手图来说明凸缺陷这一概念。手周围深色的线描画出了凸包,A 到 H 被标出的区域是凸包的各个 “缺陷”。这些凸缺陷提供了手及手状态的特征表现的方法。
              

2.2 寻找凸包:convexHull() 函数

void convexHull(InputArray points,   //输入的二维点集OutputArray hull,      //找到的凸包bool clockwise = false,     //操作方向标识符bool returnPoints = true  //操作标识符
)

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

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

  程序首先随机生成 3 ~ 103 个坐标值随机的彩色点,然后利用 convexHull,对由这些点连接起来的图形求凸包

#include<opencv2/opencv.hpp>
#include<time.h>
#include<iostream>using namespace std;
using namespace cv;int main() {system("color 1A");Mat image(600, 600, CV_8UC3);RNG& rng = theRNG();//循环,按下 ESC,Q,q 键退出,否则有键按下便一直更新while (1){char key;int count = (unsigned)rng % 100 + 3;    //随机生成的点的数量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);}//检测凸包vector<int> hull; //凸包中的点被包含在点集中,hull 中存的是 points 中的下标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 = (int)hull.size(); //凸包的边数Point point0 = points[hull[hullCount - 1]]; //连接凸包边的点坐标//绘制凸包的边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("凸包检测显示", image);key = (char)waitKey();if (key == 27 || key == 'q' || key == 'Q') {break;}}return 0;
}

程序运行图:

2.4 综合示例:寻找和绘制物体的凸包

#include<opencv2/opencv.hpp>
#include<time.h>
#include<iostream>using namespace std;
using namespace cv;#define WINDOW_NAME1 "原始窗口"
#define WINDOW_NAME2 "效果图窗口"Mat g_srcImage, g_grayImage;
int g_nThresh = 50;
int g_maxThresh = 255;
RNG g_rng = (12345);
Mat g_srcImage_copy = g_srcImage.clone();
Mat g_thresholdImage_output;
vector<vector<Point> > g_vContours;
vector<Vec4i> g_vHieraichy;void on_ThreshChange(int, void *) {//对图像进行二值化,控制阈值threshold(g_grayImage, g_thresholdImage_output, g_nThresh, 255, THRESH_BINARY);//寻找轮廓findContours(g_thresholdImage_output, g_vContours, g_vHieraichy,RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//遍历每个轮廓,寻找其凸包vector<vector<Point> > hull(g_vContours.size());for (unsigned 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 (unsigned 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, vector<Vec4i>(), 0, Point());   //画轮廓drawContours(drawing, hull, i, color, 1, 8, vector<Vec4i>(), 0, Point());    //画凸包}imshow(WINDOW_NAME2, drawing);
}int main() {system("color 1A");g_srcImage = imread("1.jpg");//将原图转换成灰度图并进行模糊降噪cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_grayImage, Size(3, 3));namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);imshow(WINDOW_NAME1, g_srcImage);createTrackbar("阈值", WINDOW_NAME1, &g_nThresh, g_maxThresh, on_ThreshChange);on_ThreshChange(0, 0);waitKey(0);return 0;
}

原图:

阈值为 51 时:

阈值为 175 时:

阈值为 236 时:

3.使用多边形将轮廓包围

3.1 返回外部矩形边界:boundingRect() 函数

  此函数计算并返回指定点集最外边(up-right)的矩形边界。

Rect boundingRect(InputArray points)

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

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

  此函数用于对给定的 2D 点集,寻找可旋转的最小面积的包围矩形。

RotatedRect minAreaRect(InputArray points)

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

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

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

void minEnclosingCircle(InputArray points,Point2f& center,float& radius
)

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

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

RotatedRect fitEllipse(InputArray points)

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

3.5 逼近多边形曲线:approxPolyDP() 函数

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

void approxPolyDP(InputArray curve,OutputArray approxCurve,double epsilon,bool closed
)

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

3.6 基础示例程序:创建包围轮廓的矩形边界

#include<opencv2/opencv.hpp>
#include<time.h>
#include<iostream>using namespace std;
using namespace cv;int main() {system("color 2F");Mat image(600, 600, CV_8UC3);RNG& rng = theRNG();while (1){int count = rng.uniform(3, 103);   //随机生成点的数量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);}//对给定的 2D 点集,寻找最小面积的包围矩形RotatedRect box = minAreaRect(Mat(points));Point2f vertex[4];box.points(vertex);//绘制出随机颜色的点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);}//绘制出最小面积包围的矩形for (int i = 0; i < 4; i++) {line(image, vertex[i], vertex[(i + 1) % 4], Scalar(rng.uniform(0, 255),rng.uniform(0, 255), rng.uniform(0, 255)), 2, LINE_AA);}imshow("矩形包围示例", image);char key = (char)waitKey();if (key == 27 || key == 'q' || key == 'Q') {break;}}return 0;
}

运行图:

3.7 基础示例程序:创建包围轮廓的圆形边界

#include<opencv2/opencv.hpp>
#include<time.h>
#include<iostream>using namespace std;
using namespace cv;int main() {system("color 2F");Mat image(600, 600, CV_8UC3);RNG& rng = theRNG();while (1) {int count = rng.uniform(3, 103);  //随机生成点的数量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);}//对给定的 2D 点集,寻找最小面积的包围圆形Point2f center;float radius = 0;minEnclosingCircle(Mat(points), center, radius);//绘制出随机颜色的点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);}//绘制出最小面积的包围圆circle(image, center, cvRound(radius), Scalar(rng.uniform(0, 255),rng.uniform(0, 255), rng.uniform(0, 255)), 2, LINE_AA);imshow("圆形包围示例", image);char key = (char)waitKey();if (key == 27 || key == 'q' || key == 'Q') {break;}}return 0;
}

运行图:

3.8 综合示例:使用多边形包围轮廓

#include<opencv2/opencv.hpp>
#include<time.h>
#include<iostream>using namespace std;
using namespace cv;#define WINDOW_NAME1 "原始图窗口"
#define WINDOW_NAME2 "效果图窗口"Mat g_srcImage;
Mat g_grayImage;
int g_nThresh = 50;    //阈值
int g_nMaxThresh = 255;    //阈值最大值
RNG g_rng(12345);   //随机数生成器void on_Contours(int , void *) {Mat threshold_output;vector<vector<Point> > contours;vector<Vec4i> hierarchy;//使用 Threshold 检测边缘threshold(g_grayImage, threshold_output, g_nThresh, 255, THRESH_BINARY);//找出轮廓findContours(threshold_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//多边形逼近轮廓 + 获取矩形和圆形边界框,指定数组大小:contours.size()vector<vector<Point> > contours_poly(contours.size());vector<Rect> boundRect(contours.size());vector<Point2f> center(contours.size());vector<float> radius(contours.size());//一个循环,遍历所有部分,进行本程序最核心的操作for (unsigned int i = 0; i < contours.size(); i++) {//用指定精度逼近多边形曲线approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);//计算点集的最外面(up-right)矩形边界boundRect[i] = boundingRect(Mat(contours_poly[i]));//对于给定的 2D 点集,寻找最小面积的包围圆形minEnclosingCircle(contours_poly[i], center[i], radius[i]);}//绘制多边形轮廓 + 包围的矩形框 + 圆形框Mat drawing = Mat::zeros(threshold_output.size(), CV_8UC3);for (unsigned int i = 0; i < contours.size(); i++) {Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));drawContours(drawing, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, Point());    //绘制轮廓rectangle(drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0); //绘制矩形circle(drawing, center[i], (int)radius[i], color, 2, 8, 0);   //绘制圆}namedWindow(WINDOW_NAME2, WINDOW_AUTOSIZE);imshow(WINDOW_NAME2, drawing);
}int main() {system("color 2F");g_srcImage = imread("1.jpg", 1);if (!g_srcImage.data) {printf("图像读取失败!");return 0;}//转换为灰度图并进行平滑cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_srcImage,Size(3,3));namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);imshow(WINDOW_NAME1, g_srcImage);createTrackbar("阈值", WINDOW_NAME1, &g_nThresh, g_nMaxThresh, on_Contours);on_Contours(0, 0);waitKey(0);return 0;
}

阈值为 16 时:

阈值为 100 时:

阈值为 234 时:

4.图像的矩

  一个从一副数字图像计算出来的矩集,通常描述了该图像形状的全局特征,并提供了大量的关于该图像不同类型的几何特性信息,比如大小、位置、方向及形状等。一阶矩与形状有关,二阶矩显示曲线围绕直线平均值的扩展程度,三阶矩则是关于平均值的对称性的测量。由二阶矩和三阶矩可以导出一组共 7 个不变矩。不变矩是图像中的统计特性,满足平移、伸缩、旋转均不变的不变性。
  在 OpenCV 中一般用 moments、contourArea、arcLength 这三个函数配合求取矩。
  ● 使用 moments 计算图像中的所有矩。
  ● 使用 contourArea 来计算轮廓面积。
  ● 使用 arcLength 来计算轮廓或曲线长度。

4.1 矩的计算:moments() 函数

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

Moments moments(InputArray array,bool binaryImage = false
)

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

4.2 计算轮廓面积:contourArea() 函数

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

double contourArea(InputArray contour,bool oriented = false
)

  ● 第一个参数:InputArray 类型的 contour,输入的向量,二维点(轮廓顶点),可以为 std::vector 或 Mat 类型。
  ● 第二个参数:bool 类型的 oriented,面向区域标识符。若其为 true,该函数一个带符号的面积值,其正负号取决于轮廓的方向(顺时针还是逆时针)。这个参数有默认值 false ,表示以绝对值返回,不带符号。
  其调用示例如下:

 vector<Point> contour;contour.push_back(Point2f(0, 0));contour.push_back(Point2f(10, 0));contour.push_back(Point2f(10, 10));contour.push_back(Point2f(5, 4));double area0 = contourArea(contour);vector<Point> approx;approxPolyDP(contour, approx, 5, true);double area1 = contourArea(approx);cout << "area0 = " << area0 << endl << "area1 = " << area1 << endl <<"approx poly vertices" << approx.size() << endl;

4.3 计算轮廓长度:arcLength() 函数

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

double arcLength(InputArray curve,bool closed
)

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

4.4 综合示例:查找和绘制图像轮廓矩

#include<opencv2/opencv.hpp>
#include<time.h>
#include<iostream>using namespace std;
using namespace cv;#define WINDOW_NAME1 "原始图"
#define WINDOW_NAME2 "图像轮廓"Mat g_srcImage;
Mat g_grayImage;
int g_nThresh = 100;   //阈值
int g_nMaxThresh = 255;    //阈值最大值
RNG g_rng(12345);   //随机数生成器
Mat g_cannyMat_output;
vector<vector<Point> > g_vContours;
vector<Vec4i> g_vHierarchy;void on_ThreshChange(int , void *) {//使用 canny 检测边缘Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3);//找到轮廓findContours(g_cannyMat_output, g_vContours, g_vHierarchy,RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//计算矩vector<Moments> mu(g_vContours.size());for (unsigned int i = 0; i < g_vContours.size(); i++) {mu[i] = moments(g_vContours[i], false);}//计算中心矩vector<Point2f> mc(g_vContours.size());for (unsigned 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);//通过 m00 计算轮廓面积并且和 OpenCV 函数比较 且绘制出轮廓printf("\t 输出内容:面积和轮廓长度\n");for (unsigned int i = 0; i < g_vContours.size(); i++) {double c1 = contourArea(g_vContours[i]);double l1 = arcLength(g_vContours[i], true);printf(" > 通过 m00 计算出轮廓 [%d] 的面积:(M00) = %.2f\n",i,mu[i].m00);printf(" OpenCV 函数计算出的面积= %.2f,长度:%.2f \n\n",c1,l1);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(0, 0));circle(drawing, mc[i], 4, color, -1, 8, 0);}namedWindow(WINDOW_NAME2, WINDOW_AUTOSIZE);imshow(WINDOW_NAME2, drawing);
}int main() {system("color 2F");g_srcImage = imread("1.jpg", 1);if (!g_srcImage.data) {printf("图像读取失败!");return 0;}//转换为灰度图并进行平滑cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);blur(g_grayImage, g_srcImage,Size(3,3));namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);imshow(WINDOW_NAME1, g_srcImage);createTrackbar("阈值", WINDOW_NAME1, &g_nThresh, g_nMaxThresh, on_ThreshChange);on_ThreshChange(0, 0);waitKey(0);return 0;
}

阈值为 100 时:


阈值为 227 时:

5. 分水岭算法

  分水岭算法可将图像中的边缘转化成 “山脉”,将均匀区域转化为 “山谷”,这样有助于分割目标。
  分水岭算法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中没一点像素的灰度值表示该店的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。
  分水岭的计算过程是一个迭代标注过程。一种比较经典的分水岭计算方法分为两个步骤:一个是排序过程,一个是淹没过程。首先对每个像素的灰度级进行从低到高的排序,然后在从低到高实现淹没过程中,对每一个局部极小值在 h 阶高度的影响域采用先进先出结构进行判断及标注。分水岭变换得到的是输入图像的集水盆图像,集水盆之间的边界点,即为分水岭。显然,分水岭表示的是输入图像的极大值点。

5.1 实现分水岭算法:watershed() 函数

  函数 watershed() 实现的分水岭算法是基于标记的分割算法中的一种。在把图像传给函数之前,我们需要大致勾画标记出图像中的期望进行分割的区域,它们被标记为正指数。所以,每一个区域都会被标记为像素值 1、2、3 等,表示成为一个或多个连接组件。这些标记的值可以使用 findContours() 函数和 drawContours() 函数由二进制的掩码检索出来。这些标记就是即将绘制出来的分割区域的 “种子”,而没有标记清楚的区域被置为 0。在函数输出中,每一个标记中的像素被设置为 “种子” 的值,而区域间的值被设置为 -1。

void watershed(InputArray image, InputOutputArray markers)

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

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

#include<opencv2/opencv.hpp>
#include<time.h>
#include<iostream>using namespace std;
using namespace cv;#define WINDOW_NAME "程序窗口"Mat g_maskImage, g_srcImage;
Point prevPt(-1, -1);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(WINDOW_NAME, g_srcImage);//imshow("掩膜图", g_maskImage);}
}void showHelpText() {printf("/t/t 按键操作说明:\n\n");printf("/t/t/t按键 1 或 space --- 运行分水岭分割算法\n");printf("/t/t/t按键 2 或 --- 恢复原图\n");printf("/t/t/t按键 ESC --- 退出程序\n");
}int main() {system("color 2F");g_srcImage = imread("1.jpg", 1);imshow(WINDOW_NAME, g_srcImage);Mat srcImage, grayImage;   //srcImage 用于恢复原图g_srcImage.copyTo(srcImage);cvtColor(g_srcImage, g_maskImage, COLOR_BGR2GRAY);cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR);g_maskImage = Scalar::all(0);showHelpText();//设置鼠标回调函数setMouseCallback(WINDOW_NAME, on_Mouse, 0);while (1) {int c = waitKey(0);if ((char)c == 27) { //当按下 ESC 时退出break;}//若按下 2 键,则恢复原图if ((char)c == '2') {g_maskImage = Scalar::all(0);srcImage.copyTo(g_srcImage);imshow(WINDOW_NAME, g_srcImage);}//若按下 1 键或空格键,则进行处理if ((char)c == '1' || (char)c == ' ') {int i, j, compCount = 0;vector<vector<Point> > contours;vector<Vec4i> hieraichy;//寻找轮廓findContours(g_maskImage, contours, hieraichy,RETR_CCOMP, CHAIN_APPROX_SIMPLE);//轮廓为空时处理if (contours.empty()) {continue;}//复制掩膜Mat maskImage(g_srcImage.size(), CV_32S);maskImage = Scalar::all(0);//循环绘制出轮廓for (int index = 0; index >= 0; index = hieraichy[index][0], compCount++) {drawContours(maskImage, contours, index, Scalar::all(compCount + 1),-1, 8, hieraichy, INT_MAX);}//compCount 为零时处理if (compCount == 0) {continue;}//生成随机颜色vector<Vec3b> colorTab;for (int i = 0; i < compCount; i++) {int b = theRNG().uniform(0, 255);int g = theRNG().uniform(0, 255);int r = theRNG().uniform(0, 255);colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));}//计算处理时间并输出到窗口中double dTime = (double)getTickCount();watershed(srcImage, maskImage);dTime = (double)getTickCount() - dTime;printf("\t处理时间 = %gms\n", dTime * 1000. / getTickFrequency());//双重循环,将分水岭图像遍历存入 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(255, 255, 255);}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);}}return 0;
}

原图:

运行效果图:

6.图像修补

  图像修复技术简单来说,就是利用那些已经破坏区域的边缘,即边缘的颜色和结构,繁殖和混合到损坏的图像中,以达到修补的目的。

6.1 实现图像修补:inpaint() 函数

  inpaint() 函数可用来从扫描的图片中清除灰尘和划痕,或者从静态图像或视频中去除不需要的物体。其原型申明如下:

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,修补方法的标识符,可以是下表中所示两者之一。

6.2 综合示例:图像修补

#include<opencv2/opencv.hpp>
#include<time.h>
#include<iostream>using namespace std;
using namespace cv;#define WINDOW_NAME1 "原始图"
#define WINDOW_NAME2 "修补后的效果图"Mat srcImage1, inpaintMask;
Point previousPoint(-1, -1);    //原来的点坐标void on_Mouse(int event, int x, int y, int flags, void*) {//左键弹起消息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(srcImage1, previousPoint, pt, Scalar::all(255), 5, 8, 0);previousPoint = pt;imshow(WINDOW_NAME1, srcImage1);}
}void showHelpText() {printf("/t/t 按键操作说明:\n\n");printf("/t/t/t按键 1 或 space --- 运行图像修复算法\n");printf("/t/t/t按键 2 或 --- 恢复原图\n");printf("/t/t/t按键 ESC --- 退出程序\n");
}int main() {system("color 2F");//载入原图并进行掩膜的初始化Mat srcImage = imread("1.jpg", -1);if (!srcImage.data) {printf("图像读取失败!");return 0;}srcImage1 = srcImage.clone();inpaintMask = Mat::zeros(srcImage1.size(), CV_8U);imshow(WINDOW_NAME1, srcImage1);showHelpText();//设置鼠标回调消息setMouseCallback(WINDOW_NAME1, on_Mouse, 0);while (1) {char c = (char)waitKey();if (c == 27) {break;}//键值为 2 ,恢复原始图像if (c == '2') {inpaintMask = Scalar::all(0);srcImage.copyTo(srcImage1);imshow(WINDOW_NAME1, srcImage1);}//键值为 1 或者空格,进行图像修补操作if (c == '1' || c == ' ') {Mat inpaintedImage;inpaint(srcImage1, inpaintMask, inpaintedImage, 3, INPAINT_TELEA);imshow(WINDOW_NAME2, inpaintedImage);}}return 0;
}

原始图:

效果图:

图像轮廓、凸包、图像的矩、分水岭算法、图像修补相关推荐

  1. 《OpenCV3编程入门》学习笔记8 图像轮廓与图像分割修复(四)图像的矩

    8.4 图像的矩 从一幅数字图形中计算出来的矩集,通常描述了该图像形状的全局特征,并提供了大量关于该图像不同类型的几何特性信息,如大小.位置.方向.形状等 (1)一阶矩与形状有关 (2)二阶矩显示曲线 ...

  2. 《OpenCV3编程入门》学习笔记8 图像轮廓与图像分割修复(六)图像修补

    8.6 图像修补 基本思想:   利用已经被破坏区域的边缘,即边缘的颜色和结构,繁殖和混合到损坏的图像中,达到图像修补的目的. 8.6.1 实现图像修补:inpaint()函数 1.作用:   用来从 ...

  3. 三维空间图像轮廓 c语言,三维模型轮廓线抽取算法.doc

    三维模型轮廓线抽取算法 第 6 卷 (A 版) 第 2 期2001 年 2 月中国图象图形学报Jo u rn a l o f Im age an d G rap h ic sV o l. 6 (A ) ...

  4. OpenCV4.5.5学习笔记(十七):分水岭算法watershed(),图像修补inpaint()

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.分水岭算法watershed() 二.图像修补inpaint() 总结 前言 笔者本科时候有幸接触了OpenCV3 ...

  5. 一种新型鱼眼图像轮廓提取算法

    from: http://www.scimao.com/read/2307651     摘 要:提取鱼眼图像轮廓是利用鱼眼图像的前提.传统提取鱼眼图像轮廓的扫描线逼近法对噪点抑制能力不强,精度差.本 ...

  6. 《OpenCv视觉之眼》Python图像处理十二 :Opencv图像轮廓提取之基于一阶导数的Roberts算法、Prewitt算法及Sobel算法

    本专栏主要介绍如果通过OpenCv-Python进行图像处理,通过原理理解OpenCv-Python的函数处理原型,在具体情况中,针对不同的图像进行不同等级的.不同方法的处理,以达到对图像进行去噪.锐 ...

  7. 《OpenCV3编程入门》学习笔记八:图像轮廓与分割

    一:内容介绍 本节主要介绍OpenCV的imgproc模块的图像轮廓与分割部分: 1. 查找并绘制轮廓 2. 寻找物体的凸包 3. 使用多边形将轮廓包围 4. 图像的矩 5. 分水岭算法 6. 图像修 ...

  8. OpenCV学习笔记(九)——图像轮廓(下)

    <OpenCV轻松入门:面向Python>学习笔记(九) 1-3 查找并绘制轮廓.矩特性及Hu矩 4-5 轮廓拟合及凸包 6. 利用形状场景算法比较轮廓 6.1 计算形状场景距离 6.2 ...

  9. Python+OpenCV:图像轮廓

    Python+OpenCV:图像轮廓 轮廓是什么? 轮廓可以简单地解释为一条连接所有连续点(沿边界)的曲线,具有相同的颜色和强度. 轮廓线是形状分析.目标检测和识别的重要工具. 为了获得更好的精度,可 ...

  10. openCV专栏(八):图像轮廓:绘制轮廓

    OPENCV基础操作 提示:本专栏所用版本仅供参考,其他版本也可 库 版本 python Python 3.9.3 opencv 4.5.5 matplotlib 3.4.3 numpy 1.19.5 ...

最新文章

  1. [BZOJ 3211]花神游历各国(并查集+树状数组)
  2. 第一个python去掉行号
  3. pdf百度云下载 python编程 从数据分析到数据科学_python零基础入门教程,不同方向的,这很重要...
  4. 【业务知识】数字档案馆建设内容
  5. 深度学习-KNN,K近邻算法简介
  6. 实战:Redis 集群模式(下)
  7. 蓝牙广播错误码3_蓝牙简介—物理层(PHY)
  8. atomQQ 笔记 之 列表元素的异步加载
  9. Windows 8实例教程系列 - 开篇
  10. 知网下载学位论文PDF版本的一个方法
  11. 如何在计算机上设置禁止游戏,如何禁止玩电脑游戏 屏蔽网络游戏的方法
  12. MongoDB+模板引擎 项目学习 ---学生档案管理
  13. 展开运算符和object.assign()的区别
  14. 2023最新SSM计算机毕业设计选题大全(附源码+LW)之java学生宿舍管理系统15pjb
  15. 刚刚,2022中科院分区表发布(附下载)
  16. Unity3D个人版更换黑色皮肤
  17. 猿(媛)来你也在这里!!
  18. from .cv2 import * ImportError: libGL.so.1: cannot open shared object file: No such file or directo
  19. 深度剖析陈晓和贝恩之阴谋
  20. ansys显示没有提供服务器,ansys链接不到本地服务器

热门文章

  1. 开源!开源!我写的Anto.exe C#代码自动生成工具.欢迎下载。。
  2. Java基础篇:异常处理
  3. 真牛X!这款通用数据库连接工具DBeaver!可以连接和操作市面所有的数据库!...
  4. 五大算法设计思想,你都知道吗?
  5. 看完陈皓的116篇文章,我给自己定了个5年技术规划
  6. 来给你代码加上美颜吧!
  7. 20 亿个数字在 4G 内存中如何去重排序:快来试一试 BitMap
  8. 深入探究 RocketMQ 事务机制的实现流程,为什么它能做到发送消息零丢失?
  9. 毕业五年,几个月入百万阿里系大神的公众号!
  10. 一堂拯救万千股民的公开课