opencv图像处理

1 保存带透明通道的图片

透明通道的无论怎么操作显示的多是带白色背景的, 只有通过设置imread(“”,imread_unchanged), 保存下来的才是透明通道的

unchanged: 4通道, 有个透明通道

  • IMREAD_UNCHANGED 加载代透明的图片
  • IMREAD_COLOR: 加载bgr图片
  • IMREAD_GRAYSCALE: 加载灰色图片
  • IMREAD_ANYCOLOR: 加载各种颜色
2. mat对象

mat对象: 用来存储图像的数据(二维数据)的内存对象

存储枚举结果的

  • 头部: 宽高, 数据类型,通道(单通道一般是灰色图像, 三通道一般是rgb图像. 四通道一般是透明通道)

  • 数据部分: 像素值

  • 创建Mat对象: Mat(size(x,y),cv_8uc3)

mat对象赋值: 只是地址的指向, 复制的变量, 他的内存地址是被复制的内存地址

mat对象拷贝或者克隆: 会创建一个新的mat对象, 而不是直接执行被拷贝的内存地址

mat.depth(): 返回的是个int类型, 图像的深度

mat. type(): 返回的是个int类型, 图像的类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JL3V6aZE-1656318416402)(D:\opencvSourse\openImg\imgType&&depth对应表.png)]

type数值:

  • type = depth + (channle -1)* 8

  • Mat src = Mat::zeros(Size(100, 100), CV_8UC3);
    cout << "图片类型8: " << src.type() << "\t" << "图像深度0: " << src.depth() << endl;
    //图片类型8: 16   图像深度0: 0
    
    Mat src1 = Mat::zeros(Size(100, 100), CV_8SC3);
    cout << "图片类型9: " << src1.type() << "\t" << "图像深度1: " << src1.depth() << endl;
    //图片类型9: 17   图像深度1: 1
    
    Mat src2 = Mat::zeros(Size(100, 100), CV_16UC1);
    cout << "图片类型9: " << src2.type() << "\t" << "图像深度2: " << src2.depth() << endl;
    //图片类型8+2: 10 图像深度2: 2
    
2 Mat 对象创建

Mat(x,x,type()) : 这样创建, 他默认不会全为0, 会自带一些颜色

Mat::zeros(Size(x,x), type()): 创建一个全是0的对象

Mat::zeros mat::once : 创建一个全是1的对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8JS5rn6r-1656318416403)()]

  • mat.clone(): clone是把所有的都复制过来,不论你是否设置了ROI、COI等影响,clone都会原封不动的克隆过来
  • copyTo是实现图像roi操作的正确方法
3 mat对象遍历
imshow("原图", img);
	Mat dst = Mat::zeros(img.size(), img.type());
	/*for (int row = 0; row < img.rows; row++)
	{
		for (int col = 0; col < img.cols; col++)
		{
			if (img.channels() == 3)
			{
				dst.at<Vec3b>(row, col)[0] = img.at<Vec3b>(row, col)[0];
				dst.at<Vec3b>(row, col)[1] = img.at<Vec3b>(row, col)[1];
				dst.at<Vec3b>(row, col)[2] = img.at<Vec3b>(row, col)[2];
			}
		}
	}*/
	/*imshow("遍历", dst);*/
	for (int row = 0; row < img.rows; row++)
	{
		uchar* y = img.ptr<uchar>(row);
		uchar* newI = dst.ptr<uchar>(row);
		for (int col = 0; col < img.cols; col++)
		{
			*newI++ = *y++;
			*newI++ = *y++;
			*newI++ = *y++;
		}
	}
	imshow("遍历", dst);
4 图像算术操作
  1. 注意点: 2张图片必须大小一致, 类型一致

    越界: opencv会自动去处理, 如果得大于255 ,会等于255, 如果小于0会等于0

    add, subtract,multiple, divide

  2. API

    addWeighted: 用来调节亮度和对比度(伪装透明度)

    • 参数2, 4 是权重(*权重)
    • 参数5: 合并的dst + 参数5
void Person::erithmetic(Mat& img)
{
	imshow("原图", img);
	Mat src = Mat::zeros(img.size(), img.type());
	src = Scalar(128, 128, 128);
	Mat dst;
	//伪装透明度
	addWeighted(img, 0.5, src, 0.5,0,dst);
	//addWeighted(img, 1.5, img, -0.2, 0, dst);
	imshow("位置透明度", dst);
}
5 与或非

mask满足条件:

  1. 大小必须跟原图一致, 类型为:CV_8UC1

bitwise_not (img,dst,mask) :如果mask区域为0的地方, 不进行取反, 为黑色0, mask为255的地方则进行取反

bitwise_and, bitweise_or, bitweise_xor

imshow("原图", img);
	Mat mask = Mat::zeros(img.size(), CV_8UC1);
	for (int row = img.rows / 4; row < img.rows / 4 * 3; row++)
	{
		uchar* p = mask.ptr<uchar>(row);
		for (int col = 0 ; col < img.cols / 4 * 2; col++)
		{
			*(p + (col + img.cols / 4)) = 255;
		}
	}
	imshow("mask", mask);
	Mat dst;
	bitwise_not(img, dst, mask);
	imshow("取反", dst);
6 像素信息统计
1. api, 可以通过方差或者均值过滤掉一些颜色
  • mean 返回的是scalar对象, 求取每个通道的均值
  • meanStdDev: 返回的是一个mat对象
  • minMaxLoc: 最大值最小值, 最大值的像素点位置, 值小值的point&
    • 参数1: 是一个double类型的引用(最小值)
    • 参数2: 是一个double类型的引用(最大值)
    • 参数3: 是一个点类型的地址 Loc.x
    • 参数3: 是一个点类型的地址 Loc.y
void Person::distribution(Mat& img)
{
	imshow("原图", img);
	double min; double max;
	Point minLoc; Point maxLoc;
	vector<Mat> vMat(img.channels());
	split(img, vMat);
	//均值
	Scalar mea = mean(img);
	Scalar mea1;
	vector<Mat> stddev(img.channels());
	for (int i = 1; i < vMat.size(); i++)
	{
		//最小最大值
		minMaxLoc(vMat[i-1], &min, &max, &minLoc, &maxLoc, Mat());
		cout << "通道:" << i << "最小值: " << min << "\t" << "最大值: " << max << "\t" << "最小值像素点: " << minLoc << "最大值像素点" << maxLoc << endl;
		cout << "通道:" << i << "均值: " << mea[i - 1] << endl;

		meanStdDev(vMat[i - 1], mea1, stddev[i-1], Mat());
		cout << "通道:" << i << "方差: " << stddev[i - 1] << endl;
	}
}
六.1. 图形绘制与填充
  1. 直线绘制
  • api: line(canvas, Point(x,y), Point(x1,y1), scalar(B,G,R), thickness, lineType,)

    lineType: LINE_AA(反锯齿)

  • rectangle(canvas, Rect(10, 200, 300, 300), Scalar(0, 0, 200), -1, 8);

  • circle(canvas, Point(250, 250), 100, Scalar(200, 10, 10), -1, 8);

  • ellipse(canvas, RotatedRect(Point(300, 100), Size(200, 100), 45.5),Scalar(0, 200, 10), -1, LINE_AA);

  1. 在img中写文字

    putText(canvas, “value”,Point(x,y), Font_, 1.0 , scalar(b,g,r), thickness, line_type)

7. 像素通道的分离和合并 (实现通道提取)

putText(): 向图片中写文字

scalar: 可以()里面可以只有一个值

split: 通道分离

  • 输入是img, 输出是vector <mat>容器

merge: 合并(输入,输出),

roi:

rect roi
mat sub = img(roi)// sub是roi在img中的地方, 是赋值
mat sub = img(roi).clone// 是引用
8 图像直方图

pic: 最高峰

range: 像素分类

bin: 每个分类好了的像素区域

逻辑: 分离 --> 计算直方图(calcHist)–>输出图片归一化(对应高度)–>把归一化绘制到输出img上

API:

  • calcHist
    • 图片指针
    • 多少张图片
    • 哪一个channels
    • mask
    • 直方图输出
    • 直方图的维度
    • 直方图有多少个bins: 是一个指针
    • rangs, 是一个指针
    • 每个bins自动分配取值范围
    • 累加器
void Person::calcHistogram(Mat& img)
{
	imshow("原图", img);
	vector<Mat> vImg;
	split(img, vImg);
	Mat bImg; Mat gImg; Mat rImg;
	int bins = 256;
	float rang[] = { 0,255 };
	const float* rangs = { rang };
	calcHist(&vImg[0], 1, 0, Mat(), bImg, 1, &bins, &rangs, true, false);
	calcHist(&vImg[1], 1, 0, Mat(), gImg, 1, &bins, &rangs, true, false);
	calcHist(&vImg[2], 1, 0, Mat(), rImg, 1, &bins, &rangs, true, false);

	imshow("b", bImg);
	imshow("g", bImg);
	imshow("r", bImg);

	Mat dst = Mat::zeros(Size(500, 300), img.type());
	int margin = 50;
	int height = dst.rows - 2 * margin;
	cout << "高度" << height << endl;
	normalize(bImg, bImg, 0, height, NORM_MINMAX);
	normalize(gImg, gImg, 0, height, NORM_MINMAX);
	normalize(rImg, rImg, 0, height, NORM_MINMAX);
	double step = (dst.cols - 2 * 50) / double(bins);
	cout << "步长" << step << endl;
	for (int i = 0; i < bins-1; i++)
	{
		//cout << bImg.at<double>(i, 0) << endl;
		line(dst, Point(i * step + margin, height + margin - bImg.at<float>(i, 0)), Point((i + 1) * step + margin, height + margin - bImg.at<float>(i + 1, 0)), Scalar(255, 0, 0), 2);
		//cout << "电一" << i * step + margin << endl;
		line(dst, Point(i * step + margin, height + margin - gImg.at<float>(i, 0)), Point((i + 1) * step + margin, height + margin - gImg.at<float>(i + 1, 0)), Scalar(0, 255, 0), 2);
		line(dst, Point(i * step + margin, height + margin - rImg.at<float>(i, 0)), Point((i + 1) * step + margin, height + margin - rImg.at<float>(i + 1, 0)), Scalar(0, 0, 255), 2);
	}
	imshow("输出", dst);
}
9 图像图均衡化
1. 思路: 划分bins --> 求出bins pik --> pik / rangs = 占比 --> 然后依次每个占比累加 / bins --> 求占比 --> 重新划分 --> 映射

用来增加许多图像的全局对比度, 当图像的有用数据的对比度相当接近的时候,通过这种方法,亮度可以更好地在直方图上分布, 比较相似度

2. API

equalizeHist: 直方图均衡化 equalizeHist: 只接受灰色图像

compareHist: 直方图比较

  • HISTCMP_BHATTACHARYYA: 巴斯距离比较 相似性越大, 值越大小
  • HISTCMP_CORREL: 相关性比较 相似性越大,值越大
3. 重点

下面代码最好把图片转换为hsv(色彩分明), 在进行比较

Mat myCalcHistogram(Mat& img)
{
	int bin1 = 256; int bin2 = 256; int bin3 = 256;
	int bins[] = { bin1,bin2,bin3 };
	float rang1[] = { 0,255 }; float rang2[] = { 0,255 }; float rang3[] = { 0,255 };
	const float* rangs[] = { rang1,rang2,rang3 };
	int channels[] = { 0,1,2 };
	Mat dst;
	calcHist(&img, 1, channels, Mat(), dst, 3, bins, rangs, true, false);

	return dst;
}
void Person::compare(Mat& img1, Mat& img2)
{
	cout << "aa" << endl;
	imshow("原图1", img1); imshow("原图2", img2);
	Mat histImg = myCalcHistogram(img1);
	Mat histImg2 = myCalcHistogram(img2);
	
	normalize(histImg, histImg, 0, 1, NORM_MINMAX);
	normalize(histImg2, histImg2, 0, 1, NORM_MINMAX);

	double minBha = compareHist(histImg, histImg2, HISTCMP_BHATTACHARYYA);
	double maxBha = compareHist(histImg, histImg, HISTCMP_BHATTACHARYYA);
	cout << "巴斯距离越大差异越大" << minBha << "相同图" << maxBha << endl;

	double minCor = compareHist(histImg, histImg2, HISTCMP_CORREL);
	double manCor = compareHist(histImg, histImg, HISTCMP_CORREL);
	cout << "相似性越大差异越小: 小" << minCor << "相同图: 大" << manCor << endl;

}
10 颜色查找表
1. api
  • applyColorMap(img,dst,color[])
  • color:是颜色表, 在百度中可以查的到, 速度快
2. 注意点: 只支持彩色图像和灰度图像
void Person::colorMap(Mat& img)
{
	int colormap[] =
	{
		COLORMAP_AUTUMN ,
		COLORMAP_BONE,
		COLORMAP_CIVIDIS,
		COLORMAP_DEEPGREEN,
		COLORMAP_HOT,
		COLORMAP_HSV,
		COLORMAP_INFERNO,
		COLORMAP_JET,
		COLORMAP_MAGMA,
		COLORMAP_OCEAN,
		COLORMAP_PINK,
		COLORMAP_PARULA,
		COLORMAP_RAINBOW,
		COLORMAP_SPRING,
		COLORMAP_TWILIGHT,
		COLORMAP_TURBO,
		COLORMAP_TWILIGHT,
		COLORMAP_VIRIDIS,
		COLORMAP_TWILIGHT_SHIFTED,
		COLORMAP_WINTER
	};
	imshow("原图", img);
	Mat dst;
	int index = 0;
	while (true)
	{
		applyColorMap(img, dst, colormap[index % 19]);
		index++;
		imshow("颜色表", dst);
		int key = waitKey(100);
		if (key == 27)
		{
			break;
		}
	}
}
11. 图像卷积(滤波)
1. 概率

卷积核窗口系数: 卷积核里面的数据

原理: (卷积系数/权重 * 处于卷积核位置的img的像素相加) / 卷积核面积 = result,取整(四舍五入), 把原图中处于卷积核的中心位置像素替换为 result. 卷积核一次前进移动, 反复执行.

2. 卷积的作用
  1. 实现图像的模糊
  2. 计算图像的梯度
  3. 发现边缘
  4. 进行噪声抑制
  5. 图像的锐化或者增强
2. 处理边缘
  • 边缘填0
  • border_default
  • border_replicate
  • border_warp
  • border_reflect_101
  • border_constant
3. API

blur: 一般用于处理图像的随机噪声

boxFilter 是 blur的快速版本, 最好使用boxFilter

  • 锚定位置
    • Point(-1,-1)——卷积核与原图左边重合;卷积核与原图上边重合;锚定点位于卷积核中心
      注意:卷积核大小不同,如3x3,5x5,则卷积核中心不同(偶数无核)
    • Point(0,0)——卷积核与原图左边重合;卷积核与原图上边重合;锚定点位于Point(0,0)
    • Point(1,1)——卷积核与原图左边-1重合;卷积核与原图上边-1重合;锚定点位于Point(1,1)
    • Point(2,2)——卷积核与原图左边-2重合;卷积核与原图上边-2重合;锚定点位于Point(2,2)
4. 注意点:

当卷积核大小为偶数: 其实这个时候中心也为(ksize/2), 对2x2的卷积核,中心位置为Point**(1,1)4x4的卷积核中心位置为Point(2,2)**。

imshow("原图", img);
	Mat dst = Mat::zeros(img.size(),img.type());
	Mat dst2;
	for (int row = 1; row < img.rows -1; row++)
	{
		for (int col = 0; col < img.cols; col++)
		{
			int b = round((img.at<Vec3b>(row - 1, col - 1)[0] + img.at<Vec3b>(row - 1, col)[0] + img.at<Vec3b>(row - 1, col + 1)[0] +
				img.at<Vec3b>(row, col - 1)[0] + img.at<Vec3b>(row, col)[0] + img.at<Vec3b>(row, col + 1)[0] +
				img.at<Vec3b>(row + 1, col - 1)[0] + img.at<Vec3b>(row + 1, col)[0] + img.at<Vec3b>(row + 1, col + 1)[0]) / 9);

			int g = round((img.at<Vec3b>(row - 1, col - 1)[1] + img.at<Vec3b>(row - 1, col)[1] + img.at<Vec3b>(row - 1, col + 1)[1] +
				img.at<Vec3b>(row, col - 1)[1] + img.at<Vec3b>(row, col)[1] + img.at<Vec3b>(row, col + 1)[1] +
				img.at<Vec3b>(row + 1, col - 1)[1] + img.at<Vec3b>(row + 1, col)[1] + img.at<Vec3b>(row + 1, col + 1)[1]) / 9);

			int r = round((img.at<Vec3b>(row - 1, col - 1)[2] + img.at<Vec3b>(row - 1, col)[2] + img.at<Vec3b>(row - 1, col + 1)[2] +
				img.at<Vec3b>(row, col - 1)[2] + img.at<Vec3b>(row, col)[2] + img.at<Vec3b>(row, col + 1)[2] +
				img.at<Vec3b>(row + 1, col - 1)[2] + img.at<Vec3b>(row + 1, col)[2] + img.at<Vec3b>(row + 1, col + 1)[2]) / 9);
			dst.at<Vec3b>(row, col)[0] = b; dst.at<Vec3b>(row, col)[1] = g; dst.at<Vec3b>(row, col)[2] = r;
		}
	}
	imshow("手动blue", dst);
	blur(img, dst2, Size(3, 3), Point(-1, -1),BORDER_DEFAULT);
	imshow("blur", dst2);
5. 处理边缘像素

进行卷积之前就应该填充好

填充类型方法
BORDERT_CONSTANT(常量填充),填充的是0iiii|abc|iii (i:常量,abc:像素值)
border_replicate(填充的是2头的值)aaa|abc|hhh
border_warp(尾填到头,头填到尾)cba|abc|abc
border_reflect_101(看上去最合理)fcb|abcf|cba
border_default(看上去最合理)fcb|abcf|cba
1. 边缘填充:
  • copyMakeBorder // 可以用来做边框, 也可以用来卷积
    • copyMakeBorder(src,dst,margin, margin1, margin2,margin, 填充方式, 填充的颜色)
void Person::fill(Mat& img)
{
	imshow("原图", img);
	int top = 2; int bottom = 2; int left = 3; int right = 3;
	int margin[] = { top,bottom,left,right };
	Mat dst; Mat dst1;
	copyMakeBorder(img, dst, margin[0], margin[1], margin[2], margin[3], BORDER_CONSTANT, Scalar(0, 0, 250));
	imshow("dst", dst);
	blur(dst, dst1, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
	imshow("blur", dst1);
	cout << img.rows << "\t" << dst.rows << "\t" << dst1.rows << endl;
}
12 图像模糊
  1. 高斯模糊

    1. 优点: 会更好的保留中心点像素, 对轮廓保存好

    2. 卷积核系数不一样, 非均值, 越是中心位置权重系数越高, 中心化对称(高斯数学公式),

    3. API: GuassianBlur

    • 设置了第三个参数, 第四个参数会自动换算, 只要当size = 0的时候才需要设置第四个参数
    • 高斯模糊默认是中心位置所有不需要设定锚定点
  2. 盒子模糊(均值模糊)

    1. 优点: 可以对图像任意一个方向模糊 (调节卷积核大小)
    2. API: boxfilter
      • 参数3: img深度(如果-1就是与原图一致)
      • 参数5: 锚定点,
      • 参数6: 对卷积核归一化, 加和 = 1
void Person::fill(Mat& img)
{
	imshow("原图", img);
	int top = 2; int bottom = 2; int left = 3; int right = 3;
	int margin[] = { top,bottom,left,right };
	Mat dst; Mat dst1; Mat dst2;
	copyMakeBorder(img, dst, margin[0], margin[1], margin[2], margin[3], BORDER_CONSTANT, Scalar(0, 0, 250));
	imshow("dst", dst);
	blur(img, dst1, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
	imshow("blur", dst1);
	
	boxFilter(img, dst2, -1, Size(3, 3), Point(-1, -1), true, BORDER_DEFAULT);
	imshow("dst2", dst2);
	cout << img.rows << "\t" << dst.rows << "\t" << dst1.rows <<"\t" << dst2.rows<<endl;
}
13 自定义卷积核(自定义滤波器)
1. API: filter2D
  • 参数5: 用来提升亮度
2.API: convertScaleAbs(Mat,Mat) :

装换为8U的, 并且所有的值为正数

3. 注意点:
  1. 均值滤波如果原图是16或者16以下的depth,kernel depth * 2

  2. 非均值滤波: 非均值卷积核depth应该设置大些, (不然数据会溢出, 有负数), 然后使用abs转换全为正数, 不然输出的全是空白图像

void Person::myFilt(Mat& img)
{
	imshow("input", img);
	Mat dst; Mat dst1;
	int width = 12;

	Mat kernel = Mat::ones(width, width, CV_16F) / float(width * width);
	filter2D(img, dst, -1, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
	imshow("均值滤波", dst);
	cout << img.rows << "\t" << dst.rows << endl;

	Mat kernel1 = (Mat_<int>(2, 2) << 1, 0, 0, -1);
	filter2D(img, dst1, CV_32F, kernel1, Point(-1, -1), 128, 4);
	convertScaleAbs(dst1, dst1);
	imshow("非均值滤波", dst1);
}
14 图像梯度(一阶导数)

robot算子: 非均值卷积, 求出x的梯度和y梯度相加

1.sobel 算子
  • 参数4,5: 哪个方向的梯度(x,y),0为假, 1为真
  • 参数7: 扩张的倍数
  • 参数8: 用来提升亮度
2. scharr算子

sobel的增强版, scharr算子的卷子核系数大

三种梯度算子呈现强度: robot < sobel < scharr

imshow("原图", img);
	Mat gradX; Mat gradY;
	Mat robotX = (Mat_<int>(2, 2) << 1, 0, 0, -1);
	Mat robotY = (Mat_<int>(2, 2) << 0, 1, -1, 0) ;
	filter2D(img, gradX, CV_32F, robotX, Point(-1, -1), 0, 4);
	filter2D(img, gradY, CV_32F, robotY);
	convertScaleAbs(gradX, gradX);
	convertScaleAbs(gradY, gradY);
	Mat dst;
	add(gradX, gradY, dst);
	imshow("robot梯度", dst);

	/*Mat sobelx = (Mat_<int>(3,3)<<-1,0,1,
									-2,0,2,
									-1,0,1);
	
	Mat sobelY = (Mat_<int>(3, 3)  -1, -2, -1,
								   0, 0, 0,
								   1, 2, 1);

	Mat gradSobelX; Mat gradSobelY;
	filter2D(img, gradSobelX, CV_32F, sobelx, Point(-1, -1));
	filter2D(img, gradSobelY, CV_32F, sobelY, Point(-1, -1));
	convertScaleAbs(gradSobelX, gradSobelX);
	convertScaleAbs(gradSobelY, gradSobelY);
	Mat dst1; Mat result;
	add(gradSobelX, gradSobelY, dst1);*/

	Mat dst1;
	Sobel(img, gradX, CV_32F, 1, 0, 3, 1, 0, BORDER_DEFAULT);
	Sobel(img, gradY, CV_32F, 1, 0, 3, 1, 0, BORDER_DEFAULT);
	convertScaleAbs(gradX, gradX);
	convertScaleAbs(gradY, gradY);
	//因为sobel卷积核其中有些系数为2, 通过abs转化会放大梯度
	addWeighted(gradX, 0.5, gradY, 0.5, 0, dst1); //不然图片会偏白, 
	imshow("sobel", dst1);

	Mat dst2;
	Scharr(img, gradX, CV_32F, 1, 0, 1, 0);
	Scharr(img, gradY, CV_32F, 0, 1, 1, 0, 4);
	convertScaleAbs(gradX, gradX);
	convertScaleAbs(gradY, gradY);
	addWeighted(gradX, 0.5, gradY, 0.5, 0, dst2); 

	imshow("scharr", dst2);
15 图像边缘发现(二阶导数)

四邻域, 八邻域, 拉普拉斯变种

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UKNPniCL-1656318416405)(D:\opencvSourse\openImg\拉普拉斯分类.png)]

推导公式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PWX2C5wE-1656318416406)(D:\opencvSourse\openImg\图像锐化原理.png)]

图像锐化: 原图 + 拉普拉斯得出来的结果(图像的边缘跟梯度比较尖锐的值加到原图上), 可以很好的反映图像的边缘, 也可以反模糊

拉普拉斯的卷积核必须是奇数

缺点: 很容易受噪声干扰(对小的细节造成干扰)

拉普拉斯api得出来的是边缘

void Person::laplasi(Mat& img)
{
	cout << img.channels() << endl;
	imshow("原图", img);
	Mat Box = (Mat_<int>(3, 3) << 0, -1, 0,
							    -1, 5, -1,
							    0, -1, 0);
	Mat dst;
	filter2D(img, dst, CV_32F, Box,Point(-1,-1),0,4);
	convertScaleAbs(dst, dst);
	imshow("拉普拉斯", dst);

	Mat dst1; Mat dst2;
	Laplacian(img, dst1, -1);
	add(img, dst1, dst2);
	imshow("锐化", dst1);
}
16 usm锐化

原理: blur/高斯 - 拉普拉斯算子

相对于拉普拉斯, 他会处理掉极小值(对小的细节不造成干扰), 不容易受噪声干扰, 对大的细节进行锐化

imshow("原图", img);
	Mat gua; Mat lap;
	GaussianBlur(img, gua, Size(3,3),0);
	Laplacian(img, lap, -1, 3, 1, 0);
	Mat result;
	addWeighted(gua, 1, lap, -0.7, 0, result);
	imshow("usm", result);
17 图像的噪声和去噪
  1. API:

    • randn 产生噪声
      • 参数1: 输入图像
      • 均值
      • 方差
  2. 去噪:

    1. 中值滤波: 能反映信号理想的样子, 去除极致点,

      1. 原理: 对图像某一块区域进行排序, 取出sort中间的值替换这块局域的中心点

        还有最小值滤波(sort[0]替换中心点), 最大值滤波(sort[lenght-1]替换中心点)

      2. 作用: 最适用于椒盐噪声非0就255

      3. 重要: 卷积核必须是奇数而且是大于1

    2. 均值滤波: 没有反映信号本来的样子, 会扰动

    3. API:

      • medianBlur: 均值滤波

        没有考虑中心像素的权重, 可能会导致图片破坏

      • GaussianBlur 高斯滤波 没有考虑中心像素点与周围像素点差值很大

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DqhqGCcS-1656318416407)(D:\opencvSourse\openImg\均值滤波和中值滤波.png)]

18 边缘保留滤波(EPF)

考虑图像的梯度(边缘):高斯双边, 均值前移, 非局部均值去噪, 局部均值方差

1. EPF概念
  1. 高斯双边模糊

    1. 原理: 当差异比较大的时候(边缘),权重会趋向于0(有边缘的时候不进行模糊)
    2. API: bilaterFilter
      • 参数3: 过滤过程中每个像素领域的直径范围, 如果这个值是非正数, 则函数会从第五个参数sigmaSpace计算该值
      • 参数4: 颜色过滤器, 这个参数越大, 表明该像素领域内有越宽广的颜色会混合到一起, 产生较大的半相等颜色区域(理解为中心像素权重, 越大中心像素权重越高, 细微的部分会丢失),
      • 参数5: 空间滤波器, 如果该值较大, 则意味着越远的像素将相互影响, 从而使更大的区域中足够相似的颜色获取相同颜色(理解为,sigmaS越大,去噪越明显, 一般为Half_size为4,可以选用2sigma原则,设置为2,)
  2. 非局部均值滤波

    1. 简单理解原理: 相似像素块, 权重比较大, 不相似的权重比较小

    2. API

      • fastNIMeansDenoising 速度慢
        1. 参数三: h 决定过滤器强度。h 值高可以很好的去除噪声,但也会把图像的细节抹去。(取 10 的效果不错)
        2. 参数五: 在划分区域的卷积核
        3. 参数六: 搜索窗口(划分区域)
      • fastNIMeansDenoisingColord 彩色版本
void Person::noise(Mat& img)
{
	Mat result1; Mat result2;
	Mat dst = img.clone();
	imshow("原图", img);
	//salt and pepper
	RNG r(12345);
	int ipt = 1000;
	for (int i = 0; i < ipt; i++)
	{
		int xNum = r.uniform(0, img.cols);
		int yNum = r.uniform(0, img.rows);
		if (i % 2 == 1)
		{
			
			img.at<Vec3b>(xNum, yNum) = Vec3b(255, 255, 255);
		}
		else
		{
			img.at<Vec3b>(xNum, yNum) = Vec3b(0, 0, 0);
		}
	}
	imshow("椒盐噪声", img);
	medianBlur(img, result1, 3);
	imshow("椒盐去噪", result1);
	GaussianBlur(img, result2, Size(3, 3), 0);
	imshow("高斯去噪", result2);


	//高斯噪声
	Mat res1; Mat res2;
	Mat src = Mat::zeros(img.size(), img.type());
	randn(src, Scalar(25,15,45), Scalar(60,40,30));
	add(dst, src, dst);
	imshow("高斯", dst);
	bilateralFilter(dst, res1, 0, 100, 10);
	imshow("双边模糊", res1);
	fastNlMeansDenoisingColored(dst, res2, 3, 3, 7, 21);
	imshow("非局部去噪",res2);

}
18边缘提取
1. 基本概念
  • 边缘法线: 与边缘垂直的线, 在图像像素强度变化最大
  • 边缘强度
2. 边缘类型

​ 实际图像可能不平整有噪声

  1. 跃迁类型: 只有陡坡或者下坡
  2. 屋脊类型: 有上坡和下坡
3. 基于梯度的边缘提取

步骤:

  1. 去噪(卷积核不能太大, 不然会破坏掉边缘, 一般使用3或者5),
  2. 基于梯度提取边缘: robot, sobel, prewit Operator

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WSFkBplS-1656318416407)(D:\opencvSourse\openImg\梯度提取边缘算子.png)]

  1. 基于阈值T,得到边缘(梯度>T保留, <丢弃)

    问题: 基于T的会不够连贯

API: canny 输出是一个二值图像

非最大抑制: 求出角度, 如果中心像素大于2侧梯度值则保留, 否则丢弃, 然后进行阈值连接

阈值连接: T1 / T2 = 2 ≈ 2~3, 大于T1全部保留, 小于T2全部丢弃, t1至T2之间的如果可以连接则保留, 否则丢弃

参数:

  1. 参数5: suobel算子大小
  2. 参数6: 为计算图像梯度幅度(gradient magnitude)的标识。其默认值为 False。如果为 True,则使用更精确的 L2 范数进行计算(即两个方向的导数的平方和再开方),否则使用 L1 范数(直接将两个方向导数的绝对值相加)。
  3. 参数3,4: 高低阈值的比值应该是2~3
void myCanny(int min, void* img)
{
	Mat newImg = *(Mat*)img;
	Mat dst; Mat dst1;
	Canny(newImg, dst, min, 200, 3, false);
	bitwise_and(newImg, newImg, dst1, dst);
	imshow("边缘提取", dst1);
}
void Person::canny(Mat& img)
{
	string barName = "提取范围";
	string winName = "边缘提取";
	namedWindow(winName, WINDOW_AUTOSIZE);
	imshow("原图", img);
	int min = 50;
	int max = 120;
	createTrackbar(barName, winName, &min, max, myCanny,(void*)&img);
	myCanny(0, (void*)&img);
}
19 二值图像概念 sic(Bolb分析)

必须是灰色图像

对机器视觉或者工业领域

灰度图像 : 单通道, 取值范围0~255

二值图像: 单通道, 要么是0要么是255, 以黑色作为背景

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IfSlKbwl-1656318416408)(D:\opencvSourse\openImg\二值分割5钟方法.png)]

二值化

  • THRESH_BINARY
  • THRESH_BINARY_INV

阈值化

  • THRESH_TRUNC: 截断, 对应第三种方式
  • THRESH_TOZEREO:
  • thresh_TOZERO_INV

API:

threshold: 只接受灰色图像

void Person::myThreshold(Mat& img)
{
	Mat dst;
	cvtColor(img, img, COLOR_BGR2GRAY);
	imshow("原图灰色图像", img);
	//二值化
	threshold(img, dst, 127, 255, THRESH_BINARY);
	imshow("二值化", dst);
	//反二值化
	threshold(img, dst, 127, 255, THRESH_BINARY_INV);
	imshow("反二值化", dst);
	//阈值化切割, 小于阈值化保持原数据, 否则为T
	threshold(img, dst, 127, 255, THRESH_TRUNC);
	imshow("阈值化切割", dst);
	//阈值化,大于T的保持原数据, 其他的为0
	threshold(img, dst, 127, 255, THRESH_TOZERO);
	imshow("阈值化", dst);
	//反阈值化, 
	threshold(img, dst, 127, 255, THRESH_TOZERO_INV);
	imshow("反阈值化", dst);
}
//返回二值化分割
Mat Person::myThresh(const Mat& img)
{
	Mat newImg = img.clone();
	GaussianBlur(newImg, newImg, Size(3, 3), 0);
	cvtColor(newImg, newImg, COLOR_BGR2GRAY);
	Mat dst;
	threshold(newImg, dst, 0, 255, THRESH_BINARY | THRESH_OTSU);
	return dst;
}
19.1 全局阈值

​ 概述: 全局阈值, 自适应阈值, 缺点: 不适用与光线不均匀的情况

  1. 通过mean, 取0号下标得均值,这个均值设为T

    1. 缺点: 不能反应图像像素分布情况
  2. OTSU

    1. api: THRESH_OTSU, 返回值是一个double类型
    2. 计算类内方差: 比重 * 方差 + 比重1*方差1, 取切割后,类内方差最小的阈值T

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kr1Qrs0g-1656318416409)(D:\opencvSourse\openImg\OTSU解释.png)]

  3. 三角法

    1. api: thresh_triangle
    2. 缺点: 只对单峰比较友好,适合医学领域:x光片, 生物图像.
    void Person::tThreshold(Mat& img)
    {
    	Mat dst;
    	cvtColor(img, img, COLOR_BGR2GRAY);
    	imshow("原图", img);
    	Scalar m = mean(img);
    	threshold(img, dst, m[0], 255, THRESH_BINARY);
    	imshow("均值", dst);
    	//otsu
    	double otsu = threshold(img, dst, 0, 255, THRESH_BINARY | THRESH_OTSU);
    	imshow("otsu", dst);
    	//三角法
    	double angle = threshold(img, dst, 0, 255, THRESH_BINARY | THRESH_TRIANGLE);
    	imshow("三角法", dst);
    	cout << "otsu切割阈值T: " << otsu << "\t" << "三角法: " << angle << endl
    		<<"\t" << "均值:"<<m[0];
    }
    
  4. 自适应阈值

​ 概述: 全局阈值的局限性: 对光线照度不均匀的图像容易错误的二值化分割, 自适应阈值对图像模糊求差然后二值化,叫做自适应的高斯分割或者是自适应均值分割, 相比于自适应阈值, 会更好的保留细节

相对于全局自适应, 会提取更多的梯度

原图-模糊后的图+偏执常量 > 0 = 255, 否则=0

  1. API: adaptiveThreshold, 卷积核必须为奇数(倒数第二个)
    • ADAPTIVE_THRESH_MEAN_C 盒子模糊
    • ADAPTIVE_THRESH_GAUSSIAN_C 高斯模糊
//自适应阈值分割
void Person::myAdaptive(Mat &img)
{
	Mat dst;
	cvtColor(img, img, COLOR_BGR2GRAY);
	imshow("原图", img);
	threshold(img, dst, 0, 255, THRESH_BINARY | THRESH_OTSU);
	imshow("otsu", dst);
	//自适应
	adaptiveThreshold(img, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 21, 5);
	imshow("自适应", dst);
}
20 ccl连通组件扫描(默认值是8领域)

如果是白色对象 将过滤点不会进行处理, 只有为黑色的时候才会考虑连通性

opencv是基于块扫描结合决策表(DT) ==> BBDT

  1. 概念: 联通组件标记: CCL

  2. 算法:

    • 基于像素的扫描的方法

      缺点: 有大量重复的扫描, 而且不规则扫描

    • 基于块扫描的方法

    • 两步法扫描

      概率: 对前景像素点产生一个临时标记, 通过连通性进行等价队列合并, 然后获取lable

      决策表:DT

    • 现在opencv联通组件采用的是BBDT: 块扫描+决策表

  3. api: connectedComponents, 背景必须为黑色, 只能知道有多少个联通组件

    1. 输出图片类型为cv_32s. 数量包括背景, 真实数量=result - 1, 组件第一个是背景
    2. 参数:
      • 参数3: 选择几领域(8领域或者4领域)
      • 参数4: 输出类型, 默认是cv_32s
    3. 返回值是组件个数+ 1个背景

    api:connectedComponentsWithStats

    1. 携带附加信息(像素值,外接矩形大小,中心位置)
    2. 第三个mat对象:外接矩形,矩形面积
      1. stats: 前四个值是连通组件外接矩形的信息(前2个是初始点, 后2个是宽高), 最后一个值是统计出来的这个前景对象的面积(像素面积)
    3. 第四个mat对象:组件的中心坐标

    一般先进行高斯模糊降噪

    void Person::connectComp(Mat& img)
    {
    	RNG rn(12345);
    	imshow("原图", img);
    	Mat denoi;
    	GaussianBlur(img, denoi, Size(11, 11), 0);
    	imshow("去噪", denoi);
    	Mat grey;
    	cvtColor(denoi, grey, COLOR_BGR2GRAY);
    	Mat cutting;
    	threshold(grey, cutting, 0, 255, THRESH_BINARY | THRESH_OTSU);
    	imshow("二值化", cutting);
    	Mat labels = Mat::zeros(img.size(), CV_32S);
    	int num = connectedComponents(cutting, labels, 8, CV_32S, CCL_DEFAULT);
    	cout << "数量" << num << endl;
    	//染色
    	vector<Vec3b>color(num);
    	color[0] = Vec3b(0, 0, 0);
    	for (int index = 1; index < num; index++)
    	{
    		color[index] = Vec3b(rn.uniform(0, 256), rn.uniform(0, 256), rn.uniform(0, 256));
    	}
    	Mat result = Mat::zeros(img.size(), CV_8UC3);
    	for (int x = 0; x < result.rows; x++)
    	{
    		for (int y = 0; y < result.cols; y++)
    		{
    			result.at<Vec3b>(x, y) = color[labels.at<int>(x, y)];
    		}
    	}
    	//显示详细信息
    	Mat stats; Mat centroids;
    	int num1 = connectedComponentsWithStats(cutting, labels, stats, centroids, 8, CV_32S, CCL_DEFAULT);
    	string numn = to_string(num1);
    	for (int i = 1; i < num1; i++)
    	{
    		//center
    		double centerX = centroids.at<double>(i, 0);
    		double centerY = centroids.at<double>(i, 1);
    		//ractangle
    		int racLeft = stats.at<int>(i, CC_STAT_LEFT);
    		int racTop = stats.at<int>(i, CC_STAT_TOP);
    		int racWidth = stats.at<int>(i, CC_STAT_WIDTH);
    		int racHeight = stats.at<int>(i, CC_STAT_HEIGHT);
    		int area = stats.at<int>(i, CC_STAT_AREA);
    		cout << "面积" << i << area << endl;
    		string s =  to_string(area);
    		circle(result, Point(centerX, centerY), 3, Scalar(255, 20, 20), 2, 8);
    		Rect rect = Rect(racLeft, racTop, racWidth, racHeight);
    		rectangle(result, rect, Scalar(0, 0, 200), 2, LINE_8);
    		putText(result, s, Point(centerX, centerY), FONT_HERSHEY_PLAIN, 1.0, Scalar(0, 255, 0), 2, 8);
    		putText(result, numn, Point(50,50), FONT_HERSHEY_PLAIN, 3, Scalar(0, 255, 0), 2, 8);
    	}
    	imshow("切割染色", result);
    }
    
20.1 图像轮廓发现
1. 轮廓发现(二值化的前景边缘)
  1. 基本概率: 理解为图像边界, 主要针对二值图像, 轮廓是一系列点的集合

    基于联通组件, 反映图像拓扑结构

  2. 算法:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FeJh7Je9-1656318416410)(D:\opencvSourse\openImg\轮廓发现.png)]

  3. API:

    • findContours

      轮廓: 是点的集合vector<vector<point>>P 多个组件, 组件里面是像素

      层次: vec4i

      拓扑结构: list或者tree或者最外层最大的轮廓(external) retr

      编码方式:

      • chain_approx_simple

        hirearchy里面只存储: 4个顶点位置

      • chain_approx_none

        hirearchy里面存储所有轮廓的点位

    • drawContours

      contourldx: 绘制的是哪一个轮廓,如果是-1就是绘制全部

    void Person::myContour(Mat& img)
    {
    	//binaryimg是一个处理过的二值化图像
    	Mat binaryImg = this->myThresh(img).clone();
    	Mat dst = Mat::zeros(img.size(),CV_8UC3);
    	Mat dst1 = dst.clone();
    	Mat dst2 = dst.clone();
    	imshow("二值化", binaryImg);
    	vector<vector<Point>>contours;
    	vector<Vec4i>hierarchy;
    	findContours(binaryImg, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
    	for (int i = 0; i < contours.size(); i++)
    	{
    		drawContours(dst, contours, i, Scalar(0, 255, 0), 2, LINE_8);
    	}
    	imshow("轮廓", dst);
    	findContours(binaryImg, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
    	drawContours(dst1, contours, -1, Scalar(255, 0, 0), 2, 8);
    	imshow("最大轮廓", dst1);
    }
    
20.2 轮廓计算
  1. 计算公式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yC6XUZCC-1656318416411)(D:\opencvSourse\openImg\轮廓面积和周长计算.png)]

  1. contourArea: 轮廓面积 contourArea[contour1]

    arcLength: 轮廓周长(contour1, 是否闭合)

    boundingRect: 最大外接矩形

    nimAreaRect: 最小矩形 , 数据类型是RotatedRect

    //轮廓细节
    void Person::contourDetails(Mat& img)
    {
    	Mat result = Mat::zeros(img.size(), CV_8UC3);
    	imshow("原图", img);
    	GaussianBlur(img, img, Size(3, 3), 0);
    	cvtColor(img, img, COLOR_BGR2GRAY);
    	Mat dst;
    	threshold(img, dst, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
    	imshow("二值化分割", dst);
    	vector<vector<Point>>contours;
    	vector<Vec4i>hierarchy;
    	//Mat result = Mat::zeros(img.size(), CV_8UC3);
    	findContours(dst, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
    	/*double minLength;
    	cout << "输入最小周长" << endl;
    	cin >> minLength;
    	double minArea;
    	cout << "输入最小面积" << endl;
    	cin >> minArea;*/
    	for (int i = 0; i < contours.size(); i++)
    	{
    		if (arcLength(contours[i],true) < 100 || contourArea(contours[i]) < 10) continue;
    		//最大外接矩形
    		Rect maxRect = boundingRect(contours[i]);
    		rectangle(result, maxRect, Scalar(200, 20, 20), 2, 8);
    		//最小外接矩形
    		RotatedRect minRect = minAreaRect(contours[i]);
    		ellipse(result, minRect, Scalar(20, 200, 20), 2, 8);
    		Point2f pts[4];
    		//通过点连接成线绘制最小外接矩形
    		minRect.points(pts);
    		for (int i = 0; i < 4; i++)
    		{
    			line(result, pts[i], pts[(i + 1)%4], Scalar(20, 20, 200), 2, 8);
    		}
    		drawContours(result, contours, -1, Scalar(100, 100, 100), 2, 8);
    		cout << "轮廓" << i << "面积: " << contourArea(contours[i]) << "\t" << "轮廓" << i << "周长: " << arcLength(contours[i], true) << endl;
    	}
    	imshow("输出", result);
    }
    
20.3 轮廓匹配

作用: 可以匹配大小不一致, 旋转不一致的图像

API

  • Moments : 几何矩 Moments(contours[i])

    作用: 计算弧矩, 计算中心位置

    根据原理推中心矩: mm.m10 / mm.00 = x || mm.m01 / mm.00 = y

  • HuMoments: 弧矩(Moments, mat对象) : 选择不变性, 缩放不变性

  • matchShapes: 比较弧矩 参数3:contours_match_l1/l2/l3, 第一种比较效果比较好

void contoursFn( Mat &dst, const vector<vector<Point>> &imgContours, const vector<vector<Point>>& srcContours)
{
	Moments srcMm = moments(srcContours[0]);
	Mat srcHu;
	HuMoments(srcMm, srcHu);
	for (int i = 0; i < imgContours.size(); i++)
	{
		Mat imgHu;
		Moments imgMm = moments(imgContours[i]);
		double pX = imgMm.m10 / imgMm.m00;
		double pY = imgMm.m01 / imgMm.m00;
		circle(dst, Point(pX, pY), 3, Scalar(200, 200, 20), 2, LINE_8);
		HuMoments(imgMm, imgHu);
		double ss = matchShapes(imgHu, srcHu, CONTOURS_MATCH_I1,0);
		//cout << ss << endl;
		if (ss < 2.0)
		{
			cout << ss << endl;
			drawContours(dst, imgContours, i, Scalar(20, 20, 200), 2, 8);
		}
		else
		{
			cout << "匹配不成功" << endl;
		}
	}
	imshow("axx", dst);
	/*for (int i = 0; i < srcContours.size(); i++)
	{
		moImg.push_back(moments(srcContours[i]));
	}*/
	
}
void Person::contourComp(Mat& img, Mat& src)
{
	namedWindow("匹配轮廓图", WINDOW_FREERATIO);
	imshow("匹配轮廓图", src);
	Mat binaryImg = this->myThresh(img);
	Mat binarysrc = this->myThresh(src);
	vector<vector<Point>> imgContours;
	vector<vector<Point>> srcContours;
	vector<Vec4i> hierarchy;
	vector<Vec4i> hierarchy1;
	findContours(binaryImg, imgContours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
	findContours(binarysrc, srcContours, hierarchy1, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
	contoursFn(img, imgContours, srcContours);
}
20.4 轮廓逼近与拟合
  1. 轮廓拟合和逼近(进行轮廓发现之后通过api越来越接近真实的情况)

    1. 概念:

      • 轮廓逼近, 本质是减少编码点, 轮廓逼近的越厉害, 编码点增多

      • 拟合圆, 生成最相似的圆或者椭圆

    2. API:

      1. approxPolyDP: 轮廓逼近, 一般用来区分图形

        参数:

        • 参数1: contours[i]

        • 参数2: mat对象 里面有存放每个点,

        • 参数3: 精度,值越低精度越高(编码点越多)(一般为4)

        • 参数4:是否为闭合区

    3. fitEllipse

      1. 图像拟合, 返回值是一个rotateRact,
      2. 通过rotateRact.size.width/height获取拟合之后的长宽
      3. 通过rotateRact.center, 获取拟合之后的中心点
void Person::contourProx(Mat& img)
{
	imshow("原图", img);
	Mat binaryImg = this->myThresh(img);
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(binaryImg, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
	for (int i = 0; i < contours.size(); i++)
	{
		Mat poly;
		approxPolyDP(contours[i], poly, 4, true);
		cout << "图形:" << i << "行数 " << poly.rows << "列数: " << poly.cols << endl;
		double len = arcLength(contours[i], true);
		double Area = contourArea(contours[i]);
		Moments mm = moments(contours[i]);
		double pX = mm.m10 / mm.m00;
		double pY = mm.m01 / mm.m00;
		//if (poly.rows = 4)
		//{
		//	putText(img, "矩形", Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
		//	string Sarea = "面积: " + to_string(Area);
		//	string Slen = "周长: " + to_string(len);
		//	/*putText(img, Sarea, Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
		//	putText(img, Slen, Point(pX, pY - 20), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);*/
		//	circle(img, Point(pX, pY), 4, Scalar(20, 20, 200), 2, LINE_8);
		//}
		//if (poly.rows = 3)
		//{
		//	putText(img, "三角形", Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
		//	string Sarea = "面积: " + to_string(Area);
		//	string Slen = "周长: " + to_string(len);
		//	/*putText(img, Sarea, Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
		//	putText(img, Slen, Point(pX, pY - 20), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);*/
		//	circle(img, Point(pX, pY), 4, Scalar(20, 20, 200), 2, LINE_8);
		//}
		//if (poly.rows = 6)
		//{
		//	putText(img, "6边形", Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
		//	string Sarea = "面积: " + to_string(Area);
		//	string Slen = "周长: " + to_string(len);
		///*	putText(img, Sarea, Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
		//	putText(img, Slen, Point(pX, pY - 20), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);*/
		//	circle(img, Point(pX, pY), 4, Scalar(20, 20, 200), 2, LINE_8);
		//}
		//if (poly.rows > 12)
		//{
		//	putText(img, "圆", Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
		//	string Sarea = "面积: " + to_string(Area);
		//	string Slen = "周长: " + to_string(len);
		///*	putText(img, Sarea, Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
		//	putText(img, Slen, Point(pX, pY - 20), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);*/
		//	circle(img, Point(pX, pY), 4, Scalar(20, 20, 200), 2, LINE_8);
		//}
		putText(img, "我", Point(pX, pY - 20), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
	}
	imshow("判断轮廓图像", img);
}

图像拟合

//返回二值化轮廓
vector<vector<Point>> Person::contourOne(Mat& img)
{
	Mat binary = this->myBinary(img);
	vector<vector<Point>> contours;
	vector<Vec4i> hierarchy;
	findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
	return contours;
}

//拟合
void Person::myFitEllipse(Mat& img)
{
	imshow("原图", img);
	vector<vector<Point>> contours = this->contourOne(img);
	for (int i = 0; i < contours.size(); i++)
	{
		 RotatedRect rotRect = fitEllipse(contours[i]);
		 Point center = rotRect.center;
		 string height = to_string(rotRect.size.height);
		 string width = to_string(rotRect.size.width);
		 string are = to_string(rotRect.size.area());
		 ellipse(img, rotRect, Scalar(255, 0, 10), 2, 8);
		 circle(img, center, 3, Scalar(0, 255, 0), 2, LINE_8);
	}
	imshow("处理图", img);
	
}
21 霍夫直线检测

对噪声很敏感, 需要降噪

r = x0*cosθ + y0 * sinθ ==>如果点坐标在同一条直线: r和θ是相同的

  1. API:

    1. HoughLines : 得出来的是极坐标空间参数 vector(p,θ) 直线

      霍夫直线检测出的来的结果是vec3F, 距离,角度,累加

      参数:

      1. 参数2: 输出 vector<vec3f>, 这个vec3f

        下标0: 代表R(距离)

        下标1: 代表θ

        下标2: 代表累加值

      2. 参数3: 步长

      3. 参数4: 角度: cv_pi / xx

      4. 参数5: 阈值, 有多少个点集中在一起 算直线


      //InputArray image:输入图像,必须是8位单通道图像。 
        //OutputArray lines:检测到的线条参数集合。 
        //double rho:	累加器的距离
        //double theta:累加器的角度。 
        //int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。 
        //double srn:默认值为0,用于在多尺度霍夫变换中作为参数rho的除数,rho=rho/srn。 
        //double stn:默认值为0,用于在多尺度霍夫变换中作为参数theta的除数,theta=theta/stn。
        //如果srn和stn同时为0,就表示HoughLines函数执行标准霍夫变换,否则就是执行多尺度霍夫变换
      

      代码:

      void Person::Houline(Mat& img)
      {
      	imshow("原图", img);
      	Mat binary = this->myBinary(img);
      	vector<Vec3f>lines;
      	HoughLines(binary, lines, 1, CV_PI / 180, 160, 0, 0);
      	Point p1; Point p2;
      	for (int i = 0; i < lines.size(); i++)
      	{
      		double step = lines[i][0];
      		double angle = lines[i][1];
      		double add = lines[i][2];
      		cout << "直线" << i << "\t" << "距离: " << step << "角度: " << angle << i << "累加点: " << add << endl;
      		double x = cos(angle);
      		double y = sin(angle);
      		double x0 = step * x;
      		double y0 = step * y;
      		p1.x = cvRound(x0 + 1000 * -y);
      		p1.y = cvRound(y0 + 1000 * x);
      		p2.x = cvRound(x0 - 1000 * -y);
      		p2.y = cvRound(y0 - 1000 * x);
      		line(img, p1, p2, Scalar(0, 0, 255), 2, 8);
      	}
      	imshow("霍夫直线", img);
      }
      
    2. HoughLinesP() 线段

      参数2: 为2个点的坐标的容器: vector<Vec4i>

      参数3, 4 步长, 角度

      参数5: 阈值, 有这么多点以上连接的线段

      参数6: 检测出的最小长度(minLineLength)

      参数7: 线段之间的间隔, 如果超出则为新的线段

    void Person::houlineP(Mat& img)
    {
    	Mat result = Mat::zeros(img.size(), img.type());
    	imshow("原图", img);
    	Mat binary = this->myBinary(img); //之前有封装一个返回二值化的函数的对象
    	vector<Vec4i> lines;
    	imshow("binary",binary);
    	HoughLinesP(binary, lines, 1, CV_PI / 180, 80,30,10);
    	//Point p1; Point p2;
    	for (int i = 0; i < lines.size(); i++)
    	{
    		/*p1.x = lines[i][0];
    		p1.y = lines[i][1];
    		p2.x = lines[i][2];
    		p2.y = lines[i][3];*/
    		//line(result, p1, p2, Scalar(0, 0, 255), 1, 8);
    		line(result, Point(lines[i][0], lines[i][1]), Point(lines[i][2], lines[i][3]), Scalar(255, 0, 0), 1, 8);
    	}
    	imshow("霍夫直线", result);
    }
    

21.1 霍夫圆检测

x = x0 + rcos(θ); y = y0+rsin(θ) 一直圆心(x0,y0)和r

基于梯度去寻找, 不然计算量太大了

接收的必须是灰色图像, 而且对噪声很敏感, 需要进行降噪

  1. 圆的参数方程:

    ​ 圆的参数(X0, Y0, r), 圆任意三个点, 以这些点位圆心 r为半径, 相交的一个点

    ​ 基于梯度或者边缘,轮廓进行查找

    1. X = X0 + r * cos(θ)
    2. Y = Y0 + r * sin(θ)
  2. api: HoughCircles

    1. 参数2: 输出的圆 vector<ve3f>
    2. 参数3: 方法(可以为霍夫梯度,HOUGH_GRADIENT)
    3. 参数4: 参数方程空间的整个数据的大小(dp, 在其他参数不变的情况下,参数越高, 越容易检测出圆)
    4. 参数5: 两个圆直径的最小距离 (防止得到同心圆)
    5. 参数6: canny边缘提取的高阈值
    6. 参数7: 累计值(阈值)
    7. 参数8,9: 最小半径, 最大半径
void Person::houghCir(Mat &img)
{
	imshow("原图", img);
	Mat result = Mat::zeros(img.size(), CV_8UC3);
	Mat gray;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	GaussianBlur(gray, gray, Size(15,15), 2, 2);
	vector<Vec3f> cir;
	int dp = 2;
	double cirSpace = 5;
	int add = 100;
	int maxThreshold = 100;
	double cirMin = 15; double cirMax = 100;
	HoughCircles(gray, cir, HOUGH_GRADIENT, dp, cirSpace, add, maxThreshold, cirMin, cirMax);
	for (int i = 0; i < cir.size(); i++)
	{
		int cenX = round(cir[i][0]);
		int cenY = round(cir[i][1]);
		int radius = round(cir[i][2]);
		circle(result, Point(cenX, cenY), radius, Scalar(0, 0, 200), 2, 8);
	}
	imshow("霍夫找园", result);
}
21.2 图像形态学

图像形态学操作

概念: 可以对灰度图像和二值化图像处理

1. 腐蚀与膨胀 支持彩色图像

原理:对处于卷积核像素的进行排序 腐蚀, 用最小像素来替换中心像素, 膨胀是用最大像素来替换中心像素

作用: 断开或者连接前景对象

getStructuringElement: 获取类型() 形态学

  • 类型: morph_rect
  • 大小: size(x,y)
  • 中心点: Point()
  1. API:

    腐蚀: erode

    膨胀: dilate

    参数: 第3个参数是结构元素(形态学)

void Person::erodeDilate(Mat& img)
{
	imshow("原图", img);
	Mat result; 
	Mat result1;
	Mat binary = this->myBinary(img);
	Mat rect = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	erode(img, result1, rect);
	imshow("腐蚀", result1);
	dilate(img, result, rect);
	imshow("膨胀", result);
}
2. 开闭操作

只对起作用的区域产生变化, 对其他没作用到的不会产生变化

  1. 开操作 = 腐蚀+膨胀, 删除小的干扰块

  2. 闭操作 = 膨胀+腐蚀, 填充闭合区域

void Person::erodeDilate(Mat& img)
{
	imshow("原图", img);
	Mat result; 
	Mat result1;
	Mat binary = this->myBinary(img);
	Mat rect = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	erode(img, result1, rect);
	imshow("腐蚀", result1);
	dilate(img, result, rect);
	imshow("膨胀", result);
}
  1. API: morphologyEx

    1. 参数3:morph_open

    2. 参数6: 连续操作(iterations):

      作用: 速度会高于直接提升kernel大小

3. 结构元素形状(morph形态学)
  • 线形 - 水平与垂直
  • 矩形 - w*h
  • 十字交叉形状
void Person::openCloss(Mat& img)
{
	Mat dst;
	imshow("原图", img);
	Mat binary = this->myBinary(img);
	imshow("二值化", binary);
	Mat kernel =  getStructuringElement(MORPH_RECT, Size(15, 1), Point(-1, -1));
	morphologyEx(binary, dst, MORPH_OPEN, kernel, Point(-1, -1), 1);
	imshow("腐蚀", dst);
}
4. 形态学梯度(轮廓,线) 支持彩色
  • 基本梯度: 膨胀减去腐蚀之后的结果
  • 内梯度: 原图减去腐蚀之后的结果
  • 外梯度: 膨胀减去原图的结果
6. 更多形态学操作
  1. 黑帽和顶帽

    • 顶帽: 是原图减去开操作之后的结果(morph_TOPHAT)
    • 黑帽: 是闭操作之后的结果减去原图(morph_BLACKHAT)
    • 顶帽与黑帽的作用是用来提取图像中微小有用信息块
  2. 击中击不中变换(morph_hitmiss)

    通过特点的元素去匹配 如果匹配成功则击中

    MORPH_CROSS: 十字交叉元素

21.3 形态学梯度
  • 基本梯度: 膨胀减去腐蚀之后的结果
  • 内梯度: 原图减去腐蚀之后的结果
  • 外梯度: 膨胀减去原图的结果
void Person::gradi(Mat& img)
{
	
	if (img.empty())
	{
		cout << "图片路径错误" << endl;
		return;
	}
	imshow("原图", img);
	Mat gray; Mat binary;
	cvtColor(img, gray, COLOR_BGR2GRAY);
	Mat Ksize = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
	Mat grad; Mat inside; Mat extral; Mat src; Mat src1;
	erode(gray, src, Ksize, Point(-1, -1));
	dilate(gray, src1, Ksize, Point(-1, -1));
	subtract(src1, src, grad);
	subtract(gray, src, inside);
	subtract(src1, gray, extral);
	imshow("形态学", grad);
	threshold(grad, grad, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
	imshow("基本形态学梯度", grad);
}
21.4更多形态学操作
1. 黑帽和顶帽(提取瑕疵)

API:morph_TOPHAT, morph_BLACKHAT

  • 顶帽: 是原图减去开操作之后的结果(morph_TOPHAT) 提取微小(被腐蚀)的像素块
  • 黑帽: 是闭操作之后的结果减去原图(morph_BLACKHAT) 找出被膨胀的像素块
  • 顶帽与黑帽的作用是用来提取图像中微小有用信息块
void Person::morph1(Mat& img)
{
	imshow("原图", img);
	Mat binary = this->myBinary(img);
	Mat dst;
	Mat rect = getStructuringElement(MORPH_ELLIPSE, Size(14, 14), Point(-1, -1));
	imshow("二值化", binary);
	//MORPH_TOPHAT
	morphologyEx(binary, dst, MORPH_BLACKHAT, rect);
	imshow("顶帽", dst);
}
2. 击中击不中变换(morph_hitmiss)

API:morph_hitmiss

通过特点的元素去匹配 如果匹配成功则击中/通过结构元素去寻找, 如果形状一致则被击中

MORPH_CROSS: 十字交叉元素

void Person::morph2(Mat& img)
{
	imshow("原图", img);
	Mat dst;
	Mat binary = this->myBinary(img);
	Mat rect = getStructuringElement(MORPH_CROSS, Size(12, 12), Point(-1, -1));
	morphologyEx(binary, dst, MORPH_HITMISS, rect);
	imshow("匹配模板", dst);
}
22: 视频读和写
1. api
  1. VideoCapture xx(0): 打开当前摄像头, 也可以打开usb摄像头

    1. 如果是(“xxx”): 代表读取视频流文件路径
    2. 如果是网址, 就直接从网址上去读取
  2. capture.isOpened: 确认是否打开摄像头, 为1=打开

  3. namedWindow默认打开的摄像头是640*480

  4. capture.read(mat) || capture >> mat: 读取摄像头

  5. capture内置属性

    1. capture.get(CAP_PROP_FPS): 帧率
    2. CAP_PROP_FRAME_WIDTH: 宽度
    3. CAP_PROP_FRAME_HEIGHT: 高度
    4. CAP_PROP_FRAME_COUNT: 总共多少帧
    5. CAP_PROP_FRAME_FOURCC: 视频类型
  6. VideoWirter: 保存视频/写入

    1. 保存路径
    2. 参数2: 视频类型
    3. 参数3: 帧率
    4. 参数4: 宽高
    5. 参数5: 是否是彩色的

    一个文件保存视频流最大是2g

注意点: 如果不是c++语言要记得销毁: 读取.release(), 写入.release(), 如果没有可能会造成视频保存打不开, 保存摄像头一般设置为25.否则打不开

void Person::cvtVideos(string src)
{
	VideoCapture capture(src);
	if (!capture.isOpened())
	{
		cout << "视频路径错误" << endl;
		return;
	}
	Mat img;
	while (true)
	{
		Mat hsv; Mat mask; Mat result;
		bool b = capture.read(img);
		if (!b) break;
		imshow("加载视频", img);
		cvtColor(img, hsv, COLOR_BGR2HSV);
		inRange(hsv, Scalar(35, 43, 46), Scalar(77, 255, 255), hsv);
		imshow("hsv", hsv);
		bitwise_not(hsv, mask);
		imshow("mask", mask);

		bitwise_and(img, img, result,mask);
		imshow("roi", result);
		int key = waitKey(25);
		if (key == 27)
		{
			break;
		}
	}
	capture.release();
}
23: 图像色彩空间转换
  1. 色彩空间分布

    绝大数的普遍的

    1. RGB色彩空间

    特点设备的

    • HSV色彩空间

      对各种颜色分辨很清楚

    • Lab色彩空间

      主要的颜色在L和B上面 只有2个通道

    • YCbCr色彩空间

      对皮肤能更好的显示

    inrang: rgb转换lab设置的参数,

    h:0~180, s:0~255

    void Person::cvtVideos(string src)
    {
    	VideoCapture capture(src);
    	if (!capture.isOpened())
    	{
    		cout << "视频路径错误" << endl;
    		return;
    	}
    	Mat img;
    	while (true)
    	{
    		Mat hsv; Mat mask; Mat result;
    		bool b = capture.read(img);
    		if (!b) break;
    		imshow("加载视频", img);
    		cvtColor(img, hsv, COLOR_BGR2HSV);
    		inRange(hsv, Scalar(35, 43, 46), Scalar(77, 255, 255), hsv);
    		imshow("hsv", hsv);
    		bitwise_not(hsv, mask);
    		imshow("mask", mask);
    
    		bitwise_and(img, img, result,mask);
    		imshow("roi", result);
    		int key = waitKey(25);
    		if (key == 27)
    		{
    			break;
    		}
    	}
    	capture.release();
    }
    
24: 直方图反向投影
  1. 注意点: 模型和样品的bins必须完全一直

  2. API: calcBackProject : 反向投影

    会受到2个因素影响, 直方图的bins会影响到, bins越大, 匹配越细微, bins一般设置为48左右

    void Person::calcBack(Mat sample, Mat target)
    {
    	if (sample.empty() || target.empty())
    	{
    		cout << "img路径错误" << endl;
    		return;
    	}
    	imshow("样本", sample); imshow("模板", target);
    	Mat hsvSample; Mat hsvTarget;
    	cvtColor(sample, hsvSample, COLOR_BGR2HSV);
    	cvtColor(target, hsvTarget, COLOR_BGR2HSV);
    	int channles[] = { 0,1 };
    	Mat hist;
    	int hBins = 48; int sBins = 48;
    	int bins[] = { hBins,sBins };
    	float hRangs[] = { 0,180 };
    	float sRangs[] = { 0,255 };
    	const float* rangs[] = { hRangs,sRangs };
    	calcHist(&hsvSample, 1, channles, Mat(), hist, 2, bins, rangs, true, false);
    	Mat norm;
    	normalize(hist, norm, 0, 255,NORM_MINMAX,-1,Mat());
    	Mat dst;
    	calcBackProject(&hsvTarget, 1, channles, norm, dst, rangs, 1.0, true);
    	imshow("直方图反向投影", dst);
    }
    
  3. 色彩空间分布

    绝大数的普遍的

    1. RGB色彩空间

    特点设备的

    • HSV色彩空间

      对各种颜色分辨很清楚

    • Lab色彩空间

      主要的颜色在L和B上面 只有2个通道

    • YCbCr色彩空间

      对皮肤能更好的显示

    inrang: rgb转换lab设置的参数,

    h:0~180, s:0~255

    void Person::cvtVideos(string src)
    {
    	VideoCapture capture(src);
    	if (!capture.isOpened())
    	{
    		cout << "视频路径错误" << endl;
    		return;
    	}
    	Mat img;
    	while (true)
    	{
    		Mat hsv; Mat mask; Mat result;
    		bool b = capture.read(img);
    		if (!b) break;
    		imshow("加载视频", img);
    		cvtColor(img, hsv, COLOR_BGR2HSV);
    		inRange(hsv, Scalar(35, 43, 46), Scalar(77, 255, 255), hsv);
    		imshow("hsv", hsv);
    		bitwise_not(hsv, mask);
    		imshow("mask", mask);
    
    		bitwise_and(img, img, result,mask);
    		imshow("roi", result);
    		int key = waitKey(25);
    		if (key == 27)
    		{
    			break;
    		}
    	}
    	capture.release();
    }
    
24: 直方图反向投影
  1. 注意点: 模型和样品的bins必须完全一直

  2. API: calcBackProject : 反向投影

    会受到2个因素影响, 直方图的bins会影响到, bins越大, 匹配越细微, bins一般设置为48左右

    void Person::calcBack(Mat sample, Mat target)
    {
    	if (sample.empty() || target.empty())
    	{
    		cout << "img路径错误" << endl;
    		return;
    	}
    	imshow("样本", sample); imshow("模板", target);
    	Mat hsvSample; Mat hsvTarget;
    	cvtColor(sample, hsvSample, COLOR_BGR2HSV);
    	cvtColor(target, hsvTarget, COLOR_BGR2HSV);
    	int channles[] = { 0,1 };
    	Mat hist;
    	int hBins = 48; int sBins = 48;
    	int bins[] = { hBins,sBins };
    	float hRangs[] = { 0,180 };
    	float sRangs[] = { 0,255 };
    	const float* rangs[] = { hRangs,sRangs };
    	calcHist(&hsvSample, 1, channles, Mat(), hist, 2, bins, rangs, true, false);
    	Mat norm;
    	normalize(hist, norm, 0, 255,NORM_MINMAX,-1,Mat());
    	Mat dst;
    	calcBackProject(&hsvTarget, 1, channles, norm, dst, rangs, 1.0, true);
    	imshow("直方图反向投影", dst);
    }
    
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值