机器图像处理技术

文章目录

  • OpenCV介绍
    • Mat类
      • 创建
      • 复制
      • 遍历
  • 基本概念
    • 物理设备
    • 相机的光学模型
    • 镜头畸变
    • 图像的采样和量化
    • 图像的分辨率
    • 图像的灰度级
    • 图像的坐标
    • 像素的空间关系
    • 图像的种类
    • 色彩模型
      • CMYK 色彩模型
      • HSV 色彩模型
      • HSV 与 RGB的转换
    • 图像的直方图
    • 直方图的作用
      • 图像匹配
      • 判断成像质量
      • 二值化阈值
    • 习题1
    • 习题2
    • 习题3
    • 常用操作
  • 二值化
    • 连通域标记
    • 图像二值化的目的
    • P-tile法
    • 最小误判概率法
    • 大津法(OTSU)法
    • 局部自适应二值化
    • 习题1
    • 习题2
    • 习题3
    • 习题4
  • 图像形态学
    • 绪论
    • 膨胀运算
    • 腐蚀运算
    • 开运算
    • 闭运算
    • 顶帽和底帽变换
    • 灰度图的形态学处理
    • 习题1
    • 习题2
    • 习题3
  • 空间滤波
    • 图像卷积
    • 均值滤波
    • 中值滤波
    • 图像的边缘检测
      • Prewitt算子
      • Sobel算子
      • Laplacian算子
      • canny算子
    • 习题1
    • 习题2
    • 习题3
    • 习题4
  • 图像变换
    • 几何变换
    • 平层变换
    • 比例缩放
    • 旋转和镜像
    • 对称变换
    • 错切变换
    • 复合变换
    • 仿射变换和投影变换
    • 插值算法
      • 最近邻插值法
      • 双线性插值法
      • 双三次插值法
    • 练习
  • 图像特征
    • 概论
      • HSV直方图
    • 形状特征
      • Shape Context
    • 霍夫变换
    • 局部特征
      • 角点检测

OpenCV介绍

OpenCV主页:https://opencv.org/

Mat类

matrix类本身是一个矩阵格式,可以用来保存图像

int main()
{
	cout << "hello opencv!" << endl;
	Mat srcImage = imread("test.bmp");
	imshow("srcImage", srcImage); //窗口名称,图片的名称
	waitKey(0);
	return 0;
}

创建

使用Mat()构造函数

	Mat M1(2,2,CV_8UC3,Scalar(0,0,255));
	Mat M2(2,2,CV_8UC3,1);  //只对第一个通道进行赋值

创建了一个名为M1的Mat,尺寸为2,2,类型为8位uchar类型,通道数为3(这个mat的每一个元素包含三个通道(数值)),然后用0,0,255为每一个元素赋值

CV_(位数) + (数据类型) +(通道数)

使用create()函数

Mat M3;
M3.create(3,4,CV_8UC3);

声明了一个3行4列的mat对象

复制

浅复制

Mat srcM(2,2,CV_8UC3,Scalar(0,0,255))
Mat dstM;
dstM = srcM;

只是新生成一个矩阵头,dstMdata依然指向矩阵srcMdata

深复制

Mat srcM(2,2,CV_8UC3,Scalar(0,0,255))
Mat dstM;
srcM.copyTo(dstM);

dstM是一个全新的矩阵,内存中的位置和srcM是不一样的

浅复制,srcM修改后dstM的值也会随着变化,深复制不会

遍历

//方法1
int height = image.rows;
int width = image.cols * image.channels();	//列数乘以它的通道数
for(int j = 0;j < height;j++)
{
	uchar* data = image.ptr(j);
	for(int i = 0;i < width;i++)
	{
		data[i] = data[i/2];
	}
}
//方法2
for (int j = 0; j < M7.rows; j++)
{
	for (int i = 0; i < M7.rows; i++)
	{
		M7.at(j, i)[0] = j;	//取到(j,i)位置上的像素,然后对他进行赋值
		M7.at(j, i)[1] = j+1;
		M7.at(j, i)[2] = j+2;
	}
}

基本概念

物理设备

一个数字图像处理系统包括:

图像输入设备(输入|采样量化|专用处理),电脑(数字图像处理),图像输出设备(专用处理|D/A转换|输出)

最常见的输入设备就是相机(CCD/CMOS)

CCD(电荷耦合器件)是一种半导体器件,能够把光学影像转化为数字信号,CCD上植入的微小光敏物质称作像素,包含的像素越多,画面分辨率也就越高

线阵相机|面阵相机

面阵相机一次拍摄一个区域,视觉检测中绝大部分应用面阵相机

线阵相机一次拍摄一行像素,通过移动以及拼接来获取图像,分辨率高,价格较为昂贵

传感器得到数据后,利用插值算法生成实际的图像

相机的光学模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1IdmJPF9-1645958195344)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216143214276.png)]

镜头畸变

由于镜头中间部分的放大率和周围的放大率不同而产生的,焦距越短失真程度会越大

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mAqRTXTt-1645958195345)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216143452275.png)]

图像的采样和量化

数字化坐标值称为采样,数字化幅度值称为量化

图像的分辨率

分辨率 160 * 128 的意思是水平像素数为160个,垂直像素数为128个,像素的数目越多,感应到的图像就越精密

视频图像一般采用 smpte 制定的格式标准,如 1080p,表示垂直方向有 1080条水平扫描线(p代表隔行扫描)

图像的灰度级

最常见的图像为 8位图像,灰度级为 256位

灰度级越多,可以展现的图像细节就越多

图像的坐标

图像原点为图像的左上角,坐标记为 [0,0]

一副 M * N 的图像可以用一个矩阵来表示
KaTeX parse error: Undefined control sequence: \matrix at position 9: \left[ \̲m̲a̲t̲r̲i̲x̲{ f(0,0) & ... …

像素的空间关系

8 - 邻接 | 4 - 邻接

8 - 邻接关系中,像素 [x,y] 的 8 - 邻域像素,被认为是和该像素相邻的像素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3vWpZ7qU-1645958195346)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216150609444.png)]

4 - 邻接关系中,像素 [x,y] 的 4 - 邻域像素,被认为是和该像素相邻的像素

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nziGT9sI-1645958195348)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216150702168.png)]

图像的种类

1)二值图像:像素取值仅为 0 和 1,“0” 代表黑色,"1"代表白色;通常用来表示状态,如区分图像中的前景和背景

2)灰度图像:像素取值范围为 [0,255],“0” 代表纯黑,“255” 代表纯白

3)彩色图像:RGB格式,采用R(红色)G(绿色)B(蓝色)三种颜色的组合叠加来获得各种颜色

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJItcF1z-1645958195350)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216151122787.png)]

除了 RGB 外,常用的色彩模型还有 HSV/HSI(数字图像算法常用),CMYK(常用于印刷),YUV(用于图像传输)

色彩模型

CMYK 色彩模型

印刷业通过青©,品(M),黄(Y) 三原色油墨的不同网点面积率的叠印来表现颜色,一般采用青©,品(M),黄(Y),黑(BK) 四色印刷

CMYK 可以看做是从黑色中减少颜色得到新的颜色,故可称为 减色模型

RGB 是在白色上叠加颜色得到新的颜色,故称为 加色模型

HSV 色彩模型

色相(Hue),饱和度(Saturation),明度(Value),常用于图像算法中的色彩分析,对光照具有较强的鲁棒性

H:用角度表示,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°;该值表示颜色接近于哪种纯色值

S:通常取值范围为 0%~100%,圆锥的中心为0。该值越大表示颜色越饱满,直观的说即颜色深而艳

V:亮度,表示颜色的明亮程度

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BX6hs3yY-1645958195351)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216152216655.png)]

HSV 与 RGB的转换

R G B − > H S V RGB -> HSV RGB>HSV

h = { 0 ° , m a x = m i n 60 ° × g − b m a x − m i n + 0 ° , m a x = r a n d g ≥ b 60 ° × g − b m a x − m i n + 360 ° , m a x = r a n d g < b 60 ° × b − r m a x − m i n + 120 ° , m a x = g 60 ° × r − g m a x − m i n + 240 ° , m a x = b h=\begin{cases} 0°,max=min\\ 60°\times\frac{g-b}{max-min}+0°,max=r & and & g\geq b\\60°\times\frac{g-b}{max-min}+360°,max=r&and&gh=0°,max=min60°×maxmingb+0°max=r60°×maxmingb+360°max=r60°×maxminbr+120°max=g60°×maxminrg+240°max=bandandgbg<b

s = { 0 m a x = 0 m a x − m i n m a x = 1 − m i n m a x o t h e r w i s e s=\begin{cases}0&max=0\\ \frac{max-min}{max} = 1-\frac{min}{max}&otherwise\end{cases} s={0maxmaxmin=1maxminmax=0otherwise

v = m a x v=max v=max


H S V − > R G B HSV->RGB HSV>RGB

h i = ⌊ h 60 ⌋ ( m o d 6 ) h_i=\left\lfloor\frac{h}{60}\right\rfloor(mod\quad6) hi=60h(mod6)

f = h 60 − h i f=\frac{h}{60}-h_i f=60hhi

p = v × ( 1 − s ) p=v\times(1-s) p=v×(1s)

q = v × ( 1 − f × s ) q=v\times(1-f\times s) q=v×(1f×s)

t = v × ( 1 − ( 1 − f ) × s ) t=v\times(1-(1-f)\times s) t=v×(1(1f)×s)

对 于 每 个 颜 色 向 量 ( r , g , b ) 对于每个颜色向量(r,g,b) r,g,b

( r , g , b ) = { ( v , t , p ) , h i = 0 ( q , v , p ) , h i = 1 ( p , v , t ) , h i = 2 ( p , q , v ) , h i = 3 ( t , p , v ) , h i = 4 ( v , p , q ) , h i = 5 (r,g,b)=\begin{cases}(v,t,p),h_i=0\\(q,v,p), h_i=1\\(p,v,t),h_i=2\\(p,q,v),h_i=3\\(t,p,v),h_i=4\\(v,p,q),h_i=5\end{cases} (r,g,b)=(v,t,p),hi=0(q,v,p),hi=1(p,v,t),hi=2(p,q,v),hi=3(t,p,v),hi=4(v,p,q),hi=5

图像的直方图

是图像处理中的一个非常重要的工具,被广泛应用

本质是概率分布的图形化,同时直方图也可以用来表示向量

假设有如下的图像

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0kphbNDu-1645958195353)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216155512779.png)]

我们记录每个像素出现的次数
v 0 = 5 v 1 = 12 v 2 = 18 v 3 = 8 v 4 = 1 v 5 = 5 v 6 = 8 v 7 = 5 v_0=5\quad v_1=12\quad v_2=18\quad v_3=8\quad v_4=1\quad v_5=5\quad v_6=8\quad v_7=5 v0=5v1=12v2=18v3=8v4=1v5=5v6=8v7=5
得到分布图

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-goITUEHC-1645958195354)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216155808261.png)]

统计总的像素个数,把每个像素出现的次数除以这个数,这个操作叫做归一化,即可得到直方图图像

因为这是一个 8*8的图,所以我们 让 次数/64

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6bTM758K-1645958195356)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216160033356.png)]

纵坐标是出现的概率,横坐标是灰度值

两个直方图的比较可以使用 欧几里得距离马氏距离等,OpenCV的compareHist函数提供了4中方法:Correlation,Chi-Square,Intersection,Bhattacharyya距离

直方图的作用

图像匹配

比较两幅图像的直方图,可以得到两幅图像的相似程度,其本质是对比灰度出现的概率是否相似

同样的图像直方图应该相同,反之不然

判断成像质量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NUSIq0yg-1645958195358)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216160657746.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4KeI4Ru7-1645958195360)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216160705751.png)]

二值化阈值

二值化即通过设置一个门限值,把灰度图像转换为二值化图像,通常的目的是分离前景和背景

像素大于这个阈值,就设置为1,反之设置为0;

习题1

使用imread(“文件名”),读取一张彩色图片,然后将每个像素的r,g,b值同时改为(r+g+b)/3

Mat m1;
m1 = imread("test.bmp");
int height = m1.rows;	//行数
int width = m1.cols;	//每行元素数量
for (int i = 0; i < height; i++)
{
	for (int j = 0; j < width; j++)
	{
		uchar average = (m1.at(i, j)[0] + m1.at(i, j)[1] + m1.at(i, j)[2]) / 3;
		m1.at(i, j)[0] = average;
		m1.at(i, j)[1] = average;
		m1.at(i, j)[2] = average;
	}
}
imshow("src", m1);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HHP5OzxO-1645958195362)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216175546539.png)]

习题2

使用imread(“文件名”,0)读取一张彩色图片,实现把彩色图片转换成灰度图

Mat m1;
m1 = imread("test.bmp",0);
imshow("src", m1);

习题3

在习题1的基础上,增加一个条件, uchar threshold = 100;如果average > thershold ,则average = 255,否则 = 0

Mat m1;
m1 = imread("test.bmp");
int height = m1.rows;	//行数
int width = m1.cols;	//每行元素数量
uchar theshold = 100;
for (int i = 0; i < height; i++)
{
	for (int j = 0; j < width; j++)
	{
		uchar average = (m1.at(i, j)[0] + m1.at(i, j)[1] + m1.at(i, j)[2]) / 3;
		if (average > theshold)
		{
			average = 255;
		}
		else {
			average = 0;
		}
		m1.at(i, j)[0] = average;
		m1.at(i, j)[1] = average;
		m1.at(i, j)[2] = average;
	}
}
imshow("src", m1);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rb5RKUXB-1645958195364)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220216174201656.png)]

常用操作

#include 
#include 
#include 
using namespace std;
using namespace cv;

int readImage()
{

	cout << "-----------start to read Image-------------" << endl;

	//读入单张图片,路径可替换成自己的图片的路径
	Mat srcMat = imread("test.bmp");

	//读取图片的一些信息
	// Mat是否为空,可以判断读图是否成功
        cout << "empty:" << (srcMat.empty() ? "the Mat is empty,fail to read" : "not empty") << endl;
	if (srcMat.empty())return -1;

	//在Mat中
	//cols 是 列数 相当于 width  
	//rows 是 行数 相当于 height 
	//行数
	cout << "rows:" << srcMat.rows << endl;
	//列数 
	cout << "cols:" << srcMat.cols << endl;
	//维度,普通图片为2维
	cout << "dims:" << srcMat.dims << endl;

	// Size是OpenCV内部定义的数据类型
	cout << "size[]:" << srcMat.size().width << "," << srcMat.size().height << endl;

	// 深度id
	// 用来度量每一个像素中【每一个通道】的精度,depth数值越大,精度越高。在                 
	//Opencv中,Mat.depth()得到的是一个0~6的数字,分别代表不同的位数,对应关系如下:                            
	//opencv中,由于使用Mat.at访问数据时,必须正确填写相应的数据类型,
	//Mat的类型定义方法
	//The definition is as follows:
	//CV_(位) + (数据类型) + (通道数量)
	//如, CV_32FC1 表示 32位 float型单通道,
	//OpenCV中的数据类型与C的数据类型的对应关系。
	/*
	uchar   CV_8U		0 
	char    CV_8S		1
	ushort  CV_16U		2
	short   CV_16S		3	
	int     CV_32S		4
	float   CV_32F		5
	double  CV_64F		6
	*/
	cout << "depth (ID):" << srcMat.depth() << endl;

	// channel数,如灰度图为单通道,RGB图为3通道
	cout << "channels:" << srcMat.channels() << endl;

	// Mat中一个元素的size(byte数),矩阵一个元素占用的字节数,
	//数据类型是
	//CV_8UC1,				elemSize==1,1 byte;
	//CV_8UC3/CV_8SC3,		elemSize==3;3 byte
	//CV_16UC3/CV_16SC3,	elemSize==6;6 byte
	//即elemSize==字节数x通道数;矩阵一个元素占用的字节数,
	cout << "elemSize:" << srcMat.elemSize() << "[byte]" << endl;
	// Mat中一个元素的一个通道的size(byte数),矩阵元素一个通道占用的字节数,
	//eleSize1==elemSize/channels;
	cout << "elemSize1 (elemSize/channels):" << srcMat.elemSize1() << "[byte]" << endl;

	//元素的总数,如果是图像,即为像素个数
	cout << "total:" << srcMat.total() << endl;
	// step (byte数)  
	//Mat矩阵中每一行的“步长”,以字节为基本单位,每一行中所有元素的字节总量
	//cols*elemSize=cols*eleSize1*channels
	cout << "step:" << srcMat.step << "[byte]" << endl;
	// 一个step的channel总数,每行的channel数
	cout << "step1 (step/elemSize1):" << srcMat.step1() << endl;
	// 该Mat在内存上是否连续
	cout << "isContinuous:" << (srcMat.isContinuous() ? "true" : "false") << endl;
	// 是否为子矩阵
	cout << "isSubmatrix:" << (srcMat.isSubmatrix() ? "true" : "false") << endl;


	//读入单张图片,加参数0,表示读入,并转换成灰度图
	Mat gryMat = imread("test,bmp", 0);
	if (srcMat.empty())return -1;
	//保存图片
	imwrite("test.bmp",gryMat);

	//显示图片
	imshow("src", srcMat);
	imshow("gray", gryMat);

	//显示图片,必须要加waitKey(),否则无法显示图像
	//waitKey(0),无限地显示窗口,直到任何按键按下
	//如果是其他数字,如waitKey(25)表示25毫秒,然后关闭。
	waitKey(0);

	//关闭所有窗口
	destroyAllWindows();

	return 0;
}

//通过OpenCV读取视频
int readVideo()
{
	cout << "-----------start to read Video-------------" << endl;

	//读取本地视频,OpenCV可以读取本地视频文件,摄像头,及连续的图像文件
	//VideCapture为opencv定义的视频数据的类,实际是底层对ffmpeg的封装实现的

	//----------------------读取视频文件--------------------------
	//实例化VideoCapture类,名为cap,并打开()中的视频
	//也可以通过 capVideo.open("../testImages\\vtest.avi"); 打开
	//如果 capVideo.open(0)则打开默认摄像头,参数0为摄像头的id
	VideoCapture capVideo("../testImages\\vtest.avi");

	//如果视频打开失败
	if (!capVideo.isOpened()) {
		cout << "Unable to open video!" << endl;
		return -1;
	}

	//读取视频的一些属性,更多参数可参考videoio_c.h中得定义
	cout << "parameters" << endl;
	cout << "width:" << capVideo.get(CAP_PROP_FRAME_WIDTH) << endl;
	cout << "heigth:" << capVideo.get(CAP_PROP_FRAME_HEIGHT) << endl;
	cout << "frames:" << capVideo.get(CAP_PROP_FRAME_COUNT) << endl;
	cout << "fps:" << capVideo.get(CAP_PROP_FPS) << endl;

	//保存文件初始化,VideoWriter为OpenCV中定义的视频保存类
	VideoWriter writer;
	//选择编码方式
	int codec = VideoWriter::fourcc('M', 'J', 'P', 'G');  
	// 输出的视频地址及名字
	string filename = "test_1.avi";             
	//定义帧率
	double fps = capVideo.get(CV_CAP_PROP_FPS);                      
	//保存的视频的尺寸,此处尺寸缩小一半
	Size vSize;
	vSize.width = capVideo.get(CAP_PROP_FRAME_WIDTH) / 2;
	vSize.height = capVideo.get(CAP_PROP_FRAME_HEIGHT) / 2;

	//打开视频流
	writer.open(filename, codec, fps, vSize);

	Mat frame;
	Mat resizeFrame;
	Mat grayFrame;

	while (1) {
		//视频流中读取图像
		capVideo >> frame;

		if (frame.empty()) {
			cout << "Unable to read frame!" << endl;
			destroyAllWindows();
			return -1;
		}

		//保存到视频流,由于视频文件尺寸降为1/2,frame尺寸也要减半
		resize(frame,resizeFrame,vSize);
		writer.write(resizeFrame);

		//可以接各种处理
		cvtColor(frame, grayFrame, CV_RGB2GRAY);

		imshow("frame",frame);
		imshow("resizeFrame",resizeFrame);
		imshow("gray",grayFrame);

		//显示图片,延时30ms,必须要加waitKey(),否则无法显示图像
		//等待键盘相应,按下ESC键退出
		if (waitKey(30) == 27){
			destroyAllWindows();
			break;
		}
	}

	destroyAllWindows();
	return 0;
}

//读取连续图片
int readSequence()
{
	//(eg. `img_%02d.jpg`, which will read samples like `img_00.jpg, img_01.jpg, img_02.jpg, ...`)
	VideoCapture capSequence("../testImages\\sequence\\left%02d.jpg");

	if (!capSequence.isOpened())
	{
		cerr << "Unable to open the image sequence!\n" << endl;
		return 1;
	}

	Mat frame;
	while (1) {
		//视频流中读取图像
		capSequence >> frame;

		if (frame.empty()) {
			cout << "Unable to read frame!" << endl;
			destroyAllWindows();
			return -1;
		}

		imshow("frame", frame);

		waitKey(200);
		
	}

	return 0;

}



//Mat类的创建方法,及初始化示例
int createMat()
{
	//---创建Mat---
	//cols 是 列数 相当于 width  
	//rows 是 行数 相当于 height 
	int cols = 4;
	int rows = 3;
	int type = CV_32S;
	int dataArray[] = { 0,  1,  2,  3,
						10, 11, 12, 13,
						10, 11, 12, 13 };

	Mat mat1_0;	//实例化,此操作并不在内存上开辟空间
	Mat mat2;	//实例化,此操作并不在内存上开辟空间
	Mat mat3;	//实例化,此操作并不在内存上开辟空间


	//几种方法,进行初始化定义尺寸和类型,并开辟空间
	mat1_0.create(rows, cols, type);
	mat2.create(Size(cols, rows), type);
	mat3.create(mat1_0.size(), mat1_0.type());

	//通过指针对mat1初始化
	Mat mat1_1(rows, cols, CV_32S, &dataArray);

	//如果mat1的保存空间连续,则拷贝数组的数据给mat1
	//Mat的数据实际保存在成员数组 data 里面
	if (mat1_0.isContinuous()) {
		memcpy(mat1_0.data, dataArray, sizeof(int)*cols*rows);
        //memcpy(void *dest, void *src, unsigned int count);
        //由src所指内存区域复制count个字节到dest所指内存区域。
	}

	//生成随机数
	//均一分布的随机数,[0,256)
	randu(mat2, Scalar(0), Scalar(256));
	// 正太分布的随机数,mean=128, stddev=10
	randn(mat3, Scalar(128), Scalar(10));

	cout << "m1_0:" << endl << mat1_0 << endl << endl;
	cout << "m1_1:" << endl << mat1_1 << endl << endl;
	cout << "m2:" << endl << mat2 << endl << endl;
	cout << "m3:" << endl << mat3 << endl << endl;

	//---创建Mat---
	// 创建数据类型为64F, channels=10, 3x3 的2维矩阵
	Mat mat4(3, 3, CV_64FC(10));
	//也可以通过CV_MAKETYPE()获得赋值的参数,本例中CV_MAKETYPE(CV_64F, 10)==78
	Mat mat5(3, 3, CV_MAKETYPE(CV_64F, 10));

	//创建channels=2,int型,2x2矩阵,并赋值,其他数据类型可查matx.hpp中的定义
	Mat mat6 = (Mat_(2, 2) << Vec2i(1, 1), Vec2i(2, 4), Vec2i(3, 9), Vec2i(4, 16));

	cout << "m6:" << endl << mat6 << endl << endl;

	// 5×4矩阵, 5行×4列,元素均为1
	Mat mat7 = Mat::ones(5, 4, CV_8U);
	// 5×4矩阵, 5行×4列,元素均为3
	Mat mat8 = Mat::ones(5, 4, CV_8U) * 3;
	// 5×4矩阵, 5行×4列,元素均为0
	Mat mat9 = Mat::zeros(5,4, CV_8U);
	// 3×3矩阵, 3行×3列,单位矩阵
	Mat mat10 = Mat::eye(3, 3, CV_8U);

	cout << "m7:" << endl << mat7 << endl << endl;
	cout << "m8:" << endl << mat8 << endl << endl;
	cout << "m9:" << endl << mat9 << endl << endl;
	cout << "m10:" << endl << mat10 << :endl << endl;


	return 0;

}

//Mat的复制方法
//Mat的复制,有深复制及浅复制的分别
int copyMat()
{
	//生成一个3×3的Mat
	Mat m1 = (Mat_(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);

	//浅复制,实质只是把m1的内存地址赋值给m_shallow
	//两个Mat在内存中是同一块数据
	Mat m_shallow = m1;

	//深复制,clone和copyTo,为m_deep1及m_deep2在内存中开辟空间,并且复制内容
	Mat m_deep1 = m1.clone();
	Mat m_deep2;
	m1.copyTo(m_deep2);

	cout << "m1=" << m1 << endl << endl;
	cout << "m_shallow=" << m_shallow << endl << endl;
	cout << "m_deep1=" << m_deep1 << endl << endl;
	cout << "m_deep2=" << m_deep2 << endl << endl;

	// 修改m1的(0,0)位置的数值,注意观察修改以后,其他几个Mat的内容
	m1.at(0, 0) = 100;

	cout << "m1=" << m1 << endl << endl;
	cout << "m_shallow=" << m_shallow << endl << endl;
	cout << "m_deep1=" << m_deep1 << endl << endl;
	cout << "m_deep2=" << m_deep2 << endl << endl;


	//定义ROI并复制
	//ROI(region of interest)感兴趣区域,即需要被处理的区域
	//Rect是opencv中定义的矩形数据类型
	//读入单张图片,路径可替换成自己的图片的路径
	Mat srcMat = imread("test.bmp");
	Mat roiMat;
	Rect roi;
	roi.x = 0;
	roi.y = 0;
	roi.width = srcMat.cols / 2;
	roi.height = srcMat.rows / 2;

	//定义mask并复制
	//mask即遮罩,用来屏蔽掉图像中的部分区域
	//mask的格式为uchar格式的mat,黑色部分表示需要屏蔽的,白色表示不需要遮蔽
	//生成mask
	Mat mask= zeros(srcMat.size(), CV_8U);
	rectangle(mask,roi,Scalar(255),-1);
    //绘制一个矩形框,Scalar:颜色;-1:线条宽度
    //Rect(x,y,width,height),x, y为左上角坐标,width, height则为长和宽。画出图像中的矩形

	Mat maskedMat;

	//复制ROI区域
	srcMat(roi).copyTo(roiMat);

	//带mask复制
	srcMat.copyTo(maskedMat, mask);

	imshow("src",srcMat);
	imshow("mask",mask);
	imshow("masked image",maskedMat);
	imshow("roi",roiMat);

	waitKey(0);

	return 0;
}


//利用Mat进行一些基本运算
int calcMat()
{
	//创建Mat
	Mat m1 = (Mat_(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
	cout << "m1=" << m1 << endl << endl;

	//基本四则运算
	Mat m2 = m1 + 3;
	Mat m3 = m1 * 3; 
	Mat m4 = m1 / 3;

	cout << "m2=" << m2 << endl << endl;
	cout << "m3=" << m3 << endl << endl;
	cout << "m4=" << m4 << endl <(4, 1) << 0, 1, 4, 1);
	Mat y = (Mat_(4, 1) << 1, 1, 3, 1.7320504);
	Mat magnitude, angle;
	cartToPolar(x, y, magnitude, angle, true); 

	for (int i = 0; i<4; ++i) {
		cout << "(" << x.at(i) << ", " << y.at(i) << ") ";
		cout << "mag=" << magnitude.at(i) << ", angle=" << angle.at(i) << "[deg]" << endl;
	}

	cout << endl;
	// 通过大小和角度,计算2维坐标
	cout << "calc Cartesian" << endl;
	Mat mag2 = (Mat_(4, 1) << 1, 1.41421, 5, 2);
	Mat ang2 = (Mat_(4, 1) << 90, 45, 36.8699, 60);

	Mat x2, y2;
	polarToCart(mag2, ang2, x2, y2, true); // in degrees

	for (int i = 0; i<4; ++i) {
		cout << "(" << x2.at(i) << ", " << y2.at(i) << ") ";
		cout << "mag=" << mag2.at(i) << ", angle=" << ang2.at(i) << "[deg]" << endl;
	}

	cout << endl;

	return 0;
}

//求解线性方程
int solveLinearEquations()
{
	//独立方程数和未知数相同时
	//  x +  y +  z = 6
	// 3x + 2y - 2z = 1
	// 2x - y  + 3z = 9
	//左边 
	Mat lhand = (Mat_(3, 3) << 1, 1, 1, 3, 2, -2, 2, -1, 3);
	//右边 
	Mat rhand = (Mat_(3, 1) << 6, 1, 9);

	//高斯消去法求解 
	Mat ans;
	solve(lhand, rhand, ans);

	cout << "Gaussian elimination" << endl;
	cout << "(x,y,z) = " << ans << endl << endl;
	//独立方程数 多于 未知数数量时
	//通过最小二乘法求解
	//  x +  y = 3  
	// 3x + 4y = 8 
	// -x - 2y = 2 
	cout << "the least square method" << endl;
	Mat lhand2 = (Mat_(3, 2) << 1, 1, 3, 4, -1, -2);
	Mat rhand2 = (Mat_(3, 1) << 3, 8, 2);

	Mat x;
	//通过SVD求解最小二乘法
	//方程组左侧,方程组右侧,输出,求解方法
	solve(lhand2, rhand2, x, DECOMP_SVD);

	cout << "(x,y) = " << x << endl;
	cout << "norm(lhand2*x-rhand2)=" << norm(lhand2*x - rhand2) << endl << endl;

	return 0;
}

二值化

连通域标记

根据(四邻域|八邻域)进行连通域标记

把挨在一起的像素,赋一个相同的标签值

Two-pass(等价表法)

1.第一次遍历

a.当像素 [x,y] 的邻域 (4-邻域) 的所有像素都为0,则给 [x,y] 一个新的标签,并且标签累加1

b.当像素 [x,y] 的邻域 (4-邻域) 中存在标签种类大于1时,则给 [x,y] 一个邻域中值最小的标签

c.当像素 [x,y] 的邻域 (4-邻域) 中存在标签种类等于1时,则给 [x,y] 一个相同的标签

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pTdz694k-1645958195365)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220217110313566.png)]

​ 等价表:{1 3} {5 4}

2.第二次遍历

给所有的像素重新赋值,值为等价表中的最小值

Seed Filling(种子填充法)

1.遍历

当像素 [x,y] 为前景,则作为种子,给与一个标签,之后将与之相邻的前景像素全部给与相同标签,重复该步骤

图像二值化的目的

指将 256阶的灰度图通过合适的阈值,转换为黑白二值图。即,像素只有0,1两种取值。其目的通常为将图像的前后景进行分割,以便进行进一步的处理

数学表达:对于图像中的像素 [x,y],其灰度值为 f(x,y),设置门限值(阈值)为TH,则:
g ( x , y ) = { 255 i f f ( x , y ) ≥ T H 0 i f f ( x , y ) < T H g(x,y)=\begin{cases} 255&if&f(x,y)\geq TH \\0&if&f(x,y)g(x,y)={2550ififf(x,y)THf(x,y)<TH
二值化的关键在于阈值的选择,合理的阈值应该尽可能的分离前景和背景

P-tile法

需要预先获得图像中前景占完整画面的比值 P%,依次累积灰度直方图,直到该累积值大于或等于前景图像(目标)所占面积,此时的灰度级即为所求的阈值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vVeFQbgv-1645958195366)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220217122854092.png)]

通过这个阈值,可以把P%的像素分割出来

最小误判概率法

设前景像素点灰度概率密度函数为 p(x),背景像素点灰度概率密度函数为 q(x),分布函数如图,前景像素个数占图像总像素数的百分比为θ-1,背景为 θ-2 = 1-θ-1;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iz6s3GsR-1645958195367)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220217123218148.png)]

设分割阈值为 T,前景像素被错分为背景的概率为
E 1 ( T ) = θ 1 ∫ T ∞ p ( x ) d x E_1(T)=θ_1\int_{T}^{\infty} {p(x)}dx E1(T)=θ1Tp(x)dx

背景像素被错分为前景的概率为
E 2 ( T ) = θ 2 ∫ − ∞ T q ( x ) d x E_2(T)=θ_2\int_{-\infty}^{T}{q(x)}dx E2(T)=θ2Tq(x)dx

阈值T造成的错误分割概率为
E ( T ) = θ 2 ∫ − ∞ T q ( x ) d x + θ 1 ∫ T ∞ p ( x ) d x E(T) = θ_2\int_{-\infty}^{T}{q(x)}dx+θ_1\int_{T}^{\infty} {p(x)}dx E(T)=θ2Tq(x)dx+θ1Tp(x)dx
最小误判概率法就是求一个使 E(T) 的数值最小的阈值T

显然当 E(T) 取得最小值时,其导数为0
δ E δ T = θ 2 q ( T ) − θ 1 p ( T ) = 0 \frac{\delta E}{\delta T}=θ_2q(T)-θ_1p(T) = 0 δTδE=θ2q(T)θ1p(T)=0

θ 2 q ( T ) = θ 1 p ( T ) θ_2q(T) = θ_1p(T) θ2q(T)=θ1p(T)

假设图像中前景和背景像素灰度都呈正态分布,均值和方差分别为
μ 1 , σ 1 2 , μ 2 , σ 2 2 μ_1,σ_1^2,\mu_2,\sigma_2^2 μ1,σ12,μ2,σ22
所以有
θ 2 e − ( μ 2 − T ) 2 2 σ 2 2 = θ 1 e − ( μ 1 − T ) 2 2 σ 1 2 θ_2e^{-\frac{(\mu_2-T)^2}{2\sigma_2^2}}=θ_1e^{-\frac{(\mu_1-T)^2}{2\sigma_1^2}} θ2e2σ22(μ2T)2=θ1e2σ12(μ1T)2
为了便于计算,假设
σ 1 2 = σ 2 2 = σ 2 \sigma_1^2=\sigma_2^2=\sigma^2 σ12=σ22=σ2

θ 1 = θ 2 = 1 2 θ_1=θ_2=\frac{1}{2} θ1=θ2=21

得到最佳阈值公式
= > ( μ 2 − T ) 2 = ( μ 1 − T ) 2 = > T = μ 1 + μ 2 2 =>(\mu_2-T)^2=(\mu_1-T)^2 =>T=\frac{\mu_1+\mu_2}{2} =>(μ2T)2=(μ1T)2=>T=2μ1+μ2
迭代法实现

a.选择阈值T的初始估计值(求图像的最大灰度A,最小灰度B,令(A+B)/2为初始估计值)

b.用 T 分割图像,得到两组像素:G1 由所有灰度值 > T 的像素组成,G2 所有灰度值 <= T的像素组成

c.对区域 G1 和 G2 中的所有像素计算平均灰度值 u1 和 u2

d.计算新的门限值
T = 1 2 ( u 1 + u 2 ) T=\frac{1}{2}(u1+u2) T=21(u1+u2)
e.重复步骤 b 到 d,直到逐次迭代所得的 T 值之差小于事先定义的参数 ξ (实际计算时候一般取 10^-4,或者直接定义迭代次数,比如 100次也能得到不错的结果)

大津法(OTSU)法

前景和背景的分布有交错部分,表示部分前景和背景像素颜色相同。由于交错部分存在,通过阈值将前后景完全分离是不可能的,只能寻找最优解。大津法考虑最佳的阈值应该,类内方差尽可能小,类间方差尽可能大

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8kU1z18h-1645958195369)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220217123218148.png)]

p分布:
像 素 数 量 : w 1 ; 均 值 : μ 1 ; 方 差 : σ 1 2 = ∑ i = 1 w 1 ( X i − μ 1 ) 2 w 1 像素数量:w_1;均值:μ_1;方差:σ_1^2=\frac{\sum_{i=1}^{w_1}{(X_i-\mu_1)}^2}{w_1} :w1;:μ1;:σ12=w1i=1w1(Xiμ1)2
q分布:
像 素 数 量 : w 2 ; 均 值 : μ 2 ; 方 差 : σ 2 2 = ∑ i = 1 w 2 ( X i − μ 2 ) 2 w 2 像素数量:w_2;均值:μ_2;方差:σ_2^2=\frac{\sum_{i=1}^{w_2}{(X_i-\mu_2)}^2}{w_2} :w2;:μ2;:σ22=w2i=1w2(Xiμ2)2

图 像 总 像 素 数 : w t ; 均 值 : μ t ; 方 差 : σ t 2 图像总像素数:w_t;均值:\mu_t;方差:\sigma_t^2 :wt;:μt;:σt2

类 内 方 差 : σ w 2 = w 1 σ 1 2 + w 2 σ 2 2 w 1 + w 2 类内方差:\sigma_w^2=\frac{w_1\sigma_1^2+w_2\sigma_2^2}{w_1+w_2} :σw2=w1+w2w1σ12+w2σ22

类 间 方 差 : σ b 2 = w 1 ( μ 1 − μ t ) 2 + w 2 ( μ 2 − μ 1 ) 2 w 1 + w 2 = w 1 w 2 ( μ 1 − μ 2 ) 2 ( w 1 + w 2 ) 2 类间方差:\sigma_b^2=\frac{w_1(\mu_1-\mu_t)^2+w_2(\mu_2-\mu_1)^2}{w_1+w_2}=\frac{w_1w_2(\mu_1-\mu_2)^2}{(w_1+w_2)^2} :σb2=w1+w2w1(μ1μt)2+w2(μ2μ1)2=(w1+w2)2w1w2(μ1μ2)2

分 离 度 : σ b 2 σ w 2 分离度:\frac{\sigma_b^2}{\sigma_w^2} :σw2σb2

由 于 σ t 2 = σ w 2 + σ b 2 则 σ b 2 σ w 2 = σ b 2 σ t 2 − σ b 2 由于\sigma_t^2=\sigma_w^2+\sigma_b^2则\frac{\sigma_b^2}{\sigma_w^2}=\frac{\sigma_b^2}{\sigma_t^2-\sigma_b^2} σt2=σw2+σb2σw2σb2=σt2σb2σb2

求 分 离 度 最 大 时 的 阈 值 , 即 求 σ b 2 最 大 , 而 类 间 方 差 分 母 为 固 定 值 , 因 此 即 求 分 子 最 大 时 的 阈 值 求分离度最大时的阈值,即求\sigma_b^2最大,而类间方差分母为固定值,因此即求分子最大时的阈值 σb2

局部自适应二值化

有一些场合,单一的阈值是不可能将前景的字母和背景分离开,因为一些字符的颜色和背景颜色是相同的,颜色分布上不呈双峰性

Chow and Kaneko algorithm

a.将图像分成多个子区域

b.对每个区域求解阈值

c.通过插值法计算每个像素的阈值

习题1

通过opencv将下图的rgb三个通道分离

Mat src_color = imread("test_2.png");
vector channels;
split(src_color, channels);
Mat B = channels.at(0);
Mat G = channels.at(1);
Mat R = channels.at(2);
imshow("Red", R);
imshow("Green", G);
imshow("Blue", B);
imshow("orignal Mat", src_color);

机器图像处理技术_第1张图片

习题2

调用本机摄像头

VideoCapture cap;
	
cap.open(0);

if (!cap.isOpened())
{
	cout << "不能打开视频文件" << endl;
	return -1;
}

double fps = cap.get(CAP_PROP_FPS);
cout << "fps:" << fps << endl;

while (1)
{
	Mat frame;
	bool rSucess = cap.read(frame);
	if (!rSucess)
	{
		cout << "不能从视频中读取帧" << endl;
		break;
	}
	else
	{
		imshow("frame", frame);
	}
}

习题3

opencv基本画图功能

int drawLines()
{
	Mat displayMat = Mat::zeros(500, 500, CV_8UC3);	//500是窗口尺寸

	//0.绘制的Mat图像
	//1.线段起点
	//2.线段终点
	//3.线段颜色
	//4.线段粗细
	//5.线段的连接方法,4邻接,8邻接,antialiased连接 
	//6.坐标点的小数点位数。

	// 红色 ,宽度为3,4近邻居连接
	line(displayMat, Point(100, 100), Point(400, 105), Scalar(0, 0, 200), 3, 4);
	// 绿色,宽度为5,8近邻居连接
	line(displayMat, Point(100, 200), Point(400, 205), Scalar(0, 200, 0), 5, 8);
	// 蓝色,宽度为10,antialiased线段连接法
	line(displayMat, Point(100, 300), Point(400, 305), Scalar(200, 0, 0), 10, CV_AA);

	namedWindow("lines", WINDOW_AUTOSIZE | WINDOW_FREERATIO);
	imshow("lines", displayMat);
	waitKey(0);

	destroyAllWindows();

	return 0;
}

int drawRectangles()
{
	Mat displayMat = Mat::zeros(500, 500, CV_8UC3);

	//0.绘制的Mat图像
	//1.矩形上的顶点1
	//2.顶点1的对角点
	//3.线段颜色
	//4.线段粗细,如果该参数是-1的话,则绘制实心的矩形
	//5.线段的连接方法,4邻接,8邻接,antialiased连接 
	//6.坐标点的小数点位数。
	// 红色 ,宽度为3,4近邻居连接
	rectangle(displayMat, Point(200, 50), Point(300, 150), Scalar(0, 0, 200), 3, 4);
	// 绿色,宽度为5,8近邻居连接
	rectangle(displayMat, Point(200, 200), Point(300, 300), Scalar(0, 200, 0), 5, CV_AA);
	//另一种绘制方式是,输入参数使用cv::Rect,而不是用两个点
	//0.绘制的Mat图像
	//1.被绘制的矩形
	//2.线段颜色
	//3.线段粗细,如果该参数是-1的话,则绘制实心的矩形
	//4.线段的连接方法,4邻接,8邻接,antialiased连接 
	//5.坐标点的小数点位数。
	// 蓝色,矩形内部填色,antialiased线段连接法
	Rect rect;
	rect.x = 200;
	rect.y = 350;
	rect.width = 50;
	rect.height = 100;
	rectangle(displayMat, rect, Scalar(200, 0, 0), -1, 8);
	namedWindow("drawing", WINDOW_AUTOSIZE | WINDOW_FREERATIO);
	imshow("drawing", displayMat);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

int drawCircles()
{
	Mat displayMat = Mat::zeros(500, 500, CV_8UC3);

	//0.绘制的Mat图像
	//1.圆心坐标
	//2.圆半径
	//3.圆的颜色
	//4.线段粗细,如果该参数是-1的话,则绘制实心圆
	//5.线段的连接方法,4邻接,8邻接,antialiased连接 
	//6.坐标点的小数点位数。

	//红色
	circle(displayMat, Point(300, 100), 100, Scalar(0, 0, 200), 3, 4);
	//绿色 
	circle(displayMat, Point(200, 250), 120, Scalar(0, 200, 0), 8, 8);
	//蓝色
	circle(displayMat, Point(300, 400), 80, Scalar(200, 0, 0), -1, CV_AA);

	namedWindow("drawing", WINDOW_AUTOSIZE | WINDOW_FREERATIO);
	imshow("drawing", displayMat);
	waitKey(0);
	destroyAllWindows();

	return 0;
}

int drawEllipse()
{
	Mat displayMat = Mat::zeros(800, 600, CV_8UC3);
	double angle;

	//0.绘制的Mat图像
	//1.中心坐标
	//2.长轴,短轴
	//3.旋转角度, 水平为0度,顺时针为正值
	//4.圆弧开始角度,水平向右为0度,顺时针为正值
	//5.圆弧结束角度
	//6.颜色
	//7.线段粗细,如果该参数是-1的话,则绘制实心圆
	//8.线段的连接方法,4邻接,8邻接,antialiased连接 
	//9.坐标点的小数点位数。

	ellipse(displayMat, Point(150, 150), Size(50, 10), 0, 30, 360, Scalar(0, 0, 200), 1, 4);

	angle = 30;
	ellipse(displayMat, Point(400, 150), Size(200, 100), angle, angle - 100, angle + 200, Scalar(0, 0, 200), 3, 4);

	angle = 0;
	//相当于画一个圆
	ellipse(displayMat, Point(200, 200), Size(100, 100), angle, angle, angle + 360, Scalar(0, 200, 0), 5, 8);

	angle = 100;
	ellipse(displayMat, Point(200, 400), Size(100, 200), angle, angle - 200, angle + 100, Scalar(200, 0, 0), -1, CV_AA);

	namedWindow("drawing", WINDOW_AUTOSIZE | WINDOW_FREERATIO);
	imshow("drawing", displayMat);
	waitKey(0);

	destroyAllWindows();

	return 0;
}

int drawMarkers()
{
	Mat displayMat = Mat::zeros(800, 600, CV_8UC3);



	//0.绘制的Mat图像
	//1.中心坐标
	//2.颜色
	//3.标记类型
	//4.标记的尺寸,默认20pixesl
	//5.线段粗细
	//6.线段的连接方法,4邻接,8邻接,antialiased连接 

	//加号
	drawMarker(displayMat, Point(100, 50), cv::Scalar(0, 255, 255), 0, 20, 1, 8);
	//叉
	drawMarker(displayMat, Point(100, 100), cv::Scalar(0, 255, 255), 1);
	//星
	drawMarker(displayMat, Point(100, 200), cv::Scalar(0, 255, 255), 2);
	//方片
	drawMarker(displayMat, Point(100, 250), cv::Scalar(0, 255, 255), 3);
	//方块
	drawMarker(displayMat, Point(100, 300), cv::Scalar(0, 255, 255), 4);
	//三角
	drawMarker(displayMat, Point(100, 350), cv::Scalar(0, 255, 255), 5);
	//倒三角
	drawMarker(displayMat, Point(100, 400), cv::Scalar(0, 255, 255), 6);


	namedWindow("drawing", WINDOW_AUTOSIZE | WINDOW_FREERATIO);
	imshow("drawing", displayMat);
	waitKey(0);

	destroyAllWindows();

	return 0;
}


//写字
int writeText()
{
	Mat img = Mat::zeros(500, 500, CV_8UC3);

	//定义不同字体
	int face[] = { FONT_HERSHEY_SIMPLEX, FONT_HERSHEY_PLAIN, FONT_HERSHEY_DUPLEX, FONT_HERSHEY_COMPLEX,
		FONT_HERSHEY_TRIPLEX, FONT_HERSHEY_COMPLEX_SMALL, FONT_HERSHEY_SCRIPT_SIMPLEX,
		FONT_HERSHEY_SCRIPT_COMPLEX, FONT_ITALIC };

	String ssss;

	//std::string  

	//0.文字绘制的Mat图像
	//1.被书写的文字,String类型 
	//2.文字的左下角的位置
	//3.字体
	//4.文字的尺寸参数
	//5.文字的颜色
	//6.文字的线条粗细
	//7.文字的连接类型

	for (int i = 0; i < 8; i++) {

		//int 转换为 字符
		stringstream ss1;
		string str1;
		ss1 << i;
		ss1 >> str1;

		putText(img, str1, Point(30, (i + 1) * 50), face[i], 1.2, Scalar(255, 255, 255), 2, CV_AA);

		putText(img, "OpenCV", Point(100, (i + 1) * 50), face[i], 1.2, Scalar(255, 255, 255), 2, CV_AA);
	}

	namedWindow("drawing", WINDOW_AUTOSIZE | WINDOW_FREERATIO);
	imshow("drawing", img);
	waitKey(0);
	destroyAllWindows();

	return 0;
}

习题4

直方图计算

VideoCapture cap;
cap.open(0);

double scale = 0.5;

while (1)
{
	Mat SrcImg;
	cap >> SrcImg;
	Size ResImgSiz = Size(SrcImg.cols * scale, SrcImg.rows * scale);
	Mat matSrc = Mat(ResImgSiz, SrcImg.type());
	resize(SrcImg, matSrc, ResImgSiz, INTER_LINEAR);
	Mat matRGB[3];
	split(matSrc, matRGB);
	int Channels[] = { 0 };
	int nHistSize[] = { 256 };
	float range[] = { 0, 255 };
	const float* fHistRanges[] = { range };
	Mat histR, histG, histB;
	// 计算直方图
	calcHist(&matRGB[0], 1, Channels, Mat(), histB, 1, nHistSize, fHistRanges, true, false);
	calcHist(&matRGB[1], 1, Channels, Mat(), histG, 1, nHistSize, fHistRanges, true, false);
	calcHist(&matRGB[2], 1, Channels, Mat(), histR, 1, nHistSize, fHistRanges, true, false);

	// 创建直方图画布
	int nHistWidth = 800;
	int nHistHeight = 600;
	int nBinWidth = cvRound((double)nHistWidth / nHistSize[0]);
	Mat matHistImage(nHistHeight, nHistWidth, CV_8UC3, Scalar(255, 255, 255));
	// 直方图归一化
	normalize(histB, histB, 0.0, matHistImage.rows, NORM_MINMAX, -1, Mat());
	normalize(histG, histG, 0.0, matHistImage.rows, NORM_MINMAX, -1, Mat());
	normalize(histR, histR, 0.0, matHistImage.rows, NORM_MINMAX, -1, Mat());
	// 在直方图中画出直方图
	for (int i = 1; i < nHistSize[0]; i++)
	{
		line(matHistImage,Point(nBinWidth * (i - 1), nHistHeight - cvRound(histB.at(i - 1))),Point(nBinWidth * (i), nHistHeight - cvRound(histB.at(i))),Scalar(255, 0, 0),2,8,0);
		line(matHistImage,Point(nBinWidth * (i - 1), nHistHeight - cvRound(histG.at(i - 1))),Point(nBinWidth * (i), nHistHeight - cvRound(histG.at(i))),Scalar(0, 255, 0),2,8,0);
		line(matHistImage,Point(nBinWidth * (i - 1), nHistHeight - cvRound(histR.at(i - 1))),Point(nBinWidth * (i), nHistHeight - cvRound(histR.at(i))),Scalar(0, 0, 255),2,8,0);
	}

	// 显示直方图
	imshow("frame", matSrc);
	imshow("histogram", matHistImage);
	waitKey(30);
}

图像形态学

绪论

理论基础为集合论

图像中的集合代表二值图像或者灰度图像的形状:如二值图像的前景像素集合

作用是简化图像数据,保持基本形状特性,去除不相干的结构等

基本运算包括:膨胀,腐蚀,开运算,闭运算,顶帽运算和底帽运算等

膨胀运算

当结构元素所覆盖范围之内出现0,则所有 0 变为 1

作用:由于无法实现理想的二值化,使得原本连通的像素集合被分成不同的连通域,从而影响目标物的提取,可通过膨胀运算使其恢复连通性

腐蚀运算

当结构元素所覆盖范围之内如果出现0,则被处理的像素置0

操作过程是在原图中处理的,即腐蚀运算产生的 0 不参加比较

作用:去除一些黏连像素,以及去除噪声

开运算

对图像先进行 3x3腐蚀,再对腐蚀结果进行3x3膨胀

先腐蚀再膨胀的结果并不是恢复原状,而是会消除黏连部分,同时不影响其他部分的形状

闭运算

对图像先进行 3x3膨胀,再对腐蚀结果进行3x3腐蚀

先膨胀再腐蚀的结果并不是恢复原状,而是保留原状的同时,对原先图像之间的小孔得到了填充

顶帽和底帽变换

**顶帽变换:**原图-灰度开运算结果(灰度腐蚀+灰度膨胀)

· 保留比结构元素小的部分

· 保留比周围环境亮的像素

· 顶帽处理还可以消除背景光照不均匀的现象,从而改善再二值化时的效果,同样结构元素的尺寸大小要根据目标物体的大小进行选择

**底帽变换:**灰度闭运算结果(灰度膨胀+灰度腐蚀) - 原图

· 保留比结构元素小的部分

· 保留比周围环境暗的像素

灰度图的形态学处理

灰度图的腐蚀运算:遍历像素结构元素锚点和待处理的像素对齐,该像素处理后的值等于结构元素范围内的最小值

灰度图的膨胀运算:遍历像素结构元素锚点和待处理的像素对齐,该像素处理后的值等于结构元素范围内的最大值

习题1

肤色检测

VideoCapture cap(0);
	
double scale = 0.5;

double i_minH = 0;
double i_maxH = 20;

double i_minS = 43;
double i_maxS = 255;

double i_minV = 55;
double i_maxV = 255;

while (1)
{
	Mat frame;
	Mat hsvMat;
	Mat detecMat;
	cap >> frame;
	Size ResImgSiz = Size(frame.cols * scale, frame.rows * scale);
	Mat rFrame = Mat(ResImgSiz, frame.type());
	resize(frame, rFrame, ResImgSiz, INTER_LINEAR);

	cvtColor(rFrame, hsvMat, CV_BGR2HSV);
	rFrame.copyTo(detecMat);

	inRange(hsvMat, Scalar(i_minH, i_minS, i_minV), Scalar(i_maxH, i_maxS, i_maxV), detecMat);
	imshow("whie:in the range", detecMat);
	imshow("frame", rFrame);
	waitKey(30);

}
return 0;

习题2

二值化

void binarization() {
	Mat srcMat = imread("test.bmp", 0);
	if (srcMat.empty())
	{
		cout << "fail to read!" << endl;
		return;
	}

	Mat bin_Mat;
	Mat otsu_Mat;
	Mat adap_Mat;

	threshold(srcMat, bin_Mat, 100, 255, CV_THRESH_BINARY);
	threshold(srcMat, otsu_Mat, 100, 255, CV_THRESH_OTSU);
	adaptiveThreshold(srcMat, adap_Mat, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 15, 10);
	imshow("bin_Mat", bin_Mat);
	imshow("otsu_Mat", otsu_Mat);
	imshow("adap_Mat", adap_Mat);
	waitKey(0);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i88IqbPb-1645958195372)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220218160440927.png)]

习题3

可调节的阈值

void callback(int th, void* data)
{
	//强制类型转换
	Mat src = *((Mat*)data);

	//定义输出图片容器
	Mat dst;

	//二值化函数
	threshold(src, dst, th, 255, CV_THRESH_BINARY);
	imshow("bar", dst);
}

void trackbar()
{
	//定义图像容器
	Mat srcMat;
	Mat gryMat;


	//初始化滑动条的值,并设置滑动节的调节范围
	int lowth = 30;
	int maxth = 255;

	//读取图片
	srcMat = imread("test.bmp");

	//判断图片读取是否成功
	if (srcMat.empty())
	{
		cout << "fail to read!" << endl;
	}


	//转化为灰度图
	cvtColor(srcMat, gryMat, CV_BGR2GRAY);

	//显示灰度图
	namedWindow("bar");
	imshow("bar", gryMat);
	/************************************************
	createTrackbar() 函数模型:
	createTrackbar(const String& trackbarname, const String& winname,
							  int* value, int count,
							  TrackbarCallback onChange = 0,
							  void* userdata = 0);
							  */
	createTrackbar("阈值", "bar", &lowth, maxth, callback, &gryMat);
	waitKey(0);
}

空间滤波

图像卷积

空间滤波是一种采用滤波处理的影像增强方法

理论基础是空间卷积和空间相关

目的改善影像质量,包括去除高频噪声与干扰。及影像边缘增强线性增强以及去模糊

分为低通滤波(平滑化)高通滤波(锐化)带通滤波

一维卷积实例:

对数字图像做卷积操作其实就是利用卷积核在图像上滑动,将图像点上的像素灰度值与对应的卷积核上的数值相乘,然后将所有相乘后的值相加作为卷积核中间像素对应的图像上像素的灰度值,并最终滑动完所有图像

Input:0,1,2,3,4,5,6

卷积核:2,1,1
0 ∗ 2 + 1 ∗ 1 + 2 ∗ 1 = 3 0*2+1*1+2*1=3 02+11+21=3
Output:0,3,7,11,15,19,0

边缘位置不能处理,一般置0

均值滤波

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cC0I61nz-1645958195373)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220219134508882.png)]

与周围差距较大的值与项羽周围相似,整体值趋向于平均化

通过高斯分布加权的高斯平滑卷积

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MtHbbu2o-1645958195374)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220219134635830.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9m2AqdPb-1645958195375)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220219134645812.png)]

中值滤波

非线性的滤波,不需要指定卷积核,只需要指定滤波器尺寸

Input:10,12,28

卷积核尺寸:3

排序:10,12,28

Output:12

Input:12,28,10

排序:10,12,28

Output:12

一些和周围像素值差异特别大的点呗周围的像素值代替。表现在图像就是一些特别亮或特别暗的点被周围的像素值代替

椒盐噪声:

随机出现的白点或者黑点,即亮的区域有黑色像素或是在暗的区域有白色像素

成因:图像信号收到突如其来的强烈干扰而产生

通常使用中值滤波降噪

高斯噪声:

例如由照明不良和/或高温引起的传感器噪声

通常用平滑滤波进行降噪

图像的边缘检测

边缘是图像中的重要特征信息,如深度学习中的卷积神经网络,其本质就是通过卷积抓取基本的边缘特征,再不断向上构建更高层次的特征

边缘再图像上表现为亮度变化剧烈的像素点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8raYpZ1z-1645958195376)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220219140239936.png)]

离散信号的微分计算 = 减法运算
水 平 方 向 : δ f ( x , y ) δ x ≈ f ( x + 1 , y ) − f ( x , y ) x + 1 − x = f ( x + 1 , y ) − f ( x , y ) 水平方向:\frac{\delta f(x,y)}{\delta x}≈\frac{f(x+1,y)-f(x,y)}{x+1-x}=f(x+1,y)-f(x,y) δxδf(x,y)x+1xf(x+1,y)f(x,y)=f(x+1,y)f(x,y)

垂 直 方 向 : δ f ( x , y ) δ y ≈ f ( x , y + 1 ) − f ( x , y ) y + 1 − y = f ( x , y + 1 ) − f ( x , y ) 垂直方向:\frac{\delta f(x,y)}{\delta y}≈\frac{f(x,y+1)-f(x,y)}{y+1-y}=f(x,y+1)-f(x,y) δyδf(x,y)y+1yf(x,y+1)f(x,y)=f(x,y+1)f(x,y)

KaTeX parse error: Undefined control sequence: \matrix at position 9: \left[ \̲m̲a̲t̲r̲i̲x̲{ 0 & 0 & 1 &…

实际就是求图像水平方向上的微分

Prewitt算子

由两个卷积核组成
KaTeX parse error: Undefined control sequence: \matrix at position 9: \left[ \̲m̲a̲t̲r̲i̲x̲{ -1 & & & 1 …

Sobel算子

利用高斯分布进行加权得到卷积核
KaTeX parse error: Undefined control sequence: \matrix at position 9: \left[ \̲m̲a̲t̲r̲i̲x̲{ -1 & & & 1 …

Laplacian算子

∇ 2 f = δ 2 f δ 2 x 2 + δ 2 f δ 2 y 2 \nabla^2f=\frac{\delta^2f}{\delta^2x^2}+\frac{\delta^2f}{\delta^2y^2} 2f=δ2x2δ2f+δ2y2δ2f

KaTeX parse error: Undefined control sequence: \matrix at position 9: \left[ \̲m̲a̲t̲r̲i̲x̲{ & 1 & \\ …

推导过程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yoXkGB1P-1645958195377)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220219141840679.png)]

canny算子

最常用的边缘检测算子,很好的解决了伪边缘的问题

步骤:

· 高斯卷积降噪

· 计算图像的一阶微分,方向和幅值

· 非极大值抑制

· 双阈值处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kSQSeZfC-1645958195378)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220219142129020.png)]

在一阶微分的基础上,canny算子进一步求出边缘的实际方向和该方向上的幅值
幅 值 : G = G x 2 + G y 2 幅值:G=\sqrt{G_x^2+G_y^2} G=Gx2+Gy2

方 向 : θ = t a n − 1 ( G y G x ) 方向:\theta = tan^{-1}(\frac{G_y}{G_x}) θ=tan1(GxGy)

并非梯度响应值大的像素就是边缘像素,而是局部最大值为边缘像素

判断点A 是否为边缘点,需要把和其他边缘方向上的B点和C点进行比较,如果是最大值的化则进入下一步,否则置0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zlWc423B-1645958195379)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220219142649928.png)]

双阈值筛选并连接边缘像素,设置最大阈值 HT 和最小阈值 LT

像素 评价
梯度值 > HT 边缘像素

​ HT > 梯度值 > LT 如果该像素周围有边缘像素,则该像素为边缘像素,否 则为非边缘像素

​ 梯度值 < LT 非边缘像素

习题1

腐蚀,膨胀,开和闭运算

void morphology()
{
	//读取图片并转化为灰度图
	Mat srcMat = imread("1and2.png", 0);

	//判断读取图片是否失败
	if (srcMat.empty()) {
		cout << "fail to read pic!" << endl;
		return;
	}
	//定义图像容器
	Mat thresh_Mat;
	Mat dilate_Mat;
	Mat erode_Mat;
	Mat open_Mat;
	Mat close_Mat;

	//二值化
	threshold(srcMat, thresh_Mat, 100, 255, THRESH_OTSU);

	/************************************************
	getStructuringElement() 函数模型:
	getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));

	参数介绍:
	. int shape: 这个函数的第一个参数表示内核的形状,有三种形状可以选择。矩形:MORPH_RECT;交叉形:MORPH_CROSS;椭圆形:MORPH_ELLIPSE;
	. Size ksize: 内核的尺寸
	. Point anchor: 锚点的位置
	**************************************************/
	//定义结构元素
	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));

	//腐蚀
	/************************************************
	erode() 函数模型:
	 erode( InputArray src, OutputArray dst, InputArray kernel,
						 Point anchor = Point(-1,-1), int iterations = 1,
						 int borderType = BORDER_CONSTANT,
						 const Scalar& borderValue = morphologyDefaultBorderValue() );

	参数介绍:
	. InputArray src: Mat类,通道数量不限,但深度应为CV_8U,CV_16U...
	. OutputArray dst: 输出图像,需要有和原图片一样的尺寸和类型
	. InputArray kernel: 腐蚀操作的内核,一般用3*3的核
	. Point anchor:锚的位置,一般用(-1,-1)
	.int iterations:使用函数的次数
	.int borderType:用于推断图像外部像素的某种边界模式
	. const Scalar& borderValue:边界为常数时的边界值

	**************************************************/

	erode(thresh_Mat, erode_Mat, element, Point(-1, -1), 1);

	//膨胀
	/************************************************
	dialte() 函数模型:
	dilate( InputArray src, OutputArray dst, InputArray kernel,
						  Point anchor = Point(-1,-1), int iterations = 1,
						  int borderType = BORDER_CONSTANT,
						  const Scalar& borderValue = morphologyDefaultBorderValue() );

	参数介绍:
	. InputArray src: Mat类,通道数量不限,但深度应为CV_8U,CV_16U...
	. OutputArray dst: 输出图像,需要有和原图片一样的尺寸和类型
	. InputArray kernel: 腐蚀操作的内核,一般用3*3的核
	. Point anchor:锚的位置,一般用(-1,-1)
	.int iterations:使用函数的次数
	.int borderType:用于推断图像外部像素的某种边界模式
	. const Scalar& borderValue:边界为常数时的边界值

	**************************************************/
	dilate(thresh_Mat, dilate_Mat, element, Point(-1, -1), 1);

	//开运算
	/************************************************
	 morphologyEx() 函数模型:
	 morphologyEx( InputArray src, OutputArray dst,
								int op, InputArray kernel,
								Point anchor = Point(-1,-1), int iterations = 1,
								int borderType = BORDER_CONSTANT,
								const Scalar& borderValue = morphologyDefaultBorderValue() );


	参数介绍:
	. InputArray src: Mat类,通道数量不限,但深度应为CV_8U,CV_16U...
	. OutputArray dst: 输出图像,需要有和原图片一样的尺寸和类型
	. int op:表示形态学运算的类型,如MORPH_OPEN、MORPH_CLOSE分别代表开运算和闭运算
	. InputArray kernel: 腐蚀操作的内核,一般用3*3的核
	. Point anchor:锚的位置,一般用(-1,-1)
	. int iterations:使用函数的次数
	. int borderType:用于推断图像外部像素的某种边界模式
	. const Scalar& borderValue:边界为常数时的边界值

	**************************************************/
	morphologyEx(thresh_Mat, open_Mat, MORPH_OPEN, element, Point(-1, -1), 1);

	// 闭运算
	morphologyEx(thresh_Mat, close_Mat, MORPH_CLOSE, element, Point(-1, -1), 1);


	//显示结果
	imshow("thresh_Mat", thresh_Mat);
	imshow("erode_Mat", erode_Mat);
	imshow("dilate_Mat", dilate_Mat);
	imshow("open_Mat", open_Mat);
	imshow("close_Mat", close_Mat);
	waitKey(0);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aZmptKgZ-1645958195380)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220219143739517.png)]

习题2

连通域标记

void connectedwithstats()
{
	//读取图片并转化为灰度图
	Mat srcMat = imread("1and2.png");

	//判断读取图片是否失败
	if (srcMat.empty()) {
		cout << "fail to read pic!" << endl;
		return;
	}

	//转化为灰度图
	Mat gryMat;
	cvtColor(srcMat, gryMat, COLOR_BGRA2GRAY);

	//定义图像容器
	Mat stats;
	Mat centroids;
	Mat labels;
	Mat thresh_Mat;

	//大津法处理图像
	threshold(gryMat, thresh_Mat, 100, 255, THRESH_OTSU);


	//进行连通域标记
	/************************************************
	connectedComponentsWithStats() 函数模型:
	connectedComponentsWithStats(InputArray image, OutputArray labels,
											  OutputArray stats, OutputArray centroids,
											  int connectivity = 8, int ltype = CV_32S);


	参数介绍:
	. InputArray image: 输入8位单通道二值图像;
	. OutputArray labels: 输出和原图image一样大的标记图,label对应于表示是当前像素是第几个轮廓,背景置0
	. OutputArray stats:输出nccomps(标签数)×5的矩阵 ,表示每个连通区域的外接矩形和面积(pixel)
	. OutputArray centroids: 对应的是轮廓的中心点。nccomps×2的矩阵 表示每个连通区域的质心
	. int connectivity:使用8邻域或者4邻域
	. int ltype:输出标签的数据类型

	**************************************************/
	int nComp = connectedComponentsWithStats(thresh_Mat, labels, stats, centroids, 8, CV_32S);
	

	//减去背景0,并输出
	cout << "硬币个数为:" << nComp - 1 << endl;

	//对识别出的连通域加最小外接边框
	for (int i = 1; i < nComp; i++)
	{
		//定义Rect类
		Rect bandbox;
		bandbox.x = stats.at(i, 0);
		bandbox.y = stats.at(i, 1);

		bandbox.width = stats.at(i, 2);
		bandbox.height = stats.at(i, 3);
		//画出矩形
		/************************************************
		rectangle() 函数模型:
		rectangle(CV_IN_OUT Mat& img, Rect rec,
						  const Scalar& color, int thickness = 1,
						  int lineType = LINE_8, int shift = 0);

		参数介绍:
		. ICV_IN_OUT Mat& img: CV_IN_OUT Mat& img
		. Rect rec: Rect类成员(包含矩形的左上角坐标以及长宽)
		. const Scalar& color:输出颜色信息
		. int thickness: 表示线的粗细
		. int lineType :邻接关系,一般设置默认值
		. int shift: 偏移,一般设0
		**************************************************/
		rectangle(thresh_Mat, bandbox, 255, 1, 8, 0);
	}
	imshow("thresh_Mat", thresh_Mat);
	waitKey(0);
}

习题3

原点计数

void origincount()
{
	//读取图片并转化为灰度图
	Mat srcMat = imread("3.jpg");

	//判断读取图片是否失败
	if (srcMat.empty()) {
		cout << "fail to read pic!" << endl;
		return;
	}

	//转化为灰度图
	Mat gryMat;
	cvtColor(srcMat, gryMat, COLOR_BGRA2GRAY);

	//反色
	gryMat = 255 - gryMat;

	Mat stats;
	Mat centroids;
	Mat labels;
	Mat thresh_Mat;
	Mat erode_Mat;

	//大津法处理图像
	threshold(gryMat, thresh_Mat, 100, 255, THRESH_OTSU);


	//定义结构元素
	Mat element = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));

	//对图像进行腐蚀处理,只保留要求的点
	erode(thresh_Mat, erode_Mat, element, Point(-1, -1), 2);



	//进行连通域标记
	int nComp = connectedComponentsWithStats(erode_Mat, labels, stats, centroids, 8, CV_32S);
	imshow("erode_Mat", erode_Mat);
	waitKey(0);

	//减去背景,并输出个数
	cout << "原点个数为:" << nComp - 1 << endl;

}

习题4

回形针计数

void clipcount()
{
	//读取图片并转化为灰度图
	Mat srcMat = imread("4.png");

	//判断读取图片是否失败
	if (srcMat.empty()) {
		cout << "fail to read pic!" << endl;
		return;
	}

	//转化为灰度图
	Mat gryMat;
	cvtColor(srcMat, gryMat, COLOR_BGRA2GRAY);

	//反色
	gryMat = 255 - gryMat;

	Mat stats;
	Mat centroids;
	Mat labels;
	Mat thresh_Mat;
	Mat open_Mat;

	//大津法处理图像
	threshold(gryMat, thresh_Mat, 100, 255, THRESH_OTSU);


	//定义结构元素
	Mat element = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));

	//对图像进行开运算处理,消除一些杂点
	morphologyEx(thresh_Mat, open_Mat, MORPH_OPEN, element, Point(-1, -1), 1);



	//进行连通域标记
	int nComp = connectedComponentsWithStats(open_Mat, labels, stats, centroids, 8, CV_32S);


	//比较长宽比,筛选掉干扰连通域
	for (int i = 1; i < nComp; i++)
	{
		int width = stats.at(i, 2);
		int height = stats.at(i, 3);
		int ratio = height / width;
		if (ratio > 10)
		{
			nComp--;
		}
	}

	imshow("open_Mat", open_Mat);
	//减去背景连通域,输出回形针格个数
	cout << "回型针个数为:" << nComp - 1 << endl;
	waitKey(0);
}

图像变换

几何变换

又称空间变换,是图形处理的一方面,是各种图形处理算法的基础

将一副图像中的坐标位置映射到另一幅图像中的新坐标位置,其实质是改变像素的空间位置,估算新空间位置上的像素值

包括:空间变换运算插值算法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SOcd8VNj-1645958195381)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220220150146937.png)]
KaTeX parse error: Undefined control sequence: \matrix at position 37: …}=[xy1]·\left[ \̲m̲a̲t̲r̲i̲x̲{ a&b&p\\ c&d&q…
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SgZJMWPN-1645958195383)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220220150408953.png)]

T1:比例,旋转,对称,错切

T2:平移

T3:投影

T4:整体缩放

s通常会设置为1

齐次坐标:对一个在 2 维平面上的点(x,y),对任意非零实数Z,三元组(xZ,yZ,Z)即称之为该点的齐次坐标

本质上:使用 n+1 维,来表示 n维 的坐标

(x,y,z) => (x/z,y/z)

· 统一坐标的加法运算和乘法运算,运算时提高效率

· 表示无穷远的点

· (2,2,1)和(4,4,2)表示同样的点

· 当 z = 0 时候,表示无穷远的点

z 同时是一个尺度,可以控制点的位置

平层变换

原坐标 (x,y),平移后坐标 (x’,y’)
{ x ′ = x + T x y ′ = y + T y \begin{cases}x'=x+T_x\\y'=y+T_y\end{cases} {x=x+Txy=y+Ty
矩阵相乘形式
KaTeX parse error: Undefined control sequence: \matrix at position 22: …1]=[xy1]·\left[\̲m̲a̲t̲r̲i̲x̲{1&0&0\\0&1&0\\…

x ′ = x × 1 + y × 0 + 1 × T x ( y ′ 同 理 ) x' = x\times1+y\times0+1\times T_x(y'同理) x=x×1+y×0+1×Tx(y)

平移是一种不产生形变而移动物体的刚体变换,Tx和Ty被称为平移变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LRBtAqet-1645958195384)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220220151708557.png)]

比例缩放

给定的图像在 x轴放心按比例缩放 a倍,在 y轴放心按比例缩放 b倍,从而获得一副新的图像

如果 a = b,称这样的比例缩放为图像的全比例缩放,如果 a 不等于 b,图像的比例缩放会改变原始图像的像素间的相对位置,产生几何畸变
原 坐 标 ( x , y ) , 缩 放 后 坐 标 ( x ′ , y ′ ) { x ′ = x s x y ′ = y s y 原坐标(x,y),缩放后坐标(x',y') \begin{cases}x'=xs_x\\y'=ys_y\end{cases} (x,y)(x,y){x=xsxy=ysy
矩阵相乘形式
KaTeX parse error: Undefined control sequence: \matrix at position 23: …]=[xy1]· \left[\̲m̲a̲t̲r̲i̲x̲{S_X&0&0\\0&S_y…

x ′ = x × s x + y × 0 + 1 × 0 ( y 同 理 ) x'=x\times s_x+y\times0+1\times0(y同理) x=x×sx+y×0+1×0(y)

旋转和镜像

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lgg6AQX8-1645958195385)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220220152932200.png)]
x ′ = r × c o s ( a + θ ) = r × c o s a × c o s θ − r × s i n a × s i n θ = x × c o s θ − y × s i n θ x'=r\times cos(a+\theta)\\=r\times cosa\times cos\theta-r\times sina\times sin\theta\\=x\times cos\theta-y\times sin\theta x=r×cos(a+θ)=r×cosa×cosθr×sina×sinθ=x×cosθy×sinθ

y ′ = r × s i n ( a + θ ) = r × c o s a × s i n θ − r × s i n a × c o s θ = x × s i n θ − y × c o s θ y'=r\times sin(a+\theta)\\=r\times cosa\times sin\theta-r\times sina\times cos\theta\\=x\times sin\theta-y\times cos\theta y=r×sin(a+θ)=r×cosa×sinθr×sina×cosθ=x×sinθy×cosθ

矩阵形式为
KaTeX parse error: Undefined control sequence: \matrix at position 8: \left[\̲m̲a̲t̲r̲i̲x̲{cos\theta&sin\…

对称变换

对称变换后的图形是原图形关于某一轴线或原点的镜像

KaTeX parse error: Undefined control sequence: \matrix at position 16: 关于x轴对称= \left[\̲m̲a̲t̲r̲i̲x̲{1&0&0\\0&-1&0\…

KaTeX parse error: Undefined control sequence: \matrix at position 16: 关于y轴对称= \left[\̲m̲a̲t̲r̲i̲x̲{-1&0&0\\0&1&0\…

KaTeX parse error: Undefined control sequence: \matrix at position 18: …于x=y轴对称= \left[\̲m̲a̲t̲r̲i̲x̲{0&1&0\\1&0&0\\…

KaTeX parse error: Undefined control sequence: \matrix at position 19: …x=-y轴对称= \left[\̲m̲a̲t̲r̲i̲x̲{0&-1&0\\-1&0&0…

错切变换

也称剪切,错位变换,用于产生弹性物体的变形处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tKmft22q-1645958195386)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220220153745196.png)]
KaTeX parse error: Undefined control sequence: \matrix at position 8: \left[\̲m̲a̲t̲r̲i̲x̲{1&d&0\\b&1&0\\…
1)改变d,x方向错切

2)改变b,y方向错切

3)改变两个参数,两个方向同时错切

复合变换

图形作一次以上的几何变换,变换结果是每次的变换矩阵相乘

任何一复杂的几何变换都可以看作基本几何变换的组合形式

仿射变换和投影变换

仿射允许图形任意移动,任意倾斜,在 x 和 y 方向上任意伸缩

点共线特性:原本一条直线经过仿射变换,必然还是一条直线

原图的平行线经过仿射变换,还是平行线

但是线段长度,及直线夹角不一定保持不变

投影变换:点共线特性依旧保持,平行线有可能不再平行

投影变换由八个参数(T1+T2+T3)决定

例如:我们在扫描二维码的时候,往往做不到使成像面完全平行于实际画面,可通过投影变换进行矫正
KaTeX parse error: Undefined control sequence: \matrix at position 22: …1]=[xy1]·\left[\̲m̲a̲t̲r̲i̲x̲{a&b&0\\c&d&0\\…
仿射变换需要求解 6 个未知数,需要 3 组对应点
KaTeX parse error: Undefined control sequence: \matrix at position 22: …1]=[xy1]·\left[\̲m̲a̲t̲r̲i̲x̲{a&b&p\\c&d&q\\…
投影变换需要求解 8 个未知数,需要 4 组对应点

插值算法

图像发生几何变化时,像素数量发生变化,需要使用插值算法估算新像素的值

当图像缩小,新图像的像素值可以采用等间距采用的方式获得新图像的像素值

当图像放大时,则需要对新产生的像素的像素值进行推算,即对图像进行插值

最近邻插值法

不需要计算,是最快速的插值算法

对待定的像素值,直接取其最近的像素的像素值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8IdNkTm5-1645958195387)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220220155423048.png)]

缺点是插值生成的图像灰度上的不连续,会有明显的锯齿状

双线性插值法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S6PDiOYe-1645958195388)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220220155537844.png)]

x1 和 x2 成线性变化,而新产生的点也符合该线性变化
f ( x 3 ) ≈ x 2 − x 3 x 2 − x 1 f ( x 2 ) + x 3 − x 1 x 2 − x 1 f ( x ! ) f(x_3)≈\frac{x_2-x_3}{x_2-x_1}f(x_2)+\frac{x_3-x_1}{x_2-x_1}f(x_!) f(x3)x2x1x2x3f(x2)+x2x1x3x1f(x!)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NiK0q6C9-1645958195390)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220220155826896.png)]

双三次插值法

考虑最近四个像素,通过三项式去逼近原函数
f ( x ) = a x 3 + b x 2 + c x + d f ′ ( x ) = 3 a x 2 + 2 b x + c f ( 0 ) = d f ( 1 ) = a + b + c + d f ′ ( 0 ) = c f ′ ( 1 ) = 3 a + 2 b + c = > f 0 = p 1 f ( 1 ) = p 2 f ′ ( 0 ) = p 2 − p 0 2 f ′ ( 1 ) = p 3 − p 1 2 f(x)=ax^3+bx^2+cx+d\\f'(x)=3ax^2+2bx+c\\f(0)=d\\f(1)=a+b+c+d\\f'(0)=c\\f'(1)=3a+2b+c\\=>\\f{0}=p1\\f(1)=p2\\f'(0)=\frac{p2-p0}{2}\\f'(1)=\frac{p3-p1}{2} f(x)=ax3+bx2+cx+df(x)=3ax2+2bx+cf(0)=df(1)=a+b+c+df(0)=cf(1)=3a+2b+c=>f0=p1f(1)=p2f(0)=2p2p0f(1)=2p3p1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FIHPC679-1645958195390)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220220160319046.png)]

练习

磨皮

void Convolution_app()
{
	//打开摄像头
	VideoCapture cap(0);

	//肤色h					
	double i_minH = 0;
	double i_maxH = 20;

	//肤色颜色饱和度s
	double i_minS = 0;
	double i_maxS = 255;

	//肤色颜色亮度v
	double i_minV = 0;
	double i_maxV = 255;

	while (1)
	{
		//定义图像容器
		Mat frame;
		Mat hsvMat;
		Mat maskMat;
		Mat objectMat;
		Mat guss_Mat;

		cap >> frame;//读取当前帧的照片

		//将原图转化为hsv类型的图片
		cvtColor(frame, hsvMat, COLOR_BGR2HSV);

		//对detecMat进行初始化
		frame.copyTo(maskMat);

		//利用inRange函数对图片进行hsv筛选(选出人脸部分)
		inRange(hsvMat, Scalar(i_minH, i_minS, i_minV), Scalar(i_maxH, i_maxS, i_maxV), maskMat);

		//原图拷入输出中
		frame.copyTo(objectMat);

		//对图像进行高斯滤波
		GaussianBlur(frame, guss_Mat, Size(5, 5), 3, 0);

		//高斯滤波后的人脸部分送入目标图中
		guss_Mat.copyTo(objectMat, maskMat);//从原图中取出目标图像(与运算)


		//显示结果
		imshow("display", maskMat);
		imshow("磨皮前", frame);
		imshow("磨皮后", objectMat);

		waitKey(30);
	}

}

图像特征

概论

即通过数学方法来描述图像的属性或特点,是图像识别图像分析等任务中的一个基本问题

· 通过人工设计数学方法来描述图像的特征

· 深度学习,通过训练数据自动提取特征

但是,人工设计的特征量在很多视觉任务中任然起到很重要的作用

图像特征的选择:

· 可区别性(能够反映图像的本质特点)

· 鲁棒性(旋转,光照,尺寸等)

· 维度(可区别性和鲁棒性的平衡)

HSV直方图

HSV特征,将图像所有像素的 RGB值转化为 HSV格式,并将 HSV值量化后一维展开

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D4uGsVRz-1645958195391)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220221175000808.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8yV0B7Uh-1645958195392)(C:\Users\AlexMiller\AppData\Roaming\Typora\typora-user-images\image-20220221175344115.png)]

通过两个图像的 HSV 图来比较它们颜色的相似程度

形状特征

区域特征:计算连通域形状特征。

1.面积:连通域像素总数

2.周长:边界像素的总数,或相邻边界像素的距离总和。

3.圆形度:如果 F 是区域的面积,max是从中心到所有轮廓像素的最大距离,则圆形度定义为
C = F Π × m a x 2 C=\frac{F}{Π\times max^2} C=Π×max2F
这个比值越小,说明这个形状和圆形越不接近

4.矩形度:最小外接矩形和连通域面积的比值
![[Pasted image 20220227160526.png]]

5.宽长比:长轴与短轴的比值
![[Pasted image 20220227160714.png]]

Shape Context

对形状在空间上的分布来进行对形状的描述

  1. 先进行离散化
    ![[Pasted image 20220227161120.png]]
    将它的边缘离散化成一个一个的采样点
    2.使用极坐标的方法来描述采样点在空间上的分布
    3.建立 16维 的直方图
    4.计算每个区域中采样点的数量,填入直方图
    ![[Pasted image 20220227161719.png]]

一般会采用 二维的直方图,颜色深浅表示值的大小

对两个形状,假设分别有 N 个采样点。构成 N * N 的 cost matrix(效益矩阵)
Cost matrix 的每个成分即为点与点的 shape context 的直方图之间的相似度。转化一次指派问题,对点进行匹配。匹配的总 cost 即为 形状的总相似度。一次指派问题可通过匈牙利算法求解
![[Pasted image 20220227162731.png]]
矩阵的第一个元素表示的 形状A 中间的第一个采样点 和 形状B 中间的第一个采样点 它们的相似度,即两个直方图之间的距离
要尽可能使 形状A 中的每个采样点都与 形状B 中最相似的采样点匹配
总的距离总和就是两个形状的相似程度

霍夫变换

很多应用场景下,不光需要知道代表边缘的像素,还需要进一步获得直线方程

获得边缘点,通过最小二乘法拟合直线,

理想情况下,边缘点全部在一条线上

霍夫变换,在参数空间中执行投票来决定参数,假设直线方程为 y = kx + b

其直线参数为 k 和 b 。构建由 k ,b ,组成的参数空间,即霍夫空间

![[Pasted image 20220227163404.png]]

![[Pasted image 20220227163429.png]]

假设图像空间中,一条直线经过 点[x0,y0] 和点[x1,y1],在霍夫空间中表现为两条直线的交点

![[Pasted image 20220227163653.png]]

假设:
![[Pasted image 20220227163752.png]]
∵ y = m x + b \because y = mx+b y=mx+b
∴ b = − x m + y \therefore b=-xm+y\\ b=xm+y
(1,0)->b=-m
(1,1)->b=-m+1

在霍夫空间中,包含线最多的那个交点,即在图像空间被拟合的直线

![[Pasted image 20220227164626.png]]

在图像空间中即

![[Pasted image 20220227164651.png]]

使用斜截式 y = k 0 x + b 0 y=k_0x+b_0 y=k0x+b0表示直线的问题:无法表示垂直于 x轴 的直线。因为其斜率为无穷大。实际采用 极坐标形式,可构建 (ρ,θ)组成的参数空间

y = ( − c o s θ s i n θ ) x + ( ρ s i n θ ) = > ρ = x c o s θ + y s i n θ y=(-\frac{cos\theta}{sin\theta})x+(\frac{\rho}{sin\theta})\\=>\rho=xcos\theta+ysin\theta y=(sinθcosθ)x+(sinθρ)=>ρ=xcosθ+ysinθ

给定一个点[x0,y0],则通过该点所有的直线的参数(ρ,θ)的集合,在(ρ,θ)参数空间中为 cos函数。
因此,给定很多点,判断这些点共线的问题,变为判断霍夫空间中一组曲线(每个点决定一个cos曲线)是否在霍夫空间中相交于同一点

![[Pasted image 20220227165423.png]]

霍夫变换检测圆的原理,与检查直线类似

圆的表达式可表示为 ( x − a ) 2 + ( y − b ) 2 = r 2 (x-a)^2+(y-b)^2=r^2 (xa)2+(yb)2=r2

共有三个参数,因此需要构建 3维 的霍夫空间进行投票

局部特征

很多应用场景下,不需要对图像的全局进行描述就可以进行图像匹配。对图像局部进行特征描述的描述算子成为局部特征

局部特征对遮挡具有一定的鲁棒性

哪些像素的可区分性较强?

亮度平滑变化或近似恒定区域的像素x
边缘上的像素x
角落的像素

角点检测

角点通常是目标轮廓上曲率的局部极大值,对掌握目标轮廓特征具有决定作用

通常认为角点是二维图像亮度变化剧烈的点,或两条线的交叉处

![[Pasted image 20220227183137.png]]

角点检测算法基本思想是使用一个固定窗口(取某个像素的一个邻域窗口)在图像上进行任意方向上的滑动,比较滑动前与滑动后两种情况,窗口中的像素灰度变化程度,如果存在任意方向上的滑动,都有较大灰度变化,那么我们可以认为该窗口中存在角点

你可能感兴趣的:(图像处理,计算机视觉,opencv)