图像处理
2021年11月28日
8:50
记的是我自己不太熟悉的一些操作,很多都是文档上的,偶尔有自己的一些理解。
PIL库
基本操作
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
path = "baidu_bear.png" # 图片路径
image = Image.open(path)
plt.imshow(image)
# 输出的时候可加参数
plt.imshow(img,'gray')
# 将图片转为矩阵表示
np.array(mnist[0][0])
# 将矩阵保存成文本,数字格式为整数
np.savetxt('7.txt', np.array(mnist[0][0]), fmt='%4d')
# 使用PIL分离颜色通道
r,g,b = img.split()
# 这里也是三个通道,0,1,2
img.getchannel(0)
# PIL库的crop函数做简单的图片裁剪
r.crop((100,100,128,128))
# 将图片第一行的像素点逐个取出
np.array(img)[0]
#1*4的第一张图
plt.subplot(1, 4, 1)
plt.imshow(RGB_Image,'gray')
plt.title('RGB_Image')#加标题
CV库
cv2.IMREAD_COLOR:彩色图,默认值(1)
cv2.IMREAD_GRAYSCALE:灰度图(0)
cv2.IMREAD_UNCHANGED:包含透明通道的彩色图(-1)
Cv2路径中不能有中文
# 将彩色图的BGR通道顺序转成RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 将彩色图的BGR通道直接转为灰度图
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
OpenCV中彩色图是以B-G-R通道顺序存储的,灰度图只有一个通道。
# 查看图片的形状 cv库
img.shape
# 加载灰度图
img = cv2.imread('lena.jpg', 0)
# 显示这张灰度图
plt.imshow(img,'gray')
保持图片
cv2.imwrite('lena-grey.jpg',img)
# 加载四通道图片
img = cv2.imread('cat.png',-1)
# 将彩色图的BGR通道顺序转成RGB,注意,在这一步直接丢掉了alpha通道
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
# 默认三通道图片
img = cv2.imread('cat.png', 1)
# 转颜色通道为RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img)
Cv2截取
face = img[0:740, 400:1000]
plt.imshow(face)
# 通道合并与分割
HSV主要就是颜色处理
hsv提取颜色
缩放、平移、翻转、绘图
缩放图片
缩放就是调整图片的大小,使用cv2.resize()函数实现缩放。可以按照比例缩放,也可以按照指定的大小缩放: 我们也可以指定缩放方法interpolation,更专业点叫插值方法,默认是INTER_LINEAR,全部可以参考:InterpolationFlags
缩放过程中有五种插值方式:
cv2.INTER_NEAREST 最近邻插值
cv2.INTER_LINEAR 线性插值
cv2.INTER_AREA 基于局部像素的重采样,区域插值
cv2.INTER_CUBIC 基于邻域4x4像素的三次插值
cv2.INTER_LANCZOS4 基于8x8像素邻域的Lanczos插值
来自
平移图片
要平移图片,我们需要定义下面这样一个矩阵,tx,ty是向x和y方向平移的距离:
M = [[1, 0, tx], [0, 1, ty]]
平移是用仿射变换函数cv2.warpAffine()实现的:
绘图功能
绘制各种几何形状、添加文字
OpenCV函数:cv2.line(), cv2.circle(), cv2.rectangle(), cv2.ellipse(), cv2.putText()
绘制形状的函数有一些共同的参数,提前在此说明一下:
img:要绘制形状的图片
color:绘制的颜色
彩色图就传入BGR的一组值,如蓝色就是(255,0,0)
灰度图,传入一个灰度值就行
thickness:线宽,默认为1;对于矩形/圆之类的封闭形状而言,传入-1表示填充形状
lineType:线的类型。默认情况下,它是8连接的。cv2.LINE_AA 是适合曲线的抗锯齿线。
(线宽-1就是填充)
添加文字
使用cv2.putText()添加文字,它的参数也比较多,同样请对照后面的代码理解这几个参数:
参数2:要添加的文本
参数3:文字的起始坐标(左下角为起点)
参数4:字体
参数5:文字大小(缩放比例)
加入中文
小结
cv2.line()画直线,cv2.circle()画圆,cv2.rectangle()画矩形,cv2.ellipse()画椭圆,cv2.polylines()画多边形,cv2.putText()添加文字。
画多条直线时,cv2.polylines()要比cv2.line()高效很多。
要在图像中打上中文,可以用PIL库结合OpenCV实现。
图像间数学运算
图片间的数学运算,如相加、按位运算等
OpenCV函数:cv2.add(), cv2.addWeighted(), cv2.bitwise_and()
图片相加
要叠加两张图片,可以用cv2.add()函数,相加两幅图片的形状(高度/宽度/通道数)必须相同。
cv2.add(x, y)
图像混合
图像混合cv2.addWeighted()也是一种图片相加的操作,只不过两幅图片的权重不一样,γ相当于一个修正值:
阈值处理图片
固定阈值分割
固定阈值分割很直接,一句话说就是像素点值大于阈值变成一类值,小于阈值变成另一类值。
cv2.threshold()用来实现阈值分割,ret是return value缩写,代表当前的阈值。函数有4个参数:
参数1:要处理的原图,一般是灰度图
参数2:设定的阈值
参数3:最大阈值,一般为255
参数4:阈值的方式,主要有5种,详情:ThresholdTypes
0: THRESH_BINARY 当前点值大于阈值时,取Maxval,也就是第四个参数,否则设置为0
1: THRESH_BINARY_INV 当前点值大于阈值时,设置为0,否则设置为Maxval
2: THRESH_TRUNC 当前点值大于阈值时,设置为阈值,否则不改变
3: THRESH_TOZERO 当前点值大于阈值时,不改变,否则设置为0
4:THRESH_TOZERO_INV 当前点值大于阈值时,设置为0,否则不改变
阈值分割五种案例
自适应阈值
看得出来固定阈值是在整幅图片上应用一个阈值进行分割,它并不适用于明暗分布不均的图片。 cv2.adaptiveThreshold()自适应阈值会每次取图片的一小部分计算阈值,这样图片不同区域的阈值就不尽相同。它有5个参数,其实很好理解,先看下效果:
参数1:要处理的原图
参数2:最大阈值,一般为255
参数3:小区域阈值的计算方式
ADAPTIVE_THRESH_MEAN_C:小区域内取均值
ADAPTIVE_THRESH_GAUSSIAN_C:小区域内加权求和,权重是个高斯核
参数4:阈值方式(跟前面讲的那5种相同)
参数5:小区域的面积,如11就是11*11的小块
参数6:最终阈值等于小区域计算出的阈值再减去此值
建议读者调整下参数看看不同的结果。
几种滤波
均值滤波
均值滤波是一种最简单的滤波处理,它取的是卷积核区域内元素的均值,用cv2.blur()实现,如3×3的卷积核:
img = cv2.imread('lena.jpg')
blur = cv2.blur(img, (3, 3)) # 均值模糊
方框滤波
方框滤波跟均值滤波很像,如3×3的滤波核如下:
用cv2.boxFilter()函数实现,当可选参数normalize为True的时候,方框滤波就是均值滤波,上式中的a就等于1/9;normalize为False的时候,a=1,相当于求区域内的像素和。
blur = cv2.boxFilter(img, -1, (9, 9), normalize=True)
高斯滤波
前面两种滤波方式,卷积核内的每个值都一样,也就是说图像区域中每个像素的权重也就一样。高斯滤波的卷积核权重并不相同:中间像素点权重最高,越远离中心的像素权重越小。
显然这种处理元素间权值的方式更加合理一些。图像是2维的,所以我们需要使用2维的高斯函数,比如OpenCV中默认的3×3的高斯卷积核:
OpenCV中对应函数为cv2.GaussianBlur(src,ksize,sigmaX): 参数3 σx值越大,模糊效果越明显。高斯滤波相比均值滤波效率要慢,但可以有效消除高斯噪声,能保留更多的图像细节,所以经常被称为最有用的滤波器。均值滤波与高斯滤波的对比结果如下(均值滤波丢失的细节更多)
gaussian = cv2.GaussianBlur(img, (9, 9), 1) # 高斯滤波
中值滤波
中值又叫中位数,是所有数排序后取中间的值。中值滤波就是用区域内的中值来代替本像素值,所以那种孤立的斑点,如0或255很容易消除掉,适用于去除椒盐噪声和斑点噪声。中值是一种非线性操作,效率相比前面几种线性滤波要慢。
median = cv2.medianBlur(img, 9) # 中值滤波
双边滤波
模糊操作基本都会损失掉图像细节信息,尤其前面介绍的线性滤波器,图像的边缘信息很难保留下来。然而,边缘(edge)信息是图像中很重要的一个特征,所以这才有了双边滤波。用cv2.bilateralFilter()函数实现:可以看到,双边滤波明显保留了更多边缘信息。
blur = cv2.bilateralFilter(img, 9, 75, 75) # 双边滤波
边缘检测
cv2.Canny()进行边缘检测,参数2、3表示最低、高阈值,下面来解释下具体原理。
Canny边缘检测
Canny边缘提取的具体步骤如下:
使用5×5高斯滤波消除噪声:
边缘检测本身属于锐化操作,对噪点比较敏感,所以需要进行平滑处理。
计算图像梯度的方向:
首先使用Sobel算子计算两个方向上的梯度$ G_x 和和和 G_y $,然后算出梯度的方向:
保留这四个方向的梯度:0°/45°/90°/135°,有什么用呢?我们接着看。
取局部极大值:
梯度其实已经表示了轮廓,但为了进一步筛选,可以在上面的四个角度方向上再取局部极大值
滞后阈值:
经过前面三步,就只剩下0和可能的边缘梯度值了,为了最终确定下来,需要设定高低阈值:
像素点的值大于最高阈值,那肯定是边缘
同理像素值小于最低阈值,那肯定不是边缘
像素值介于两者之间,如果与高于最高阈值的点连接,也算边缘,所以上图中C算,B不算
Canny推荐的高低阈值比在2:1到3:1之间。
腐蚀与膨胀
了解形态学操作的概念
学习膨胀、腐蚀、开运算和闭运算等形态学操作
OpenCV函数:cv2.erode(), cv2.dilate(), cv2.morphologyEx()
啥叫形态学操作
形态学操作其实就是改变物体的形状,比如腐蚀就是"变瘦",膨胀就是"变胖"。
腐蚀
腐蚀的效果是把图片"变瘦",其原理是在原图的小区域内取局部最小值。因为是二值化图,只有0和255,所以小区域内有一个是0该像素点就为0。
这样原图中边缘地方就会变成0,达到了瘦身目的
OpenCV中用cv2.erode()函数进行腐蚀,只需要指定核的大小就行:
这个核也叫结构元素,因为形态学操作其实也是应用卷积来实现的。结构元素可以是矩形/椭圆/十字形,可以用cv2.getStructuringElement()来生成不同形状的结构元素,比如:
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) # 矩形结构kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) # 椭圆结构kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5)) # 十字形结构
膨胀
膨胀与腐蚀相反,取的是局部最大值,效果是把图片"变胖":
开/闭运算
先腐蚀后膨胀叫开运算(因为先腐蚀会分开物体,这样容易记住),其作用是:分离物体,消除小区域。这类形态学操作用cv2.morphologyEx()函数实现:
闭运算则相反:先膨胀后腐蚀(先膨胀会使白色的部分扩张,以至于消除/"闭合"物体里面的小黑洞,所以叫闭运算)
如果我们的目标物体外面有很多无关的小区域,就用开运算去除掉;如果物体内部有很多小黑洞,就用闭运算填充掉。
使用OpenCV摄像头与加载视频
OpenCV函数:cv2.VideoCapture(), cv2.VideoWriter()
打开摄像头
要使用摄像头,需要使用cv2.VideoCapture(0)创建VideoCapture对象,参数0指的是摄像头的编号,如果你电脑上有两个摄像头的话,访问第2个摄像头就可以传入1,依此类推。
另外,通过cap.get(propId)可以获取摄像头的一些属性,比如捕获的分辨率,亮度和对比度等。propId是从0~18的数字,代表不同的属性,完整的属性列表可以参考:VideoCaptureProperties。也可以使用cap.set(propId,value)来修改属性值。比如说,我们在while之前添加下面的代码:
播放本地视频
跟打开摄像头一样,如果把摄像头的编号换成视频的路径就可以播放本地视频了。回想一下cv2.waitKey(),它的参数表示暂停时间,所以这个值越大,视频播放速度越慢,反之,播放速度越快,通常设置为25或30。
录制视频
之前我们保存图片用的是cv2.imwrite(),要保存视频,我们需要创建一个VideoWriter的对象,需要给它传入四个参数:
输出的文件名,如'output.avi'
编码方式FourCC码
帧率FPS
要保存的分辨率大小
FourCC是用来指定视频编码方式的四字节码,所有的编码可参考Video Codecs。如MJPG编码可以这样写: cv2.VideoWriter_fourcc(*'MJPG')或cv2.VideoWriter_fourcc('M','J','P','G')