第一节、初识OpenCV3-图像的读、写、显、格式转化等
之前一直在看深度学习,用到了图像处理的知识,所以过来补充一下OpenCV基础。
就顺便从网上了买了一本《OpenCV 3计算机视觉》这本书,这本书比较薄,但是目前已经够我用了,在这里就记录一下我的学习笔记。
一 、OpenCV3安装
1.1 Open CV3安装
在前面我已经介绍过我的python运行环境,是运行在windos 7操作系统下,安装的Anaconda集成开发环境。具体安装步骤可以参考文章《第一节,windows下深度学习theano环境搭建》。
下面介绍一下如何在Anaconda命令窗口下安装opencv:
conda install -c https://conda.binstar.org/menpo opencv
打开Anaconda Prompt,输入python,进入python环境之后,import cv2。如果不报错,说明安装成功。
1.2 内容大纲
本节主要介绍以下7块内容:
- 读写图像文件;
- 图像与原始字节之间的转换;
- 使用numpy.array访问图像数据;
- 视频文件的读写(复制视频);
- 捕获摄像头的帧,并保存视频文件;
- 窗口显示图像;
- 在窗口显示摄像头帧;
为了做测试,我专门创建了一个OpenCV文件夹,在下面放一些测试代码,并创建了两个文件夹,video和image分别保存一些视频和图像文件。
二、基础API
2.1 读写图像文件
OpenCV的imread()和imwrite()函数能支持各种静态图像文件格式。不同系统支持的文件格式不一样,但是都支持bmp格式,通常还支持 png,jpeg,tiff格式文件 :
- bmp格式:每个像素每个通道为8位;
- png:每个像素每个通道为8位或者16位。
2.1.1 读入图像
读取图像函数原型:
cv2.imread(filename[,flags])
该函数返回我们读取的图像数据:
- 第一个参数,这幅图像应该在此程序的工作路径,或者给函数提供完整路径;
- 第二个参数是标志位,要告诉函数应该如何读取这幅图片,即指定加载图片的颜色类型,默认加载类型是cv2.IMREAD_COLOR:
- cv2.IMREAD_COLOR=1:读入一副彩色图像,将图像转化为三通道BGR彩色图像。图像的透明度会被忽略,这是默认参数;
- cv2.IMREAD_GRAYSCALE=0:将加载的图像转换为单通道灰度图;
- cv2.IMREAD_UNCHANGED = -1: 已经废除,不再使用;
- cv2.IMREAD_ANYDEPTH=2: 若载入图像深度为16位或32为就返回其对应深度,否则将图像转换为8位图像;
- cv2.IMREAD_ANYCOLOR=4: 保持图像原格式,可以读取任意可能的彩色格式;
- cv2.IMREAD_LOAD_GDAL=8: 使用文件格式驱动加载图像,在现阶段用处不多。
在使用flags时可能会同时使用多种flags,如果发生冲突,函数将自动采用较小数字值对应的加载方式。如:cv2.IMREAD_COLOR | cv2.IMREAD_ANYCOLOR,则imread()函数将自动载入cv2.IMREAD_COLOR所对应的3通道彩色图。如果要载入图像原本的彩色格式和深度,则可以使用: cv2.IMREAD_ANYCOLOR | cv2.IMREAD_ANYDEPTH。
也可以利用flags是int类型的变量输入其他值以达到加载特定图像格式的目的,但符合一下标准:
- flags > 0:返回一个三通道的彩色图像;
- flags = 0: 返回灰度图像 ;
- flags < 0: 返回包含Alpha通道的图像。
图像在默认情况下不是从Alpha通道进来的,如果需要载入Alpha通道的话就取负值。
2.1.2 保存图像
保存图像函数原型:
cv2.imwrite(filename,img[,params])
该函数用于保存图像:
- 第一个参数,这幅图像应该保存的工作路径,或者给函数提供完整路径;
- 第二个参数是输入图像;
- 第三个参数表示为特定保存格式的参数编码,在一般情况下不需要更改;
2.1.3 格式转换
格式转换函数原型:
cv2.cvtColor(src,code[,dst[,dstCn]])
输出为输入图像进行颜色空间变换的图像。我们生活中大多数看到的彩色图片都是BGR类型,但是在进行图像处理时,需要用到灰度图、二值图、HSV、HSI等颜色制式,opencv提供了cv2.cvtColor()函数来实现这些功能:
- 第一个参数输入图像即要进行颜色空间变换的原图像;
- 第二个参数转换的代码或标识,即在此确定将什么制式的图片转换成什么制式的图片;
- 第三个参数为为输出图像即进行颜色空间变换后存储图像;
- 第四个参数是目标图像通道数,如果取0,则由src和code决定;
函数的作用是将一个图像从一个颜色空间转换到另一个颜色空间,但是从BGR向其它类型转换时,必须明确指出图像的颜色通道,在opencv中,其默认的颜色制式排列是BGR而非RGB。所以对于24位颜色图像来说,前8-bit是蓝色,中间8-bit是绿色,最后8-bit是红色。常见的B,G,R通道的取值范围为:
- . 0-255 :cv2.CV_8U类型图片;
- . 0-65535: cv2.CV_16U类型图片;
- . 0-1::cv2.CV_32F类型图片;
讲到这里有必要介绍一下图像的数据类型,一张图片就是一个简单的numpy数组,数组的数据类型有很多种,相互之间也可以转换,这些数据类型以及取值范围如下表所示:
Data type | Range |
---|---|
uint8 | 0 to 255 |
uint16 | 0 to 65535 |
uint32 | 0 to 232 |
float32 | -1 to 1 or 0 to 1 |
int8 | -128 to 127 |
int16 | -32768 to 32767 |
int32 | -231 to 231 - 1 |
对于线性变换来说,这些取值范围是无关紧要的。但是对于非线性转换,输入的BGR图像必须归一化到其对应的取值范围来或得最终正确的转换结果,例如从BGR>L*u*v转换。如果从一个8-bit类型图像不经过任何缩放(scaling)直接转换为32-bit浮点型图像,函数将会以0-255的取值范围来取代0-1的取值范围,所以在使用cvtColor函数之前需要对图像进行缩放如下:
img *= 1.0/255;
cvtColor(img,cv2.COLOR_BGR2Luv);
如果对8-bit图像使用cvtColor()函数进行转换将会由一些信息丢失。函数可以做下面类型的转换,需要说明的是在opencv2.x时颜色空间转换code用的宏定义是CV_前缀开头,而在opencv3.x版本其颜色空间转换code宏定义更改为COLOR_开头,而经验证,2.4.13版本中opencv同事支持这两种形式的写法。故下面表格会将两种code类型同时列出,以供参考:
这里列出的类型并不齐全,但是对于一般的图像处理已经够用。需要特别说明的是BGR–>GRAY的转换是我们常用的转换格式,其转换公式如下:
$$\gamma=0.299*R +0.587*G + 0.114*B$$
介绍一下BGRA格式图片,BGRA是代表Blue(蓝色),Green(绿色)、Red(红色)、和Alpha的色彩空间。虽然它有时候被描述为一个颜色空间,但是它其实是BGR模型附加了额外的信息,可以属于任何一种BGR颜色空间。Alpha参数一般用作不透明度参数,如果一个像素的alpha通道数值为0%,那它就是完全透明的也就是肉眼不可见,而数值为100%则意味着一个完全不透明的像素,传统的数字图像就是alpha值为100%.
2.1.4 示例程序
import cv2 import numpy as np import os ''' 1.读写图像文件 ''' #通过二维numpy数组创建一个黑色的正方形图像 img = np.zeros((3,3),dtype=np.uint8) #输出 每一个像素都是8位整数 范围0-255 图片大小为3x3 print(img) #[[0 0 0] # [0 0 0] # [0 0 0]] print(img.shape) #(3, 3) #利用cvtColor()函数将图像转换为blue-green-red(BGR)格式 img = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR) #输出维度为3x3x3 表明图像大小为3x3 有三个通道 每个像素由一个三元数组表示(B,G,R) print(img) # [[[0 0 0] # [0 0 0] # [0 0 0]] # [[0 0 0] # [0 0 0] # [0 0 0]] # [[0 0 0] # [0 0 0] # [0 0 0]]] print(img.shape) #(3, 3, 3) #把png图像转换为jpeg格式 root = '.\image' file = os.path.join(root,'test.bmp') #加载OpenCV图像最简单的方式是使用imread函数,该函数会返回一副图片,这幅图片是一个数组(根据imread()输入参数的不同,该图像可能是一个二维数组,也可能是三维数组) #在默认情况下,即使文件是灰度格式,imread()函数也会返回BGR格式的图像 #BGR和RGB所表示的色彩空间相同,字节顺序相反 img = cv2.imread(file) #读bmp格式取图片 cv2.imwrite('test.jpg',img) #保存成jpg格式 #按照指定参数读取图片 加载bmp文件作为灰度图像(丢失颜色信息) gray_image = cv2.imread(file,cv2.IMREAD_GRAYSCALE) #保存为灰度的jpg图像 cv2.imwrite('test_gray.jpg',gray_image)
2.2 图像与原始字节之间的转换
一个OpenCV图像一般是numpy.array类型的二维或者三维数组。8位的灰度图像是一个含有字节值得二维数组,维度为 $height * width$,24位的BGR是一个三维数组 维度为$height * width * channel$。
2.2.1 图像展示
图像展示函数原型:
cv2.imshow(winname,mat)
该函数用来显示图像,窗口会自动调整为图像大小:
- 第一个参数是窗口的名字。
- 第二个参数是要输出的图像。
imshow()在用于指定的窗口显示图像时,如果窗口用cv2.WINDOW_ATTOSIZE创建,那么显示图像原始大小。否则将图像进行缩放以适合窗口。而imshow()函数缩放图像取决与图像深度:
- 如果载入图像是8位无符号类型(8-bis unsigned),就显示图像本身。
- 如果图像是16位无符号类型(16-bist unsigned)或32位无整型(32-bit integer),则使用像素值除以256.也就是说将像素值范围在[0,255x256]之间的元素映射到(0,255]范围内。
- 如果载入图像是32位浮点型(32-bit floating-point),像素值要乘以255.也就是说像素值范围在[0,1]映射到[0,255]。
2.2.2 键盘绑定函数
cv2.waitKey(ms)
'''
2.图像与原始字节之间的转换
'''
img = cv2.imread(file) #读bmp格式取图片
print('图像{0}的维度为{1}'.format(file,img.shape))
#显示的转换为一维的python bytearray格式
byte_array = bytearray(img) #图像.\image\test.bmp的维度为(45, 50, 3)
print('转换之后的维度为:',len(byte_array),type(byte_array)) #转换之后的维度为: 6750 <class 'bytearray'>
#bytearray含有恰当顺序的字节,可以通过显示转换和重构 得到numpy.array形式的图像
img = np.asarray(byte_array).reshape(45,50,3)
cv2.imshow('window 1',img)cv2.waitKey(10) #等候10ms
'''
一个详细的例子 将含有随机字节的bytearray转换为灰度图像和BGR图像
'''
#随机生成120,000个字节的数组
random_byte_array = bytearray(os.urandom(120000)) #生成随机字节
#转换成numpy.array类型
byte_array = np.asarray(random_byte_array,dtype=np.uint8)
#把数组转换成 400 x 300的灰度图像
gray_image = byte_array.reshape(300,400)
cv2.imwrite('test_gray.png',gray_image)
#把数组转换成 400 x 100的BGR图像
bgr_image = byte_array.reshape(100,400,3)
cv2.imwrite('test_bgr.png',bgr_image)
2.3 使用numpy.array访问图像数据
下面实现把图像的一部分复制到另一个位置,然后显示。
''' 3.使用numpy.array访问图像数据 ''' #将BGR图像在(0,0)处的像素转化为白像素 img = cv2.imread('test.jpg') img[0,0] = [255,255,255] #img[0,0]操作和下面操作一样 ''' numpy.array的item(n_hight,n_width,n_channel)方法可以获取指定索引的值 通道BGR依次对应索引0,1,2 ''' print(img.item(25,26,0)) #获取25列26行B通道的值 222 img.itemset((25,26,0),155) #设置新的值 print(img.item(25,26,0)) #155 #将指定通道的所有值置为零 img[:,:,1] = 0 #将G通道像素值全部置为零 ''' 1.读取感兴趣区域,并把值赋值给一个变量 2.同理设定第二个区域,赋值给另一个变量 3.将第一个我区域的值赋值给第二个区域 (将图像一部分拷贝到图像另一部分) ''' img = cv2.imread('./image/img1.jpg') my_roi = img[800:1000,300:500] img[0:200,200:400] =my_roi cv2.imshow('window 2',img) cv2.waitKey(10) #等候10ms print(img.shape) #(1080, 1920, 3) 图像的高度,宽度,和通道数 print(img.size) #36220800 图像的大小 print(img.dtype) #uint8 图像的数据类型
2.4 视频文件的读写
OpenCV提供了VideoCapture类和VideoWriter类来支持各种格式的视频文件。支持的格式类型会因系统的不同而变化,但是都支持avi格式,在到达视频文件末尾之前,VideoCapture可通过read()函数来获取新的帧,每一帧都是一幅基于BGR的图像。VideoWriter可以通过write()写入每一帧图像。
''' 4.视频文件的读写(实现视频的复制) ''' ''' 将一幅图像传递给VideoWriter类的write()函数,该函数会将这幅图像加到VideoWriter类所指向的文件中 ''' video_capture = cv2.VideoCapture('./video./2.mp4') #获取帧速率 fps = video_capture.get(cv2.CAP_PROP_FPS) print('该视频的帧速率为:',fps) #该视频的帧速率为: 30.009902511911992 #获取图片的宽和高 size =(int(video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))) print('该视频每一帧的大小为:',size) #该视频每一帧的大小为: (544, 960) #1.VideoWriter类的构造函数指定视频文件名,这个文件名对应的文件若存在,则会被覆 #2.需要指定编解码器 #3.帧速率 #4.帧大小 video_writer = cv2.VideoWriter('./video/4.avi',cv2.VideoWriter_fourcc('m','p','4','v'),fps,size) success,frame = video_capture.read() cv2.imwrite('./video/frame.jpg',frame) #读取帧,直至没有帧可以读取 while success: #写入一帧 video_writer.write(frame) #读取一帧 success,frame = video_capture.read() video_writer.release() video_capture.release()
2.5 捕获摄像头的帧,并保存视频文件
VedioCapture类可以获得摄像头的帧流,但对于摄像头而言,通常不是用视频的文件名来构造VideoCapture类, 而是需要传递摄像头的设备索引,VideoCapture类对象的get方法不能返回摄像头帧速率的准确值,它总是返回0。
''' 5.捕获摄像头的帧(保存视频文件) ''' #获取摄像头10s的视频信息,并将其写入一个avi文件中 camera_capture = cv2.VideoCapture(0) #OpenCV没有提供任何查询摄像头数量和属性的方法。如果使用无效的索引构造了VideoCapture类,就得不到帧,read方法 #就会返回 False,None 因此需要在读取前判断一下设备是否已经打开 if camera_capture.isOpened(): fps = 30 #假设帧大小 #获取图片的宽和高 size =(int(camera_capture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(camera_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))) #1.VideoWriter类的构造函数指定视频文件名,这个文件名对应的文件若存在,则会被覆 #2.需要指定编解码器 #3.帧速率 #4.帧大小 video_writer = cv2.VideoWriter('./video/camera.avi',cv2.VideoWriter_fourcc('m','p','4','v'),fps,size) success,frame = camera_capture.read() num_frame_remaining = 10*fps - 1 #当前剩余捕获图像个数 while success and num_frame_remaining > 0 and cv2.waitKey(1)==-1: #显示拍照 cv2.imshow('frame',frame) #写入一帧 video_writer.write(frame) #读取一帧 success,frame = camera_capture.read() num_frame_remaining -= 1 #释放 camera_capture.release() video_writer.release()
2.6 窗口显示图像
''' 6 窗口显示图像 imshow()函数可以用来实现图片,但是这个图片显示出来后会立即消失,一般我们需要使用waitKey()函数, 传入等待键盘触发的时间,单位为ms,返回值为-1(表示没有键按下)或者ASCII码。并且需要调用destoryAllWindows() 释放OpenCV创建的所有窗口 ''' img = cv2.imread('./image/img2.jpg') cv2.imshow('window 3',img) cv2.waitKey(10)
2.7 在窗口显示摄像头帧
以下代码实现实时捕获摄像头的帧并显示在窗口,按任何键可以退出窗口。
2.7.1 cv2.destroyAllWindows()
该函数可以轻易删除任何我们建立的窗口。如果你想删除特定的窗口可以使用cv2.destroyWindow(),在括号内输入你想删除的窗口名。
2.7.2 cv2.namedWindow(winname[,flags])
指定窗口名来创建窗口:
- 第一个参数为窗口的名字。
- 第二个参数为窗口属性。
flags是一个枚举类型,其由如下参数:
- cv2.WINDOW_NORMAL:可以改变窗口大小(无限制),也可将一个满屏窗口转换成常用大小;
- cv2.WINDOW_AUTOSIZE:程序会根据呈现内容自动调整大小且不能手动更改窗口大小;
- cv2.WINDOW_OPENGL:创建支持OpenGL的窗口;
- cv2.WINDOW_FULLSCREEN:创建一个充满屏幕的窗口;cv2.WINDOW_FREETATIO:图像将尽可能展开;
- cv2.WINDOW_KEEPRATIO:图像比例受到约束。
namedWindow()函数是通过指定的名字创建一个作为图像和进度条显示的窗口,如果有相同名称的窗口已经存在,则函数不会重复创建窗口,而是什么都不做。我们可以调用destroyWindows()或者destroyAllWindows()函数来关闭窗口并取消之前分配的与窗口相关的所有内存空间。
2.7.3 示例程序
''' 7.在窗口显示摄像头帧 ''' clicked = False def on_mouse(event,x,y,flag,param): ''' #鼠标事件的回调函数 args: event:回调事件参数,有很多取值,分别对应不同的鼠标事件 param:可选参数,它是setMouseCallback()函数的第三个参数 默认为0 flag:标志参数 如 cv2.EVENT_FLAG_LBUTTON:该事件对应按下鼠标左键 x,y:鼠标坐标 ''' global clicked #鼠标左键松开 if event == cv2.EVENT_LBUTTONUP: clicked = True camera_capture = cv2.VideoCapture(0) #指定窗口名来创建窗口 cv2.namedWindow('window 4') #设置鼠标事件回调函数 来获取鼠标输入 cv2.setMouseCallback('window 4',on_mouse) print('Showing camera feed.Click window or press any key to stop') success,frame = camera_capture.read() #OpenCV窗口只有在调用cv2.waitKey()函数时才会更新,并且waitKey()函数只有在OpenCV窗口成为活动窗口时,才能捕获输入信息 while success and cv2.waitKey(1) == -1 and not clicked: cv2.imshow('window 4',frame) success,frame = camera_capture.read() camera_capture.release() cv2.waitKey(10) #等候10ms cv2.destroyAllWindows() #销毁所有窗口