《Opencv3编程入门》学习笔记—第八章

news/2024/7/21 6:35:50 标签: 计算机视觉, opencv, 图像处理, c++

《Opencv3编程入门》学习笔记

记录一下在学习《Opencv3编程入门》这本书时遇到的问题或重要的知识点。

第八章 图像轮廓与图像分割修复

一、查找并绘制轮廓

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

(一)寻找轮廓:findContours()函数

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

void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())

(1)第一个参数:InputArray类型的image,输入图像,必须是二值图像。我们可以用compare()、imrange()、threshold()、adaptivethreshold()、canny()等函数由灰度图或者彩色图创建二值图像。
(2)第二个参数:此参数类型可以使用vector<vector>类型,用于存放监测到的轮廓, 每一个轮廓被储存为一个点构成的容器。contour[i]表示第i个轮廓。
(3)第三个参数:可以使用vector类型。该容器的元素为4维int类型的向量。向量中的4个元素分别存放当前轮廓contour[i]的后一个轮廓编号、前一个轮廓编号、父轮廓编号、内嵌轮廓编号。
(4)第四个参数:int类型,轮廓检索模式,取值如下表所示

  • RETR_EXTERNAL 只检测外层轮廓
  • RETR_LIST 检测所有轮廓,轮廓间不建立等级关系
  • RETE_CCOMP 检测所有轮廓,并且将其组织为双层结构,顶层为连通域的外围边界,次层为孔的内层边界
  • RETE_TREE 提取所有轮廓,并建立网状的轮廓结构

(5)第五个参数:int类型,轮廓的近似方法,取值如下表

  • CHAIN_APPROX_NONE 获取每个轮廓的每个像素,相邻的两个像素位置差不超过1
  • CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向、对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需要4个点
  • CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOS 使用teh-Chinl链逼近算法

(6)第六个参数:Point类型的偏移量,每个轮廓点的可选偏移量。对于ROI(感兴趣区域)图像中找出的轮廓,并要在整个图像中进行分析时,这个参数便可以派上用场。

(二)绘制轮廓:drawContours()函数

void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )

(1)第一个参数:image表示目标图像,Mat类型即可。
(2)第二个参数:contours表示输入的轮廓组,每一组轮廓由点vector构成,可以使用vector<vector>类型。
(3)第三个参数:contourIdx指明画第几个轮廓,如果该参数为负值,则画全部轮廓,
(4)第四个参数:color为轮廓的颜色,Scalar类型。
(5)第五个参数:thickness为轮廓的线宽,如果为负值或CV_FILLED表示填充轮廓内部。
(6)第六个参数:lineType为线型,取值类型如下表:
在这里插入图片描述
(7)第七个参数:可选的轮廓结构信息,
(8)第八个参数:为maxLevel,绘制轮廓的最大等级
(9)第九个参数:Point类型的偏移量。

(三)基础示例程序:轮廓查找

示例代码

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;

int main()
{
	//以灰度值模式载入原始图像
	Mat srcImage = imread("D://lili/Desktop/jpg/opencv/8.jpg", 0);
	imshow("原始图", srcImage);

	//初始化结果图
	Mat dstImage = Mat::zeros(srcImage.rows, srcImage.cols, CV_8UC3);

	//src取大于阈值120的那部分
	srcImage = srcImage > 120;
	imshow("阈值处理后的原始图", srcImage);

	//定义轮廓和层次结构
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;            //该容器内放了4维int向量

	//查找轮廓
	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, 1, 8, hierarchy);
	}

	imshow("轮廓图", dstImage);
	waitKey(0);
	return 0;
}

运行效果
在这里插入图片描述
在这里插入图片描述

(四)综合示例程序:查找并绘制轮廓

示例代码

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;

#define WINDOW_NAME1 "【原始图窗口】"
#define WINDOW_NAME2 "【轮廓图】"

//全局变量声明
Mat g_srcImage, g_grayImage, g_cannyMat_output;
int g_nThresh=80, g_nThresh_max=255;
RNG g_rng(12345);
vector<vector<Point>> g_vContour;
vector<Vec4i> g_vHierarchy;

//全局函数声明
void on_threshChange(int, void*);


int main()
{
	g_srcImage = imread("D://lili/Desktop/jpg/opencv/11.jpg", 1);
	if(!g_srcImage.data)
	{
		cout << "读取图片错误" << endl;
		return false;
	}

	//转成灰度图并模糊化降噪
	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("canny阈值", WINDOW_NAME1, &g_nThresh, g_nThresh_max, on_threshChange);
	on_threshChange(0, 0);
	waitKey(0);
	return 0;
}


//回调函数
void on_threshChange(int, void*)
{
	//用canny算子检测边缘
	Canny(g_grayImage, g_cannyMat_output, g_nThresh, g_nThresh * 2, 3);

	//寻找轮廓
	findContours(g_cannyMat_output, g_vContour, 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_vContour.size(); i++)
	{
		Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));
		drawContours(drawing, g_vContour, i, color, 2, 8, g_vHierarchy, 0, Point());
	}
	imshow(WINDOW_NAME2, drawing);
}

运行效果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、寻找物体的凸包

(一)凸包

  • 简单来说凸包就是给定二维平面上的点集,将最外层点连接起来构成的凸多边形。
  • 理解物体形状或轮廓的一种比较有用的方法是计算一个物体的凸包,然后计算其凸缺陷(convexity defects)例如,图中A-H区域是凸包的各个”缺陷”:
    在这里插入图片描述

(二)寻找凸包:convexHull()函数

void convexHull(InputArray points,OutputArray hull,bool clockwise = false,bool returnPoints = true)

(1)参数一:输入的二维点集,Mat类型或std::vector
(2)参数二:输出参数,找到的凸包,返回的hull是points中点的索引
(3)参数三:操作方向标识符,为真时输出凸包为顺时针方向,否则逆时针
(4)参数四:操作标识符,默认true,标志为真时函数返回各凸包的各个点,否则返回凸包各点的指数,输出数组是std:vector时此标志忽略

(三)基础示例程序:凸包检测基础

示例代码
随机生成3-103个坐标值随机的彩色点,然后利用convexHull,对由这些点链接起来的图像求凸包。

#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
	//初始化变量和随机值
	Mat image(600, 600, CV_8UC3);
	RNG& rng = theRNG();
	//循环,按下ESC退出程序,否则有键按下便一直更新
	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;
		convexHull(Mat(points), hull, true);//返回的hull是points中点的索引
		//绘制出随机颜色的点
		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)), 1, 1);
		}
		//准备参数
		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, 1);
			point0 = point;
		}
		//显示效果图
		imshow("凸包检测示例", image);
		//按下ESC,程序退出
		key = (char)waitKey();
		if (key == 27) break;
	}
	return 0;
}

运行效果
在这里插入图片描述

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

示例代码

/*
效果:
	滑动条控制阈值g_nThresh,以改变g_thresholdImage_output,findContours以此参数为输入,查找不同轮廓图,从而得到不同凸包检测效果图
*/
#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
#define WINDOW_NAME1 "【原始图】"
#define WINDOW_NAME2 "【效果图】"
//全局变量
Mat g_srcImage, g_grayImage, g_thresholdImage_output, g_dstImage;
int g_nThresh = 80;
int g_nThresh_max = 255;
RNG g_rng(12345);
Mat srcImage_copy = g_srcImage.clone();
vector<vector<Point>>g_vContours;
vector<Vec4i>g_vHierarchy;
//全局函数
static void ShowHelpText();
static void on_ThreshChange(int, void*);
int main()
{
	//改变console字体颜色
	system("color 1F");
	ShowHelpText();

	//载入原图
	g_srcImage = imread("D://lili/Desktop/jpg/opencv/11.jpg", 1);
	if (!g_srcImage.data)
	{
		printf("图片载入失败\n");
		return false;
	}
	//创建窗口
	namedWindow(WINDOW_NAME1, WINDOW_AUTOSIZE);
	namedWindow(WINDOW_NAME2, WINDOW_AUTOSIZE);
	imshow(WINDOW_NAME1, g_srcImage);

	//转成灰度图并模糊化降噪
	cvtColor(g_srcImage, g_grayImage, CV_BGR2GRAY);
	blur(g_grayImage, g_grayImage, Size(3, 3));

	//创建滚动条并初始化
	createTrackbar("阈值", WINDOW_NAME2, &g_nThresh, g_nThresh_max, on_ThreshChange);
	on_ThreshChange(0, 0);

	waitKey(0);
	return 0;
}
static 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));
	//遍历每个轮廓,寻找其凸包
	vector<vector<Point>>hull(g_vContours.size());
	for (int i = 0; i < g_vContours.size(); i++)
	{
		convexHull(Mat(g_vContours[i]), hull[i], false);
	}
	//绘制轮廓及其凸包
	Mat g_dstImage = Mat::zeros(g_thresholdImage_output.size(), CV_8UC3);
	//或 for (int index = 0; index >= 0; index = g_vHierarchy[index][0])
	for (int index = 0; index < g_vContours.size(); index++)
	{
		//或 Scalar color(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//任意值
		Scalar color(rand() & 255, rand() & 255, rand() & 255);
		drawContours(g_dstImage, g_vContours, index, color, 1, 8, vector<Vec4i>(), 0, Point());
		drawContours(g_dstImage, hull, index, color, 1, 8, vector<Vec4i>(), 0, Point());
	}
	//显示效果图
	imshow(WINDOW_NAME2, g_dstImage);
}
static void ShowHelpText()
{
	printf("\n\n\t欢迎来到【在图形中寻找轮廓及其凸包】示例程序!\n\n");
	printf("\n\n\t操作说明:\n\n");
	printf("\t\t键盘任意键-退出程序\n\n");
	printf("\t\t滑动滚动条-改变阈值\n");
}

运行效果
在这里插入图片描述

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

三、使用多边形将轮廓包围

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

(1)作用:返回指定点集最外面的边界矩形(四个顶点)
(2)函数原型:Rect boundingRect(InputArray points)

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

(1)作用:返回指定点集可旋转的最小面积的包围矩形(四个顶点)
(2)函数原型:RotatedRect minAreaRect(InputArray points)

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

(1)作用:利用一种迭代算法,返回指定点集的最小面积的包围圆形(圆心,半径)
(2)函数原型:void minEnclosingCircle(InputArray points, Point2f& center, float& radius)
(3)参数说明:输入二维点集,输出圆心,输出半径

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

(1)作用:椭圆拟合二维点集
(2)函数原型:RotatedRect fitEllipse(InputArray points)

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

(1)作用:用指定精度逼近多边形曲线
(2)函数原型:void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)
(3)参数说明:

  • 输入的二维点集
  • 多边形逼近的结果
  • 逼近的精度,为原始曲线和近似曲线间的最大值
  • 取真时,近似的曲线为封闭曲线,取假时不封闭

(六)基础示例程序:创建包围轮廓的矩形边界、圆形边界、椭圆边界

示例代码

#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;

int main()
{
	//初始化变量和随机值
	Mat image(600, 600, CV_8UC3);
	Mat dstImage1, dstImage2, dstImage3, dstImage4;
	RNG& rng = theRNG();

	//循环,按下ESC键程序退出,否则一直更新
	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.rows / 4, image.rows * 3 / 4);
			point.y = rng.uniform(image.cols / 4, image.cols * 3 / 4);
			points.push_back(point);
		}
		//绘制出随机颜色的点
		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), rng.uniform(0, 255)), 1, 1);

		}

		//【1】对指定点集寻找边界矩形
		Rect rect = boundingRect(Mat(points));
		//绘制边界矩形
		image.copyTo(dstImage1);
		rectangle(dstImage1, rect, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 2, 1);
		//显示窗口
		imshow("【1】边界矩形窗口", dstImage1);
		
		//【2】对指定点集寻找最小面积的包围矩形
		RotatedRect box1 = minAreaRect(Mat(points));
		Point2f vertex2[4];
		box1.points(vertex2);
		//绘制最小面积的包围矩形
		image.copyTo(dstImage2);
		for (int i = 0; i < 4; i++)
		{
			line(dstImage2, vertex2[i], vertex2[(i + 1) % 4], Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 2, 1);
		}
		//显示窗口
		imshow("【2】最小面积包围矩形窗口", dstImage2);

		//【3】对指定点集寻找最小面积的包围圆形
		Point2f center;
		float radius;
		minEnclosingCircle(Mat(points),center,radius);
		//绘制最小面积的圆形
		image.copyTo(dstImage3);
		circle(dstImage3, center, cvRound(radius), Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 2, 1);
		//显示窗口
		imshow("【3】最小面积包围圆形窗口",dstImage3);

		//【4】椭圆拟合指定点集
		RotatedRect box2 = fitEllipse(Mat(points));
		//绘制椭圆
		image.copyTo(dstImage4);
		ellipse(dstImage4, box2, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 2, 1);
		//显示窗口
		imshow("【4】椭圆形拟合窗口", dstImage4);

		char key = (char)waitKey();
		if(key == 27) break;	
	}
	return 0;
}

运行效果
在这里插入图片描述
在这里插入图片描述

(八)综合示例程序:使用多边形包围轮廓

示例代码

#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
//定义辅助宏
#define WINDOW_NAME1 "【原始图窗口】"
#define WINDOW_NAME2 "【效果图窗口】"
//全局变量
Mat g_srcImage, g_grayImage;
int g_nThresh = 50;//阈值
int g_nMaxThresh = 255;//阈值最大值
RNG g_rng(12345);//随机数生成器
//全局函数
void on_ContoursChange(int, void*);

int main()
{	
	//【1】载入原图
	g_srcImage = imread("D://lili/Desktop/jpg/opencv/12.jpg");
	if (!g_srcImage.data)
	{
		printf("载入原图失败~!\n");
		return false;
	}
	//【2】创建原始图窗口并显示
	namedWindow(WINDOW_NAME1,WINDOW_AUTOSIZE);
	imshow(WINDOW_NAME1, g_srcImage);
	//【3】得到原图的灰度图像并进行平滑
	cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
	blur(g_grayImage, g_grayImage, Size(3, 3));
	//【4】设置滚动条并调用一次回调函数
	createTrackbar("阈值:", WINDOW_NAME1, &g_nThresh, g_nMaxThresh, on_ContoursChange);
	on_ContoursChange(0, 0);

	waitKey(0);
	return 0;
}
void on_ContoursChange(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));

	//多边形逼近轮廓+获取矩形和圆形边界框
	vector<vector<Point>> contours_poly(contours.size());
	vector<Rect> boundRect(contours.size());
	vector<Point2f> center(contours.size());
	vector<float> radius(contours.size());

	//一个循环,遍历所有部分,进行本程序最核心的操作
	for (int i = 0; i < contours.size(); i++)
	{
		approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true);//用指定精度逼近多边形曲线
		boundRect[i] = boundingRect(Mat(contours_poly[i]));//计算点集的最外面矩形边界
		minEnclosingCircle(contours_poly[i], center[i], radius[i]);//对给定点集寻找最小面积的包围圆形
	}
	//绘制多边形轮廓+包围的矩形框+圆形框
	Mat dstImage = Mat::zeros(threshold_output.size(), CV_8UC3);
	for (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(dstImage, contours_poly, i, color, 1, 8, vector<Vec4i>(), 0, Point());//绘制轮廓
		rectangle(dstImage, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0);//绘制矩形
		circle(dstImage, center[i], (int)radius[i], color, 2, 8, 0);
	}
	//显示效果图窗口
	namedWindow(WINDOW_NAME2,WINDOW_AUTOSIZE);
	imshow(WINDOW_NAME2, dstImage);
}

运行效果
在这里插入图片描述

四、图像的矩

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

(一)矩的计算:moments()函数

(1)作用:用于计算多边形和光栅形状的最高达三阶的所有矩。矩用来计算形状的重心、面积、主轴和其他形状特征
(2)函数原型:Moment moments(InputArray array, bool binaryImage=false)
(3)参数说明:

  • 输入参数,可以是光栅图像(单通道,8位或浮点的二维数组)或二维数组(1N或N1)
  • 此参数仅对图像使用,取true,则所有非零像素为1,默认false

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

(1)作用:用于计算整个轮廓或部分轮廓的面积
(2)函数原型:double contourArea(InputArray contour, bool oriented=false)
(3)参数说明:

  • 输入的二维点集
  • 面向区域标识符,若其为true,该函数返回一个带符号的面积值,其正负取决于轮廓方向(顺/逆时针),若其为false,该函数返回面积绝对值。

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

(1)作用:计算封闭轮廓的周长或曲线的长度
(2)函数原型:double arcLength(InputArray curve,bool closed)
(3)参数说明:

  • 输入的二维点集
  • 用于指示曲线是否封闭的标识符,默认closed

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

示例代码

#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;

//定义辅助宏
#define WINDOW_NAME1 "【原始图】"
#define WINDOW_NAME2 "【图像轮廓】"
//全局变量
Mat g_srcImage, g_grayImage, g_cannyMat_output;
int g_nThresh = 100;
int g_nMaxThresh = 255;
RNG g_rng(12345);
vector<vector<Point>>g_vContours;
vector<Vec4i>g_vHierarchy;
//全局函数
void on_ThreshChange(int, void*);

int main()
{
	//载入原图
	g_srcImage = imread("D://lili/Desktop/jpg/opencv/12.jpg");
	//把原图转为灰度图并进行平滑
	cvtColor(g_srcImage, g_grayImage, COLOR_RGB2GRAY);
	blur(g_grayImage, g_grayImage, 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;
}
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 (int i = 0; i < g_vContours.size(); i++)
	{
		mu[i] = moments(g_vContours[i], false);
	}
	//计算中心矩
	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);//绘制圆
	}
	//显示窗口
	namedWindow(WINDOW_NAME2,WINDOW_AUTOSIZE);
	imshow(WINDOW_NAME2, drawing);

	//通过m00计算轮廓面积和OpenCV函数比较
	printf("\t 输出内容:面积和轮廓长度\n");
	for (int i = 0; i < g_vContours.size(); i++)
	{
		printf(">通过m00计算出轮廓[%d]的面积:(M_00) = %.2f \n", i, mu[i].m00);
		printf(" OpenCV函数计算出的面积 = %.2f, 长度:%.2f \n\n", contourArea(g_vContours[i]), arcLength(g_vContours[i], true));
	}
}

运行效果
在这里插入图片描述

五、分水岭算法

  • 基于拓扑理论的数学形态学的分割方法。
  • 基本思想:把图像看作测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,分水岭变换得到的是输入图像的集水盆图像,集水盆边界形成分水岭(输入图像的极大值点)。
  • 计算过程:一个迭代标注过程,包括排序过程和淹没过程,先对每个像素的灰度级进行从低到高的排序,然后在从低到高实现淹没的过程中,对每一个局部最小值在h阶高度的影响域采用先进先出(FIFO)结构进行判断及标注。

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

基于标记的分割算法,在把图像传给函数之前,需要大致标记出图像中期望进行分割的区域,每个区域被标记为像素值1、2、3等,表示成为一个或多个连接组件,标记的值可使用findContours()函数和drawContours()函数由二进制的掩码检索出来,函数输出中,每一个标记中的像素被设置为标记的值,区域间的值被设置为-1。

void watershed(InputArray image,InputOutputArray markers)
  • 输入图像,8位三通道彩色图像
  • 函数调用后运算结果,输入/输出32位单通道图像的标记结果

(二)综合示例程序:分水岭算法

有些难以理解。但是没关系,了解一下原理,毕竟这是传统的图像分割方法,现在已经有了更好的分割方法了。
示例代码

/*
  程序说明:鼠标大致标记出图像中期望进行分割的区域
           键盘按键【1】启动分水岭算法
           按键【2】恢复原始图重新标记
*/
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace cv;
using namespace std;
//定义辅助宏
#define WINDOW_NAME1 "【原始图窗口】"
#define WINDOW_NAME2 "【恢复原始图窗口】"
#define WINDOW_NAME3 "【分水岭变换窗口】"
//全局变量
Mat g_srcImage, g_maskImage;
Point prevPt(-1, -1);
//全局函数
static void on_Mouse(int event, int x, int y, int flags, void*);
static void ShowHelpText();

int main()
{
	//【0】显示帮助信息
	ShowHelpText();
	
	//【1】载入原图并显示
	g_srcImage = imread("D://lili/Desktop/jpg/opencv/9.jpg");
	if (!g_srcImage.data)
	{ 
		printf("载入原图像失败\n");
		return false;
	}
	imshow(WINDOW_NAME1, g_srcImage);
	//【2】初始化掩模和灰度图
	Mat srcImage, grayImage;
	g_srcImage.copyTo(srcImage);
	cvtColor(srcImage, g_maskImage, COLOR_BGR2GRAY);
	cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR);
	g_maskImage = Scalar::all(0);
	//【3】设置鼠标回调函数
	setMouseCallback(WINDOW_NAME1, on_Mouse, 0);

	//【4】轮询按键,进行处理
	while (1)
	{
		//获取键值
		int c = waitKey(0);
		//按键值为ESC时,退出程序
		if ((char)c == 27)
			break;
		//按键为2时,恢复原图,使g_maskImage和g_srcImage可重新标记
		if ((char)c == '2')
		{
			g_maskImage = Scalar::all(0);
			srcImage.copyTo(g_srcImage);
			imshow(WINDOW_NAME2, g_srcImage);
		}
		//按键值为1时,进行处理
		if ((char)c == '1')
		{
			//定义一些参数
			int i, j;
			int compCount = 0;  //记录轮廓数
			vector<vector<Point>>contours;
			vector<Vec4i> hierarchy;
			//寻找轮廓
			findContours(g_maskImage, contours, hierarchy, CV_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), -1, 8, hierarchy, INT_MAX);
			}

			//为每一个轮廓生成一个随机颜色
			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 (i = 0; i < maskImage.rows; i++)
				for (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);//maskImage中区域间值像素为-1,将对应watershed图像像素设置为白色(255,255,255)
					else if (index <= 0 || index > compCount)
						watershedImage.at<Vec3b>(i, j) = Vec3b(0, 0, 0);      //maskImage中非标记区域,将对应watershed图像像素设置为黑色(0,0,0)
					else
						watershedImage.at<Vec3b>(i, j) = colorTab[index - 1]; //maskImage中标记区域,将对应watershed图像像素设置为之前随机出的颜色colorTab
				}
			//混合灰度图和分水岭效果图并显示最终的窗口
			watershedImage = watershedImage * 0.5 + grayImage * 0.5;
			imshow(WINDOW_NAME3, watershedImage);
		}
	}
	return 0;
}

//鼠标消息回调函数
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)  //如果白线起始点范围溢出,则白线起始点为当前点pt
			prevPt = pt;
		line(g_maskImage, prevPt, pt, Scalar::all(255), 2, 8, 0);//画白线
		line(g_srcImage, prevPt, pt, Scalar::all(255), 2, 8, 0); //画白线
		prevPt = pt;
		imshow(WINDOW_NAME1, g_srcImage);
	}
}
static void ShowHelpText()
{
	printf("\n\n\t欢迎来到【分水岭算法】示例程序~\n");
	printf("\n\t请先用鼠标在图片窗口中标记出大致的区域\n");
	printf("\n\t然后再按键【1】启动分水岭算法\n");
	printf("\n\t按键操作说明:\n");
	printf("\t\t\t键盘按键【1】--运行分水岭分割算法\n");
	printf("\t\t\t键盘按键【2】--恢复原始图\n");
	printf("\t\t\t键盘按键【ESC】--退出程序\n");
}

运行效果
在这里插入图片描述

六、图像修补

基本思想:
  利用已经被破坏区域的边缘,即边缘的颜色和结构,繁殖和混合到损坏的图像中,达到图像修补的目的。

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

用来从扫描的照片中清除灰尘和划痕,从静态图像或视频中去除不需要的物体

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

(1)输入图像,8位单通道或三通道
(2)修复掩模,8位单通道,其中的非零像素表示需要修补区域
(3)修补后图像
(4)需要修补的每个点的圆形区域,为修复算法的参考半径
(5)修补方法的标识符,可取值:

  • INPAINT_NS:基于Navier-Stokes方程的方法
  • INPAINT_TELEA:Alexandru Telea方法

(二)综合示例程序:图像修补

示例代码

/*
	程序说明:鼠标绘制白色线条破坏原图像图像
			 键盘按键【1】启动进行图像修复
			 按键【2】恢复原始图像
*/

#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
//定义辅助宏
#define WINDOW_NAME1 "【原始图窗口】"
#define WINDOW_NAME2 "【修复效果图】"
//全局变量
Mat g_srcImage, g_inpaintMask;
Point previousPoint(-1, -1); //原来的点坐标
//全局函数
static void on_Mouse(int event, int x, int y, int flags, void*);
static void ShowHelpText();

int main()
{
	//显示帮助文字
	ShowHelpText();

	//载入原图并显示
	Mat srcImage = imread("D://lili/Desktop/jpg/opencv/9.jpg");
	if (!srcImage.data)
	{
		printf("载入原图失败\n");
		return false;
	}
	imshow(WINDOW_NAME1, srcImage);
	//初始化掩模
	g_srcImage = srcImage.clone();
	g_inpaintMask = Mat::zeros(g_srcImage.size(), CV_8U);
	//设置鼠标回调消息
	setMouseCallback(WINDOW_NAME1, on_Mouse, 0);
	//按键轮询
	while (1)
	{
		//获取按键键值
		char c = (char)waitKey();
		//键值为ESC,程序退出
		if (c == 27) break;
		//键值为2,恢复原始图像
		if (c == '2')
		{
			g_inpaintMask = Scalar::all(0);
			srcImage.copyTo(g_srcImage);
			imshow(WINDOW_NAME1, g_srcImage);
		}
		//键值为1,进行图像修复
		if (c == '1')
		{
			Mat inpaintedImage;
			inpaint(g_srcImage, g_inpaintMask, inpaintedImage, 3, INPAINT_TELEA);
			imshow(WINDOW_NAME2, inpaintedImage);
		}
	}
	return 0;
}
//鼠标回调函数
static 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(g_inpaintMask, previousPoint, pt, Scalar::all(255), 5, 8, 0);
		line(g_srcImage, previousPoint, pt, Scalar::all(255), 5, 8, 0);
		previousPoint = pt;
		imshow(WINDOW_NAME1, g_srcImage);
	}
}
static void ShowHelpText()
{
	printf("\n\t欢迎来到【图像修复】示例程序~\n");
	printf("\n\t请再进行图像修复操作之前,在【原始图】窗口中进行适量的绘制\n");
	printf("\n\t按键操作说明:\n");
	printf("\t\t\t键盘按键【1】--进行图像修复\n");
	printf("\t\t\t键盘按键【2】--恢复原始图\n");
	printf("\t\t\t键盘按键【ESC】--退出程序\n");
}

运行效果
在这里插入图片描述


http://www.niftyadmin.cn/n/472237.html

相关文章

Vue----Vue条件渲染

【原文链接】Vue----Vue条件渲染 &#xff08;1&#xff09;在components文件夹下新建一个Ifdemo.vue文件。 &#xff08;2&#xff09;然后在文件中编写如下内容&#xff0c;即写入一个标题 <template><h3>条件渲染</h3> </template> <script&…

怎么用迅捷PDF转换器在线提取PDF文件中的图片

大家在学习或者是办公中经常使用到PDF文件&#xff0c;我们在做一份工作文件的时候&#xff0c;需要一些资料来补充内容&#xff0c;这些资料是以PDF文件格式呈现&#xff0c;在使用PDF文件时&#xff0c;文件中有的图片做到很精细&#xff0c;想要单独提取保存下来备用。那么&…

Java给PDF文档添加水印信息

在word生成pdf后&#xff0c;或者上传pdf文件&#xff0c;用户希望给pdf文档添加水印信息&#xff0c;具体代码如下 一. 首先pom中需要引入 <!-- PDF水印--><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId&g…

XSS注入——DOM型XSS

DOM型xss XSS根据恶意脚本的传递方式可以分为3种&#xff0c;分别为反射型、存储型、DOM型&#xff0c;前面两种恶意脚本都会经过服务器端然后返回给客户端&#xff0c;相对DOM型来说比较好检测与防御&#xff0c;而DOM型不用将恶意脚本传输到服务器在返回客户端&#xff0c;这…

SCTF2023复现(部分web复现)

文章目录 SCTF2023复现webezcheck1nSycServerpypyp? SCTF2023复现 web ezcheck1n find the way to flag.Looks like there are two containers with an evil P in the configuration file of the frontend server 源码&#xff1a; <?php$FLAG "flag{fake_flag}&…

【SQL开发实战技巧】系列(二):简单单表查询

系列文章目录 【SQL开发实战技巧】系列&#xff08;一&#xff09;:关于SQL不得不说的那些事 【SQL开发实战技巧】系列&#xff08;二&#xff09;&#xff1a;简单单表查询 【SQL开发实战技巧】系列&#xff08;三&#xff09;&#xff1a;SQL排序的那些事 【SQL开发实战技巧…

牛逼!新书冲到京东排行榜第一了!

小伙伴们大家好&#xff0c;我是阿秀。 愉快的假期时光已经结束了&#xff0c;我现在还沉浸在前几天的假期时光里舍不得出来呜呜呜~ 对于打工人来说&#xff0c;真的很难能有超过2天的连续假期&#xff0c;所以对于这种小长假我都是格外珍惜。 端午假期&#xff0c;我和对象一下…

Vue报错-取消ESLint默认检测

ESLint规则对vue的语法格式要求比较严格&#xff0c;默认在启动Vue会给出警告 但是不影响项目使用&#xff0c;如果不想看到这些警告&#xff0c;我们可以取消ESLint检查 D:\项目名\renren-fast-vue\build\webpack.base.conf.js 将内容注解掉 const createLintingRule () >…