前言
本专题开始研究计算机处理图像问题,在进入本专题前,请各位小伙伴先熟悉一下python的基础知识,安装好opencv的对应模块~
图像的读取
计算机眼中的图像
我们在屏幕上看到的彩色图片,是由很多的彩色像素组成的,每一个彩色的像素点又是由三个深度不同的单颜色的叠加。这三个颜色分别为绿、蓝、红,矩阵值的大小代表该像素点对应颜色的深度(分为256个等级):
读取图片
首先我们将图片和.py文件放在一个路径下,然后用以下代码读取图片:
import cv2
img=cv2.imread('show_cat.jpg')
print(img.shape) # 读取搭配的图像数据维度
print(img.dtype) # 读取到的矩阵存储数据的类型
print(img.size) # 像素点个数(949*1235*3)
print(type(img))
print(img)
我们看一下结果:
这里需要注意一下,这个三维矩阵是计算机认识可以直接识别的样子,但是我们并不能很直观的理解它。实际上,计算机理解这个数组的过程更符合我们竖着看它时所呈现的样子:
然后我们把图片显示出来:
import cv2
img=cv2.imread('show_cat.jpg')
#图像的显示,也可以创建多个窗口
cv2.imshow('image',img) # 创建一个名为image的窗口,展示以图像形式img数据
cv2.waitKey(0) # 参数代表等待时间,单位是毫秒。0表示任意键后运行下一句代码
cv2.destroyAllWindows() # 销毁显示图片
但是很多时候,读取彩色图片是不够的,我们需要得到灰度图片。imread函数给我们提供了一个处理方案:
import cv2
img=cv2.imread('show_cat.jpg',cv2.IMREAD_GRAYSCALE)
cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
就可以得到黑白图像了:
imread的第二参数目前有两种选择,cv2.IMREAD_COLOR代表读取彩色图像,并且是默认选项;cv2.IMREAD_GRAYSCALE代表读取灰度图像。大家可以输出一下此时读取到的img值,观察一下和之前有什么区别。
按照以上的代码,读取图片之后是会自动小会图片的。那么能不能把生成的图片保存下来呢?这就要提到imwrite函数了。在介绍这个函数之前,我们把这几段代码中都用到的相同操作做一个打包,提升代码的可读性:
def cv_show(name,img,save='0'):
cv2.imshow(name, img)
cv2.waitKey(0)
if save=='0': # 不保存
cv2.destroyAllWindows()
else: # 以制定格式和名称保存到当期按路径
name+=('.'+save)
cv2.imwrite(name,img) # 将img保存到当期按路径,name参数指定了文件名称和类型
那么加入我们希望把刚才的灰色猫保存起来,就可以这样写代码:
import cv2
img=cv2.imread('show_cat.jpg',cv2.IMREAD_GRAYSCALE)
cv_show("gray_cat",img,'png')
就可以在文件夹中找到我们保存的黑白图像了:
视频读取
除了上面介绍的图片,视频处理也是图像处理中的内容。我们熟悉的视频其实是一系列图像的连续播放,视频中的每一张图片叫做一帧,展示时间为30ms。因此,对视频的处理就是对每一帧的处理。opencv中也给我们提供了cv2.VideoCapture函数,该函数可以通过接收数字来控制不同的设备,当然也可以读取视频文件。下面我们以一段代码对该函数进行细致讲解:
import cv2
vc = cv2.VideoCapture('test.mp4')
if vc.isOpened(): # 检查是否打开正确
oepn, frame = vc.read()
'''
vc.read()会一帧一帧向后取
open会接收到一个布尔值,如果当前没有超过最后一帧则为True,
frame为当前帧的图像矩阵
大家可以运行以下的注释内容查看结果:
'''
# cv2.imshow('aaa',frame)
# cv2.waitKey(0)
# cv2.destroyAllWindows()
else: # 文件不能打开始将open标记为False
open = False
while open: # 当文件可以打开时进入循环
ret, frame = vc.read()
if frame is None: # 运行过程中没有按下esc退出程序,则当取完最后一帧后自行退出
break
if ret == True:
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow('result', gray)
if cv2.waitKey(10) & 0xFF == 27:
break # 播放完所有帧或中途按下esc键,退出循环
'''
cv2.waitKey(10)代表每一帧画面停留10ms
cv2.waitKey()函数具有返回值,在等待期间如果没有按键,返回-1,否则返回按键的ascII值
0xFF == 27 的含义在于判断是否按下esc键,如果按下esc键则为True
&和and含义相同
'''
vc.release()
cv2.destroyAllWindows()
大家可以找一段视频文件运行试一下。下面我们再详细解释一下
if cv2.waitKey(10) & 0xFF == 27:
这句代码。首先,运行到这个语句时,会自动执行cv2.waitKey(10),没有按键的时候返回值都是-1,对应的布尔值是True。0x代表16进制数,ff换算成二进制为8位全1,代表了取按键的低八位二进制数值。我们来看代码:
while True:
a=int(input('请输入数字:'))
if a & 0xFF==27: # 低八位对应的二进制数为00011011(27)时,都可以进入。例如输入283,539,1051等
print('进入了')
else:
print('没进入')
esc按键对应的低八位ASCII码值刚好是27,这下大家明白为什么按esc可以退出战士了吧?
截取部分图像数据
我们使用imread函数读取到的数据与列表相似,可以使用切片法获得一部分内容。因此,我们可以利用这个特性让计算机给我们展示图像的一部分:
import cv2
img=cv2.imread('show_cat.jpg')
cat=img[800:1000,0:600] # 三个颜色矩阵,每个矩阵取800到1000行,0到600列
cv_show('cat',cat)
大家可能有个疑问,如果我们把代码写成
cat=img[800:1000,0:600,0]
这样会不会展示成绿色的图片呢?并不会,因为我们取到的是一个二维矩阵的话,计算机会默认显示成灰度图片。那么怎么才能展示绿色图片呢?
颜色通道的提取
opencv中给我们提供了一个可以分离三个颜色通道的函数split:
import cv2
img=cv2.imread('show_cat.jpg')
b,g,r=cv2.split(img) # 将三条通道拆开
print('blue:','\n',b,'\n','green:','\n',g,'\n','red:','\n',r)
能拆开就能组合,opencv同样给我们一个将三个颜色组合为一个的函数merge:
import cv2
img=cv2.imread('show_cat.jpg')
b,g,r=cv2.split(img) # 将三条通道拆开
img=cv2.merge((b,g,r)) # 将三个矩阵道组合
print(img)
输出的矩阵就又成了最初时的样子。那么如果我们将三颜色矩阵拆开之后,再将一个矩阵置零,会有什么效果呢:
import cv2
img=cv2.imread('show_cat.jpg')
b,g,r=cv2.split(img) # 将三条通道拆开
g[:,:]=0 # 绿色矩阵置零
img=cv2.merge((b,g,r)) # 将三个矩阵道组合
cv_show('no_g',img,'jpg')
当然我们也可以用相同的方法去掉两个通道只保留一种颜色。也可以试试这样操作:
import cv2
img=cv2.imread('show_cat.jpg')
# 只保留R
cur_img = img.copy() # 拷贝一下原图
cur_img[:,:,0] = 0 # b通道矩阵全设置为0
cur_img[:,:,1] = 0 # g通道矩阵全设置为0
cv_show('R_cat',cur_img,'jpg') # 显示只保留r通道的图片
边界填充
提到卷积,大家都应该反映过来,为什么要对图片进行边界填充了。那么opencv要如何进行对填充呢?
先看代码,随后我们会解释关键参数的含义:
import cv2
import matplotlib.pyplot as plt
img=cv2.imread('show_dog.jpg')
top_size, bottom_size, left_size, right_size = (200, 200, 200, 200) # 上、下、左、右分别填充的像素大小
# 这里我们将图片上下左右均填充入200像素
replicate = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE)
# copyMakeBorder()函数为图片进行边缘填充
reflect = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_WRAP)
constant = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_CONSTANT, value=0)
# 展示填充效果
plt.subplot(231), plt.imshow(img), plt.title('ORIGINAL')
plt.subplot(232), plt.imshow(replicate), plt.title('REPLICATE')
plt.subplot(233), plt.imshow(reflect), plt.title('REFLECT')
plt.subplot(234), plt.imshow(reflect101), plt.title('REFLECT_101')
plt.subplot(235), plt.imshow(wrap), plt.title('WRAP')
plt.subplot(236), plt.imshow(constant), plt.title('CONSTANT')
# subplot()用于给显示窗口分区域
# subplot(234)代表将窗口分为两行三列显示,该图片放在四号(第二行第一列)位置
# title函数为为显示的图片添加标题
plt.show()
下面我们分析一下copyMakeBorder()函数的 borderType参数的含义:
- BORDER_REPLICATE:复制法,将图像最边缘的像素填充到规定填充的区域;
- BORDER_REFLECT:反射法,如同放了一面镜子对原来的图像进行部分反射。效果如同:cba abcdefgh hgf;
- BORDER_REFLECT_101:这也是反射法,只不过将反射轴设置为原图像的最边缘像素,效果如同:dcb abcdefgh gfe;
- BORDER_WRAP:外包装法,可以理解为循环填充:fgh abcdefgh abc;
- BORDER_CONSTANT:常量法,以常量值进行填充。
代码运行结果如图:
数值计算
某些时候我们需要对像素矩阵进行进行集体的加减值运算。先看代码:
import cv2
img_dog=cv2.imread('show_dog.jpg')
img_dog2= img_dog - 10 # 将dog图片的矩阵每个元素都加10
print(img_dog[100:103,100:103])
print(img_dog2[100:103,100:103])
当然,我们也可以对两个维度相同的矩阵进行加法:
print((img_dog+img_dog2)[100:103,100:103])
需要注意的是,如果两个矩阵加起来某个元素超过了255,计算机会对该数值进行除256取余的操作,使结果落到0到255之间:
此外,我们还可以用add函数进行两个像素矩阵相加的操作。add函数的返回值是完成相加的新矩阵,不过这种方式加出来的矩阵如果某个元素大于255,那么计算机将会记录该结果为255:
print((cv2.add(img_dog,img_dog2)[100:103,100:103]))
如果我们把相加的结果用图片展示出来,就会发现亮度提高了非常多:
图像融合
既然我们知道了两个像素矩阵可以相加,那么自然也会向如果是两个图片的像素矩阵相加会有什么效果。下面我们要探讨的就是这个问题:
首先我们必须确保索要相加的两个图片维度相同,如果不同的我们需要手动调整一下:
import cv2
img_cat=cv2.imread('show_cat.jpg')
img_dog=cv2.imread('show_dog.jpg')
h,w,c=img_dog.shape # 获取一张图片的维度
img_cat = cv2.resize(img_cat, (w,h)) # 调整另一张图片大小至适维
# resize函数接收矩阵维度要求先w后h
res = cv2.addWeighted(img_cat, 0.6, img_dog, 0.4, 0) # 提供融合权重和偏执项
cv_show('res',res)
cv2.addWeighted(img_cat, 0.6, img_dog, 0.4, 0)
这句代码表示合成矩阵中,猫的权重占0.6,狗的权重占0.4,0代表为合成的图片提亮0个单位。设x代表猫的图像矩阵,y代表狗的图像矩阵,则res=αx+βy+b,α=0.6,β=0.4,b=0。
图像缩放
resize函数可以调整图片到指定维度,实际上就是将原图片放大或缩小到我们需要的维度。这个函数还有两个比较常用的参数,以对图片进行适当的放缩:
import cv2
import matplotlib.pyplot as plt
img_cat=cv2.imread('show_cat.jpg')
img_dog=cv2.imread('show_dog.jpg')
h,w,c=img_dog.shape # 获取一张图片的维度
img_cat1 = cv2.resize(img_cat, (w,h))
img_cat2 = cv2.resize(img_cat, (w,h),fx=2,fy=1) # 错误操作,不能同时进行两种方式的放缩
img_cat3 = cv2.resize(img_cat, (0,0),fx=1.5,fy=1)
img_cat4 = cv2.resize(img_cat, (0,0),fx=1,fy=1.5)
plt.subplot(221), plt.imshow(img_cat1), plt.title('img_cat1')
plt.subplot(222), plt.imshow(img_cat2), plt.title('img_cat2')
plt.subplot(223), plt.imshow(img_cat3), plt.title('img_cat3')
plt.subplot(224), plt.imshow(img_cat4), plt.title('img_cat4')
plt.show()
我们先看一下运行的结果:
不知道大家有没有发现结果中颜色方面的一点小问题。这是由于opencv读取像素矩阵是按照bgr读取的,而matplotlab里绘图函数是按照rgb复原的。我们可以进行颜色通道拆开—排序—组合的方式,以消除显示上的问题。