OpenCV(11)边缘检测、轮廓绘制、简单平移距离测量 C++

1.边缘检测

原文链接:http://blog.sina.com.cn/s/blog_154bd48ae0102weuk.html
边缘检测的一般步骤:
1.滤波
边缘检测的算法主要是基于图像的一阶和二阶导数。但是导数通常对噪声很敏感,所以首先要用滤波器降低噪声。常见的滤波方法主要是高斯滤波。
2.增强
增强边缘的基础是确定图像各点领域强度的变化值。增强算法可以将图像灰度点邻域强度值有显著变化的点凸现出来,在具体计算的过程中,可以通过计算梯度幅值来确定。
3.检测
经过增强的图像,往往领域中有很多点的梯度值比较大,而在特定的场合中,这些点并不是边缘点,所以应采用某种方式进行取舍,我们通常采取阈值化的方法来检测。

1.1.(cv :: canny)边缘检测

Canny边缘检测的步骤:
1.消除噪声(高斯滤波)
2.计算梯度幅值与方向(sobel滤波器)
3.非极大值抑制(排除一些非边缘像素)
4.滞后阈值
Sobel算子是计算图像梯度的,所以在canny和laplacian中都调用过sobel算子。
Sobel算子是一个主要用于边缘检测的离散微分算子,它结合了高斯平滑和微分求导,用来计算图像灰度函数的近似梯度。在图像的任意一点使用该算子,都将会产生对应的梯度矢量或者法向量。
拉普拉斯算子是n维欧几里德空间中的一个二阶微分算子,定义为梯度的散度。
除了canny算子,其他都是有x和y两个方向的

Void canny(InputArray image,OutputArray edges,double threshold1,double threshold2,int apertureSize=3,bool L2gradient=false)
  • image:输入图像
  • edges:输出图像
  • threshold1:第一个滞后性阈值
  • threshold2:第二个滞后性阈值
  • apertureSize:表示应用sobel算子孔径大小,默认值为3
  • L2gradient:一个计算图像梯度幅值的标识,默认为false

1.2.(cv :: Sobel)边缘检测

需要注意的是,这个函数阈值1和阈值2中较小的值用于边缘连接,而较大的值用来控制强边缘的初始段。

Void Sobel(InputArray image,OutputArray edges,int ddepth,int dx,int dy,int ksize=3,double scale=1,double delta=0,int borderType=BORDER_DEFAULT)
  • image:输入图像
  • edges:输出图像
  • ddepth:输出图像的深度
  • dx:x方向的差分阶数
  • dy:y方向的差分阶数
  • ksize:Sobel的核大小,必须是1/3/5/7
  • scale:计算导数值时可选的缩放因子
  • delta:在结果存入目标图之前可选的delta值,默认0
  • borderType:边界模式

1.3.(cv :: Laplacian)边缘检测

Laplacian(InputArray image,OutputArray edges,int ddepth,int ksize=1,double scale=1,double delta=0,int borderType=BORDER_DEFAULT)
  • image:输入图像
  • edges:输出图像
  • ddepth:输出图像的深度
  • ksize:用于计算二阶导数的滤波器的孔径尺寸,大小必须是正奇数,默认1
  • scale:用来计算拉普拉斯值的时候可选的比例因子
  • delta:在结果存入目标图之前可选的delta值,默认0
  • borderType:边界模式
  • Scharr滤波器

1.4.(cv :: Scharr)边缘检测

Void Scharr(InputArray image,OutputArray edges,int ddepth,int dx,int dy,double scale=1,double delta=0,int borderType=BORDER_DEFAULT)
  • image:输入图像
  • edges:输出图像
  • ddepth:输出图像的深度
  • dx:x方向的差分阶数
  • dy:y方向的差分阶数
  • scale:计算导数值时可选的缩放因子
  • delta:在结果存入目标图之前可选的delta值,默认0
  • borderType:边界模式

Scharr与soble只差一个参数,内核数,Scharr只作用于大小为3的内核,该函数和sobel函数一样快,但结果却更加精确。

//#include"stdafx.h";
#include"opencv2/opencv.hpp"
//空间变量
using namespace cv;
using namespace std;
void main()
{
    
    
	//显示原图像
	Mat image = imread("./image/test3.jpg");
	namedWindow("原图");
	imshow("原图", image);
	//canny边缘检测的简单用法
	Mat result;
	Canny(image, result, 150, 70);
	namedWindow("canny边缘检测后的图像");
	imshow("canny边缘检测后的图像", result);	
	//高阶的canny用法,转成灰度图,降噪,用canny,最后将得到的边缘作为掩码,拷贝原图到效果图上,得到彩色边缘图
	Mat grayimage, edge;
	cvtColor(image, grayimage, COLOR_BGR2GRAY);
	boxFilter(grayimage, edge, -1, Size(3, 3));
	Canny(edge, edge, 150, 70);
	Mat dst;
	dst = Scalar::all(123);
	image.copyTo(dst, edge);
	namedWindow("canny高阶边缘检测后的图像");
	imshow("canny高阶边缘检测后的图像", dst);	
	//sobel算子边缘检测
	Mat x_result, y_result;
	Sobel(image, x_result, 0, 1, 0);
	Sobel(image, y_result, 0, 0, 1);
	addWeighted(x_result, 0.5, y_result, 0.5, 0, result);
	imshow("sobel边缘检测后x轴的图像", x_result);
	imshow("sobel边缘检测后y轴的图像", y_result);
	imshow("sobel边缘检测后的图像", result);
	//laplacian边缘检测
	Laplacian(image, result, 0);
	imshow("laplacian边缘检测后的图像", result);
	//scharr滤波器
	boxFilter(image, image, -1, Size(3, 3));
	Scharr(image, x_result, 0, 1, 0);
	Scharr(image, x_result, 0, 0, 1);
	addWeighted(x_result, 0.5, y_result, 0.5, 0, result);
	imshow("scharr边缘检测后x轴的图像", x_result);
	imshow("scharr边缘检测后y轴的图像", y_result);
	imshow("scharr边缘检测后的图像", result);
	waitKey();
}

执行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.轮廓绘制

原文链接:https://blog.csdn.net/zhu_hongji/article/details/81699736

2.1.(cv :: findContours)查找轮廓

CV_EXPORTS_W void findContours(InputOutputArray image, OutputArrayOfArrays contours, 
             OutputArray hierarchy, int mode, int method, Point offset = Point());
  • image:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;

  • contours:contours定义为 “vector<vector>contours”,是一个双重向量(向量内每个元素保存了一组由连续的Point构成的点的集合的向量),每一组点集就是一个轮廓,有多少轮廓,contours就有多少元素;

  • hierarchy:hierarchy定义为“vector hierarchy”,
    Vec4i的定义:typedef Vec<int, 4>Vec4i;(向量内每个元素都包含了4个int型变量),所以从定义上看,hierarchy是一个向量,向量内
    每个元素都是一个包含4个int型的数组。向量hierarchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同hierarchy
    内每个元素的4个int型变量是hierarchy[i][0] ~ hierarchy[i][3],分别表示当前轮廓 i 的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓的编
    号索引。如果当前轮廓没有对应的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓,则相应的hierarchy[i][*]被置为-1。

  • mode:定义轮廓的检索模式,取值如下:

  • RETR_EXTERNAL:只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;

  • RETR_LIST:检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到;

  • RETR_CCOMP: 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;

  • RETR_TREE: 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。

  • method:定义轮廓的近似方法,取值如下:

  • CHAIN_APPROX_NONE:保存物体边界上所有连续的轮廓点到contours向量内;

  • CHAIN_APPROX_SIMPLE:仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信
    息点不予保留;

  • CHAIN_APPROX_TC89_L1:使用teh-Chinl chain 近似算法;

  • CHAIN_APPROX_TC89_KCOS:使用teh-Chinl chain 近似算法。

  • offset:Point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,并且Point还可
    以是负值!

OpenCV提取轮廓之后,还可以进行许多操作:
ArcLength() 计算轮廓长度
ContourArea() 计算轮廓区域的面积
BoundingRect() 轮廓的外包矩形
ConvexHull() 提取轮廓的凸包
IsContourConvex() 测试轮廓的凸性
MinAreaRect() 轮廓的最小外包矩形
MinEnclosingCircle() 轮廓的最小外包圆
fitEllipse()用椭圆拟合二维点集
approxPolyDP()逼近多边形曲线

2.2.(cv :: drawContours )轮廓绘制函数

void drawContours(InputOutputArray image, InputOutputArrays contours, int contourIdx, const Scalar& color, int thickness = 1, int lineType = 8, InputArray hierarchy = noArray(), int maxLevel = INT_MAX, Point offset = Point());
  • image:目标图像,填 Mat 类对象即可。
  • contours:输入的轮廓,每个轮廓都是一组点集,可用 Point 类型的 vector 表示。
  • contourIdx:轮廓的索引编号。若为负值,则绘制所有轮廓。
  • color:轮廓颜色。
  • thickness:轮廓线条的粗细程度,有默认值 1。若其为负值,便会填充轮廓内部空间。
  • lineType:线条的类型,有默认值 8。可去类型如下:
    类型 含义
    8 8 连通线型
    4 4 连通线型
    LINE_AA 抗锯齿线型
  • hierarchy:可选的层次结构信息,有默认值 noArray()。
  • maxLevel:用于绘制轮廓的最大等级,有默认值 INT_MAX。
  • offset:轮廓信息相对于目标图像对应点的偏移量,相当于在每一个轮廓点上加上该偏移量,有默认值 Point() 。在 ROI
    区域(感兴趣区域)绘制轮廓时,这个参数便可派上用场。
#include <iostream>
#include <vector>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
int main()
{
    
    
	Mat img = imread("./image/test.jpg");
	imshow("原图", img);
	cvtColor(img, img, COLOR_BGR2GRAY);//转化为灰度图
	//大津法进行二值化
	threshold(img, img, 100, 255, THRESH_OTSU);
	imshow("二值化", img);
	//提取二值化图像中的轮廓数据
	vector<vector<Point> > contour_vec;
	vector<Vec4i> hierarchy;
	//1. RETR_EXTERNAL:只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;
	//2. RETR_LIST:检测所有的轮廓
	findContours(img, contour_vec, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
	cout << "contours number: " << contour_vec.size() << endl;
	// 以前常用的for循环绘制轮廓
	/*Mat blkImg(binImg.size(), CV_8UC1, Scalar(0));
	for(int i = 0; i < contour_vec.size(); i++)
	{
		drawContours(blkImg, contour_vec, i, Scalar(255), -1);
	}   */
	//绘制单通道轮廓图像,背景为白色,轮廓线条用黑色
	Mat blkImg(img.size(), CV_8UC1, Scalar(255));
	drawContours(blkImg, contour_vec, -1, Scalar(0), 2);
	imshow("单通道轮廓", blkImg);
	//绘制彩色轮廓图像,背景颜色为蓝绿色,轮廓线条为红色
	Mat colorImg(img.size(), CV_8UC3, Scalar(255, 255, 0));
	drawContours(colorImg, contour_vec, -1, Scalar(0, 0, 255), 3);
	imshow("彩色轮廓", colorImg);
	waitKey(0);
	return 0;
}

执行结果:
在这里插入图片描述

2.3.图像反转、检测所有轮廓示例:

#include <iostream>
#include <vector>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
int main()
{
    
    
	Mat img = imread("./image/test17.jpg");
	imshow("原图", img);
	//图像取反
	Mat lookUpTable(1, 256, CV_8U);
	uchar* p = lookUpTable.data;
	for (int i = 0; i < 256; i++)
		p[i] = 255 - i;
	LUT(img, lookUpTable, img);//通过LUT函数实现图像取反
	cvtColor(img, img, COLOR_BGR2GRAY);//转化为灰度图	
	//大津法进行二值化
	threshold(img, img, 100, 255, THRESH_OTSU);
	imshow("二值化", img);
	//提取二值化图像中的轮廓数据
	vector<vector<Point> > contour_vec;
	vector<Vec4i> hierarchy;
	findContours(img, contour_vec, hierarchy, RETR_LIST, CHAIN_APPROX_NONE);
	cout << "contours number: " << contour_vec.size() << endl;
	// 以前常用的for循环绘制轮廓
	/*Mat blkImg(binImg.size(), CV_8UC1, Scalar(0));
	for(int i = 0; i < contour_vec.size(); i++)
	{
		drawContours(blkImg, contour_vec, i, Scalar(255), -1);
	}   */
	//绘制单通道轮廓图像,背景为白色,轮廓线条用黑色
	Mat blkImg(img.size(), CV_8UC1, Scalar(255));
	drawContours(blkImg, contour_vec, -1, Scalar(0), 2);
	imshow("单通道轮廓", blkImg);
	//绘制彩色轮廓图像,背景颜色为蓝绿色,轮廓线条为红色
	Mat colorImg(img.size(), CV_8UC3, Scalar(255, 255, 0));
	drawContours(colorImg, contour_vec, -1, Scalar(0, 0, 255), 3);
	imshow("彩色轮廓", colorImg);
	waitKey(0);
	return 0;
}

执行结果:
在这里插入图片描述

2.4.(cv :: circle)图像轮廓特征与图像的矩

void circle(Mat img, Point center, int radius, Scalar color, int thickness=1, int lineType=8, int shift=0)
  • img:为源图像
  • center:为画圆的圆心坐标
  • radius:为圆的半径
  • color:为设定圆的颜色,规则根据B(蓝)G(绿)R(红)
  • thickness:如果是正数,表示组成圆的线条的粗细程度。否则,表示圆是否被填充
  • line_type:线条的类型。默认是8
  • shift:圆心坐标点和半径值的小数点位数

2.5.(cv :: moments)计算图像中的中心矩

opencv中提供了moments()来计算图像中的中心矩(最高到三阶),HuMoments()用于由中心矩计算Hu矩.同时配合函数contourArea函数计算轮廓面积和arcLength来计算轮廓或曲线长度

moments()
cv::moments (InputArray array, bool binaryImage = false ) 
  • array:输入数组,可以是光栅图像(单通道,8-bit或浮点型二维数组),或者是一个二维数组(1 X N或N X
    1),二维数组类型为Point或Point2f
  • binaryImage:默认值是false,如果为true,则所有非零的像素都会按值1对待,也就是说相当于对图像进行了二值化处理,阈值为1,此参数仅对图像有效。
  • 结构 Moments 成员数据:
(
// 空间矩(10个)
double 	m00,double 	m10,double 	m01,double 	m20,double 	m11,double 	m02,double 	m30,double 	m21,double 	m12,double 	m03  
// 中心矩(7个)
double mu20, double mu11, double mu02, double mu30, double mu21 , double mu12,double mu03
// 中心归一化矩() 
double nu20, double nu11, double nu02, double nu30, double nu21, double nu12,double nu03;
)	

示例:

//图像矩:(Image Moments)
//步骤:提取图像边缘
//发现轮廓
//计算每个轮廓对象的矩
//计算每个对象的中心、弧长、面积
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
using namespace cv;
using namespace std;


Mat src, dst, drawImage;
const char* result = "moments_demo";
int threshold_value = 120;
int threshold_max = 255;
RNG rng(12345);
void Moments_demo(int, void*);
int main(int argc, char* argv)
{
    
    
	src = imread("./image/test.jpg");
	if (!src.data)
	{
    
    
		printf("could not load image...\n");
		return -1;
	}
	char input[] = "gray image";
	namedWindow(input, WINDOW_AUTOSIZE);
	namedWindow(result, WINDOW_AUTOSIZE);
	//输入图像转为灰度图像
	cvtColor(src, dst, COLOR_BGR2GRAY);
	GaussianBlur(dst, dst, Size(3, 3), 0, 0);
	imshow(input, dst);


	const char* thresh = "threshold value";
	createTrackbar(thresh, result, &threshold_value, threshold_max, Moments_demo);
	Moments_demo(0, 0);
 

	waitKey(0);
	return 0;
}


void Moments_demo(int, void*)
{
    
    
	//提取图像边缘
	Mat canny_out;
	Canny(dst, canny_out, threshold_value, threshold_value * 2, 3, false);
	//imshow("canny image", canny_out);


	//发现轮廓,找到图像轮廓
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	findContours(canny_out, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));


	//计算每个轮廓对象的矩
	vector<	Moments> contours_moments(contours.size());
	vector<Point2f> centers(contours.size());
	for (size_t i = 0; i < contours.size(); i++)
	{
    
    
		//计算矩
		contours_moments[i] = moments(contours[i]);
		//moments(InputArray  array,//输入数据
		//bool   binaryImage = false // 是否为二值图像
		centers[i] = Point(static_cast<float>(contours_moments[i].m10 / contours_moments[i].m00), static_cast<float>(contours_moments[i].m01 / contours_moments[i].m00));
		//图像中心Center(x0, y0)=(m10/m00,m01/m00)
	}


	src.copyTo(drawImage);
	for (size_t i = 0; i < contours.size(); i++)
	{
    
    
		printf("centers point 中心点 x:%.2f,y:%.2f\n", centers[i].x, centers[i].y);
		printf("contours %d Area 面积:%.2f Arc length 弧长:%.2f \n", i, contourArea(contours[i]), arcLength(contours[i], true));


		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawImage, contours, i, color, 2, LINE_AA, hierachy, 0, Point(0, 0));//绘制轮廓
		circle(drawImage, centers[i], 2, color, 2, LINE_AA);//绘制图形中心
	}
	imshow(result, drawImage);
	return;
}

执行结果:
在这里插入图片描述

3.简单平移距离测量

#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
#include "stdio.h" 
using namespace cv;
using namespace std;
Mat src, dst, drawImage;
const char* result = "坐标";
const char* camera = "摄像头";
int threshold_value = 120;
int threshold_max = 255;
RNG rng(12345);
void Moments_demo(int, void*);
void Int_To_Str(int Int_i, char* String_s);
const char* thresh = "阈值";
//拍照图像存放目录
string writePath = "./image/";
VideoCapture capture(0);
string name;
int i = 0;
Mat frame;
 

int main(int argc, char* argv)
{
    
    
	namedWindow(camera, WINDOW_AUTOSIZE);
	namedWindow(result, WINDOW_AUTOSIZE);
	

	//拍照 
	while (1) 
	{
    
    
		capture >> frame;
		imshow(camera, frame);
		if (32 == waitKey(20))//空格拍照
		{
    
    			
			name = writePath + "test" + to_string(i) + ".jpg";
			imwrite(name, frame);
			cout << name << endl;
			//转换灰度图、二值化、画出轮廓、在图像上显示中点坐标、距离测量
			src = imread(name);
			cvtColor(src, dst, COLOR_BGR2GRAY);//输入图像转为灰度图像
			GaussianBlur(dst, dst, Size(3, 3), 0, 0);
			threshold(dst, dst, 100, 255, THRESH_OTSU);//二值化 
			imshow("二值化", dst);
			createTrackbar(thresh, result, &threshold_value, threshold_max, Moments_demo);
			Moments_demo(0, 0); 
		}
	}
	waitKey(0);
	return 0;
}
/*
数据类型转换
*/
void Int_To_Str(int Int_i, char* String_s)
{
    
    
	int a;
	int b = 0;	 				//用于计数
	char* ptrfing, pBuffer[5];	//定义一个字符串数组和字符串指针,
	ptrfing = String_s; 		//内部指针指向外部指针,进行参数传递,是属于源参数传递(通过地址),
	if (Int_i < 10)		  		// 当整数小于10,转换为0x格式
	{
    
    
		*ptrfing++ = '0'; 		//单个数字前面补0
		*ptrfing++ = Int_i + 0x30;
	}
	else
	{
    
    
		while (Int_i > 0)
		{
    
    
			a = Int_i % 10;
			Int_i = Int_i / 10;
			pBuffer[b++] = a + 0x30; // 通过计算把数字编成ASCII码形式
		}
		b--;
		for (; b >= 0; b--) 		  // 将得到的字符串倒序
		{
    
    
			*(ptrfing++) = pBuffer[b];
		}
	}
	*ptrfing = '\0';
}


/*
画出轮廓、在图像上显示中点坐标、距离测量
*/
void Moments_demo(int, void*)
{
    
    
	char centers_X[10], centers_Y[10], centers_A[15], centers_L[15];
	//提取图像边缘
	Mat canny_out;
	Canny(dst, canny_out, threshold_value, threshold_value * 2, 3, false);
	//imshow("canny image", canny_out);


	//发现轮廓,找到图像轮廓
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	findContours(canny_out, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));


	//计算每个轮廓对象的矩
	vector<	Moments> contours_moments(contours.size());
	vector<Point2f> centers(contours.size());
	for (size_t i = 0; i < contours.size(); i++)
	{
    
    
		//计算矩
		contours_moments[i] = moments(contours[i]);
		//moments(InputArray  array,//输入数据
		//bool   binaryImage = false // 是否为二值图像
		centers[i] = Point(static_cast<float>(contours_moments[i].m10 / contours_moments[i].m00), static_cast<float>(contours_moments[i].m01 / contours_moments[i].m00));
		//图像中心Center(x0, y0)=(m10/m00,m01/m00)
	}
	src.copyTo(drawImage);
	for (size_t i = 0; i < contours.size(); i++)
	{
    
    
		printf("中心点 x:%.2f,      y:%.2f\n", centers[i].x, centers[i].y);
		printf("%d   面积:%.2f   弧长:%.2f\n\n", i, contourArea(contours[i]), arcLength(contours[i], true));
		//在图像上显示轮廓中心坐标
		Int_To_Str(centers[i].x/20, centers_X);
		Int_To_Str(centers[i].y/20, centers_Y);
		std::string const& xy0 = std::string("X0: ") + std::string(centers_X) + std::string(" Y0: ") + std::string(centers_Y);
		putText(drawImage, xy0, Point(5, 20), FONT_HERSHEY_SIMPLEX, 0.35, Scalar(100, 255, 0), 1, 1);
		//在图像上显示面积和弧长
		Int_To_Str(contourArea(contours[0]), centers_A); 
		Int_To_Str(arcLength(contours[0], true), centers_L);
		std::string const& al0 = std::string("A0: ") + std::string(centers_A) + std::string(" L0: ") + std::string(centers_L);
		putText(drawImage, al0, Point(5, 30), FONT_HERSHEY_SIMPLEX, 0.35, Scalar(100, 255, 0), 1, 1);
		//在图像上显示面积和弧长
		Int_To_Str(contourArea(contours[1]), centers_A);
		Int_To_Str(arcLength(contours[1], true), centers_L);
		std::string const& al1 = std::string("A1: ") + std::string(centers_A) + std::string(" L1: ") + std::string(centers_L);
		putText(drawImage, al1, Point(5, 40), FONT_HERSHEY_SIMPLEX, 0.35, Scalar(100, 255, 0), 1, 1);
		

		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(drawImage, contours, i, color, 2, LINE_AA, hierachy, 0, Point(0, 0));//绘制轮廓
		circle(drawImage, centers[i], 2, color, 2, LINE_AA);//绘制图形中心
	} 
	imshow(result, drawImage);
	return;
}

执行结果:x0、y0单位(mm)
在这里插入图片描述

在这里插入图片描述
移动1mm后:
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/soinlove36/article/details/119853091