文档中涉及到的opencv版本为:opencv-python 4.5.1.48
安装之后我们再安装matplotlib与numpy,这两个是配合opencv使用的库,我们会使用其中的几个功能,之后我们导入这三个库
%matplotlib inline 是在jupyter notebook 中独有的用法,在下面课程中我不做使用,下面所介绍的每一个方法都是导入了这三个库之后再输入相应代码的结果
我们在查看cv2库中的函数时会经常看到两种参数
目录
1 读取图像 imread()
1.1 图像array的读取方式
1.1.1 读取1*1像素的图片
1.1.2 读取2*1像素的图像
1.1.3 读取2*2像素的图像
2 opencv使用窗口显示图像 imshow()
3 查看图像 shape
4 将图片转化为灰度图 imread(cv2.IMREAD_GRAYSCALE)
5 图像保存 imwrite()
6 显示像素点个数 size
7 显示数据类型 dtype
8 截取部分图像 img[h,w]
9 颜色通道提取 split()与merge()
9.1 R(只保留红色)
9.2 G(只保留绿色通道)
9.3 B(只保留蓝色通道)
10 边界扩充 copyMakeBorder()
11 数值计算
11.1 加号运算
11.2 add运算 add()
12 图像融合 addWeighted()
13 图像拉伸 resize()
14 阈值处理 threshold(sec,thresh,maxval,type)
15 图像平滑
15.1 均值滤波 blur()
15.2 方框滤波 boxFilter()
15.2.1 做归一化
15.2.2 不做归一化
15.3 高斯滤波 GaussianBlur()
15.4 中值滤波 medianBlur()
16 腐蚀操作 erode()
16.1 第一个例子
16.2 第二个例子
17 膨胀操作 dilate()
18 开运算与闭运算
18.1 开运算 morphologyEx(cv2.MORPH_OPEN)
18.2 闭运算 morphologyEx(cv2.MORPH_CLOSE)
19 梯度运算 morphologyEx(cv2.MORPH_GRADIENT)
20 礼帽与黑帽
20.1 礼帽 morphologyEx(cv2.MORPH_TOPHAT)
20.2 黑帽 morphologyEx(cv2.MORPH_BLACKHAT)
21 图像复制 copy()
22 图像翻转 flip()
22.1 上下翻
22.2 左右翻
22.3 上下翻加左右翻
23 图像逆时针旋转90度 transpose()
24 图像任意角度旋转 getRotationMatrix2D()与warpAffine()
25 图像画矩形框 rectangle()
26 图像写文字 putText()
27 图像与运算 bitwise_and()
28 图像或运算 bitwise_or()
29 图像非运算 bitwise_not()
30 图像异或运算 bitwise_xor()
31 图像画圈 circle()
32 图像绘制有颜色填充的多边形 fillConvexPoly()与fillPoly()
32.1 fillConvexPoly()
32.2 fillPoly()
33 图像上绘制一条线 line()
首先我要在路径下有这样一个图片文件,我的图像文件位test.png
读取图像,然后打印出来看一下
我们此时查看一下变量类型
img的变量类型为numpy.ndarray
opencv中的array是这样读的,现在我们把原图搞为1*1像素的照片,这个改变图像大小的方法到后面会提到
三个中括号内的值分别为1*1像素的R,G,B三个值
我们现在再将图像变为2*1
这一次显示的结果就在两个中括号中,但他们还是同一行,也就是说第一行第一个的RGB值是[232,230,230],第二个RGB值是[231,229,229],由于是同一行,所以没进入第二个中括号
现在我们改成2*2像素的图片
我们可以从中得出一个结论
我们最内的中括号是我们单一像素点的BGR的三个值,第二个中括号是一行内的所有像素点值,第三个中括号是我们的整个图
其中第三行的waitKey后的参数0为无限等待用户按键,按下任意键终止,如改为1000,则该窗口显示1000ms后
运行结果
我们可以定义下面这个彩色显示图像函数,name为窗口名称,img为图片路径
def cv_show(name,img):
img = cv2.imread(img)
cv2.imshow(name,img)
cv2.waitKey(0)
cv2.destroyAllWindows()
使用方法
cv_show('img','test.png')
元组中的三个值含义分别为图像高为456像素,宽为823像素,图像有BGR三通道
我之前遇到一个问题,由于shape的位置不对,所以最终无法显示出来
下面这张图的第一行是无法显示的shape,我使用了numpy中的traspose的方法,把新shape中的第0位的值置为老shape中第1位的值,新第1位的值置为老第二位的值,新第2位的值置为老第0位的值
这样就能改变shape以达到我们的要求
箭头处的参数可以换为
查看图像的shape可以看出图像大小不变,通道转化为单通道
这个其实就是shape元组中三个值的乘积,我们把shape拿出来看一下
1125864 = 456 * 823 * 3
我们截取图像高的0-300,宽的0-200部分
首先读取图片,然后把三个通道拆分出来,分别赋值给b,g,r三个变量
我们分别看一下b,g,r以及他们的shape
我们可以使用merge把分离的bgr三个矩阵合到一起
可以看出合成的图像具有三个通道
我们如果要单独显示BGR可以这样搞
我们让其余两个通道的所有值赋值为0
我们将B通道与G通道所有值置为0
我们将B通道与R通道所有值置为0
将G和R通道的值置为0
我之前项目中遇到三通道颜色读取有问题的,最终使用的是拆分然后和一的方法解决的
10.边界填充中介绍的方法为copyMakeBorder涉及到的int含义分别为上边距,下边距,左边距,右边距
参数borderType有5个可选参数
读取图片之后使用不同的扩充方式
将以上5个变量绘制出来
读一张图后打印原有的img和+50后的img
加之前
加之后
图像做加法时,如果加入的值超过255,则该值减去256为结果数值
我们现在看一下+50后的img变成了什么样子
由于好多像素点已经超过了255,所以我们加和之后会变小,当一个值越小时会变得越黑,越大就会变得越白,0是完全的黑,255是完全的白
我们现在换一种方式加和
从第一行的值我们不太能看出来有什么区别,我们现在直接看一下这张图
很明显是与上面的结果有不同,这样我们得出一个结论,如果使用cv2.add则不会减去256,当有值超过255时,就将超出值置为255
读取两张图片
test1
test2
首先我们将一张图片与另一张图片大小搞成一样的
这里我们先看一眼img的shape
之后我们resize另一张,这里我们注意,resize的参数和shape的数值是相反的
之后融合
这里有一个公式,我直接以上面这个代码举例
img 0.4 + img1 0.6 + 0
img占0.4,img1占0.6,最后的0是一个常数
原图
横向拉伸2倍,纵向不变
从这里看好像纵向变短了,实际上我用截图软件量了一下,确实没变
只有在第二个参数为0的时候,后面输入fx,fy才有效
也可以直接使用resize调整大小
如果我们当前机器的显示设置不为100%,我们截图时看到的大小会与resize的大小有区别
这个方法的参数如下
这个方法有两个返回值,第一个返回值为阈值,第二个返回值为阈值操作后的图像
我们把这五个type都用用一遍,然后展示出来
减轻图像中的噪点
下面我们使用布尔滤波,然后把图片展示出来
第一个参数为要滤波的图片,第二个参数为执行滤波运算的矩阵范围(卷积核)
我们当前设置的范围为3*3
比如我们当前的像素点为1-9
像 | 素 | 点 |
---|---|---|
1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 |
通过3*3矩阵,我们将范围内的平均值代替所有值,运行后的结果是下面这样的
像 | 素 | 点 |
---|---|---|
(1+2+4+5)/4=3 | (1+2+3+4+5+6)/6=3.5 | (2+3+5+6)/4=4 |
(1+2+4+5+7+8)/6=4.5 | (1+2+3+4+5+6+7+8+9)/9=5 | (2+3+5+6+8+9)/6=5.5 |
(4+5+7+8)/4=6 | (4+5+6+7+8+9)/6=6.5 | (5+6+8+9)/4=7 |
我们得出来的结果是这样的,如果交给计算机运算它会取浮点数的整形部分,有时进有时舍,与实际结果不会相差1的值
像 | 素 | 点 |
---|---|---|
3 | 3.5 | 4 |
4.5 | 5 | 5.5 |
6 | 6.5 | 7 |
下面涉及到原理例子了,我们直接在jupyter notebook中运行,导入库后读取灰度图片
转换之后我们取横向的三个像素点与纵向的三个像素点
我们拿到矩阵之后先自己计算一下结果
结 | 果 | |
---|---|---|
(84+84+80+79)/4=81.75 | (84+84+83+80+79+79)/6=81.5 | (84+83+79+79)/4=81.25 |
(84+84+80+79+76+76)/6=79.833 | (84+84+83+80+79+79+76+76+76)/9=79.666 | (84+83+79+79+76+76)/6=79.5 |
(80+79+76+76)/4=77.75 | (80+79+79+76+76+76)/6=77.66 | (79+79+76+76)=77.5 |
我们现在只显示计算出来的结果
结 | 果 | |
---|---|---|
81.75 | 81.5 | 81.25 |
79.833 | 79.666 | 79.5 |
77.75 | 77.66 | 77.5 |
现在我们使用布尔滤波看一下结果
发现于我们运算的结果相差不过1
为了避免巧合我们计算本张图的另外一个5*5的矩阵
同样我们使用方法之前先自己计算一下
结 | 果 | |||
---|---|---|---|---|
(77+76+79+78)/4=77.5 | (77+76+76+79+78+78)/6=77.333 | (76+76+78+78+78+81)/6=77.83 | (76+78+78+78+81+81)/6=78.667 | (78+78+81+81)/4=79.5 |
(77+78+79+78+83+82)/6=79.5 | (77+76+76+79+78+78+83+82+81)/9=78.88 | (76+76+78+78+78+81+82+81+83)/9=79.222 | (76+78+78+78+81+81+81+83+83)/9=79.8889 | (78+78+81+81+83+83)/6=80.667 |
(79+78+83+82+89+89)/6=83.333 | (79+78+78+83+82+81+89+89+88)/9=83 | (78+78+81+82+81+83+89+88+89)/9=83.2223 | (78+81+81+81+83+83+88+89+89)/9=83.667 | (81+81+83+83+89+89)/6=84.333 |
(83+82+89+89+92+92)/6=87.833 | (83+82+81+89+89+88+92+92+91)/9=87.44 | (82+81+83+89+88+89+92+91+91)/9=87.333 | (81+83+83+88+89+89+91+91+91)/9=87.333 | (83+83+89+89+91+91)/6=87.667 |
(89+89+92+92)/4=90.5 | (89+89+88+92+92+91)/6=90.1667 | (89+88+89+92+91+91)/6=90 | (88+89+89+91+91+91)/6=89.8333 | (89+89+91+91)/4=90 |
我们整理一下只保留结果
结 | 果 | |||
---|---|---|---|---|
77.5 | 77.333 | 77.83 | 78.667 | 79.5 |
79.5 | 78.88 | 79.222 | 79.8889 | 80.667 |
83.333 | 83 | 83.2223 | 83.667 | 84.333 |
87.833 | 87.44 | 87.333 | 87.333 | 87.667 |
90.5 | 90.1667 | 90 | 89.8333 | 90 |
现在我们看一下布尔滤波后的结果
如果卷积核为两个偶数则没有中心点,我也不知道是怎么计算的,使用偶数卷积核滤波的效果并不好,实际中也很少有人使用,我们下面几种滤波方式也都不考虑偶数卷积核的情况
方框滤波于均值滤波的算法相同
有两个新增的参数,-1表示和原始图片的通道一致,normalize如果为True则执行归一化,如果为False则不执行
之后我们已然使用灰度图分析做归一化的方框滤波是如何运算的
我们可以发现做归一化的方框滤波与均值滤波的计算结果相同,我们在网上查阅一些资料,发现归一化方框滤波与均值滤波的计算方式相同
不做归一化后,将卷积核内的值加在一起会导致越界,越界后使用255替代卷积核内的值,所以上面这个图大部分为白色
我们可以通过改变卷积核大小让这个图能大概看出来是什么东西
接下来我们来探究不做归一化是如何搞的
我们结合上面的图(归一化与不归一化的核的区别)不难发现,最终的结果是这样得来的
结 | 果 | |
---|---|---|
84+84+80+79 | 84+84+83+80+79+79 | 84+83+79+79 |
84+84+80+79+76+76 | 84+84+83+80+79+76+76+76 | 84+83+79+79+76+76 |
80+79+76+76 | 80+79+79+76+76+76 | 79+79+76+76 |
我就不对结果进行运算了,结果全都大于255,当大于255时,不归一化的方框滤波将值置为255,像我们减小卷积核方框滤波的图片能看出来图的大概的原因时,减小卷积核后,部分像素点加和小于255,所以会产生颜色的区别
它的计算方式大概是这样的
它会根据高斯函数给卷积核内的每个值一个权重,卷积核内的中值就是1,距离中值越近的权重就越高,反之则越低,然后它把每个值和权重乘一下,然后加一下,然后把最终的值给该像素点
中间的204是高斯函数中间的峰值对应y值为1,y值就个每个像素的权重,比如说我有个点为24,它的权重对应就是x,我们再找一个比204更大的数235,它这个点对应的y值就是235的权重
这个函数服从一维高斯分布,这个计算起来就比较麻烦了,我就不做计算了
最后我们再说一下必选的三个参数
用中值代替卷积核中心的像素值
中智滤波顾名思义就是使用核内的中间值作为该像素点的值,由于有可能是偶数个值,所以核内中值有可能是浮点数,计算机运算出来的结果与实际运算结果相差不会过1
我们不考虑边界问题,只对中心的79做出验证,上面九个值按顺序排列为
76 76 76 79 79 80 83 83 84
中值为79
我们现在把所有的图像展示一下
我们为了更好体验效果换一张图片
是这样的一个图,一个数字2,在数字2周围有几根触须
然后我们进行腐蚀操作,首先定义一个5*5,核内数值全部为1的核,然后使用erode方法,使用kernel对img进行腐蚀,迭代一次
现在我们展示出来
上面这个图是迭代一次后的效果,旁边的须子明显细了很多,现在我们迭代5次看一下
可以看到触须已经全部消失,并且我们还发现一个事儿,我们2的宽度也明显减小了
我们现在换成一张这个图,黑色背景中有一个白色的圆,我们命名其为circle
现在我们对这张图进行50次迭代的腐蚀操作
这两个例子很好表现了腐蚀的作用,在核内将少数像素值变为多数像素值
我们对之前猫的图片腐蚀五次
膨胀操作可以看作腐蚀操作的逆操作
我们先以刚才的2距离,这个是它没膨胀之前的样子
现在我对这张图膨胀5次
这个是它膨胀后的样子
我们再使用刚刚的圆做例子,这个是它没膨胀前的样子
现在我对其做5次膨胀
左侧已经贴到了边上,如果迭代次数更多会更明显
我们最后再对之前猫的图片做膨胀
这个是膨胀了五次的效果
先腐蚀,再膨胀,如果不设置迭代器默认进行一轮腐蚀膨胀,现在我们设置为5轮
先膨胀再腐蚀,闭运算与开运算调用的方法相同,区别为换了一个参数
梯度运算实际上是膨胀-腐蚀,目的是找出我们这张图的轮廓
我们分别对圆形的图进行腐蚀与膨胀,然后我们使用膨胀-腐蚀,然后把这三个图放到一起
使用opencv中的morphologyEx方法可以替代上述功能
参数改为cv2.MORPH_GRADIENT
与上面得到的结果相同
我们再对之前猫的图片做梯度运算,看看能不能显示出来轮廓
发现可以显示大致轮廓
礼貌=原始输入-开运算结果
图片中只有外面的那些触须
闭运算-原始输入
这个原理上应该会有断断续续的2的轮廓,是我将图片resize的问题,我们把resize取消
这个和直接赋值是不一样的,比如如果我们使用img2 = img,此时img2和img的地址就相同了,我们之后施加在img2上的操作也同样会施加在img上而复制就可以避免这个问题,是img2与img独立存在
分三种,一种上下翻,一种左右翻,一种上下翻加左右翻
也叫图像转置
这个迭代多次使用没有用,只有两种状态,一种是上面这种,一种是原图
我们可以使用tranpose()配合上面的flip()这样可以达到我们以90度为单位的旋转
如果是以90度为单位旋转我建议使用上面的方法,因为使用这个方法一定会有黑色填充到图片其他的区域,因为我们的窗口不是斜的
getRotationMatrix2D的参数
warpAffine的参数
rectangle的参数
putText参数
字体还可以选择下面这些值
他们都写不了中文,如果要写中文需要用其他的库来写
我下面做个例子,首先我们要有一个这样的字体文件放在代码的同级目录下
字体文件下载地址
链接:百度网盘 请输入提取码 提取码:jsst
是两张图像的每个像素点转换为二进制后对每一位进行与运算,然后再转换为十进制
我们举几个例子
1010100 = 84
1010000 = 80
1010000 = 80
1010100 = 84
1001111 = 79
1000100 = 64
与上面的方法相似,不同为 或运算:有1为1,全0为0
我们同样举两个例子
1010100 = 84
1010000 = 80
1010100 = 84
1010100 = 84
1001111 = 79
1011111 = 95
这个只有一个参数,参数为要操作的图像
结果为 255-该点的像素值
异或运算:相同为0,不同为1
我们举两个例子
1010100 = 84
1010000 = 80
0000100 = 4
1010100 = 84
1001111 = 79
0011011 = 27
参数
有两种方法,分别是fillConvexPoly()与fillPoly()
参数
参数
参数