在处理文件前需要引入OpenCV库,同时也引入unmpy库
import cv2 import numpy as np
1 基本的读写操作
1.1 图像文件的读写操作
1.1.1 图像文件的读取操作
opencv 的 imread() 函数和 imwrite() 函数支持各种静态图像文件格式。例如bmp, png, jpeg, tiff等。
cv2.imread(filename[, flags])
imread() 函数从文件中加载并返回该图像,若指定图像不能被读取(文件丢失、权限不正确、不支持或非法的格式等等),则返回一个空的矩阵。
opencv 为 flags 提供了枚举类型文档,主要有
- cv2.IMREAD_UNCHANGED = -1 如果设置,则按原样返回加载的图像(使用Alpha通道,否则会被裁剪)。
- cv2.IMREAD_GRAYSCALE = 0 如果设置,则始终将图像转换为单通道灰度图像。
- cv2.IMREAD_COLOR = 1 如果设置,请始终将图像转换为3通道BGR彩色图像。
- cv2.IMREAD_ANYDEPTH = 2 如果设置,当输入深度为16/32位时,则返回相应的深度,否则将其转换为8位。
- cv2.IMREAD_ANYCOLOR = 4 如果设置,则以任何可能的颜色格式读取图像。
- cv2.IMREAD_LOAD_GDAL = 8 如果设置,请使用gdal驱动程序加载图像。
具体参考 Image file reading and writing
备注1:
filename 可以是绝对路径,也可以是相对路径。
win环境下的绝对路径:D:\\grandfather\\father\\test.png
Unix下的文件间隔符为 /
备注2:
无论采用哪种模式,imread() 函数均会删除所有透明alpha 通道信息。
备注3:
当图像文件为灰度格式时,imread() 函数也会返回 BGR(red-green-blue) 格式的图像,BGR与RGB所表示的色彩空间相同,但是字节顺序相反。
备注4:
那怎么读取灰度图文件后再保存为灰度图呢?
增加imread()函数的 flags 参数即可
grayImage = cv2.imread('test.png', cv2.IMREAD_GRAYSCALE) cv2.imwrite('NewTest.png', grayImage)
1.1.2 图像文件的写入操作
imwrite(filename, img[, params])
该函数将图像保存为指定文件中
图像格式是根据文件扩展名选择的,一般情况下,该函数只能保存8位单通道或3位通道(按“BGR”通道顺序)的图像。
具体可参看1.1.1 中的代码示例。
另:
opencv可以读取一个格式图像文件,保存时选取另一种格式图像文件。
将 png --> jpg 格式。
image = cv2.imread('test.png') cv2.imwrite('test.jpg',image)
1.1.3 图像与原始字节之间的转换
1) 图像像素的基本介绍
无论哪种格式的图像,每个像素都有一个对应值,但不同格式所表示的像素方式也存在有差异。
一个 opencv 图像是 array 类型的二维或三维数组。8 位的灰度图像是一个含有字节值的二维数组,8 位图像有 2^8 = 256 种颜色(16 位图像的像素值有 2^16 = 65536 种颜色)
一个 24 位的 BGR 图像是一个 三维数组,它也包含了字节值。可以使用表达式访问这些值。
image[0,0] 或者 image[0, 0, 0] 。
- 第一个值代表像素 y 坐标或者行
- 第二个值代表像素 x 坐标或者列
- 第三个值代表颜色通道
例如,
对于一个白色像素的 8 位灰度图像而言,image[0, 0] 的值为255,
对于一个蓝色像素的24位BGR图像而言,image[0, 0] 的值为[255, 0, 0 ]
2)素与字节的转换
若一副图像的每个通道为 8 位,则可将其显示转换为标准的一维python bytearray 格式。
byteArray = bytearray( image)
若bytearray含有恰当顺序的字节,可以通过显示转换和重构,得到numpy.array形式的图像。
grayImage = numpy.array(grayByteArray).reshape(height, width) bgrImage = numpy.array(bgyByteArray).reshape(height, width, 3)
3) 像素与字节的转换示例
import cv2 import numpy as np import os # make an array of 120000 random bytes randomByteArray = bytearray(os.urandom(120000)) flatNumypyArray = np.array(randomByteArray) # convert the array to make a 400*300 grayscale image grayImage = flatNumypyArray.reshape(300, 400) cv2.imwrite('RandomGray.png', grayImage) # convert the array to make a 400*100 color image bgrImage = flatNumypyArray.reshape(100, 400, 3) cv2.imwrite('RandomColor.png', bgrImage)
运行该脚本,会随机生成两个图像 RandomGray.png 和 RandomColor.png ,图像位于脚本所在的目录。
备注:
使用 python 标准的 os.urandom() 函数可随机生成原始字节,随后将该字节转换成 numpy 数组。
需要注意的是,numpy.random.randint(0, 256, 120000 ).reshape(300, 400) 语句也能直接随机生成 numpy 数组,且该方法更为高效;使用os.urandom 函数的唯一理由是该语句有助于展示原始字节的转换。
1.2 视频文件的读写操作
视频中包含的信息量远远大于图片,但本质上而言,视频是由一帧帧的图像组成的,所以视频处理最终还是归结于图像处理。
opencv 提供了 VideoCapture 类和 VideoWriter 类来支持个各种视频格式读写操作
1.2.1 视频文件的读取操作
1)打开一段已有视频或默认的摄像头
一种实例化 VideoCapture 类时直接传参,第二种是使用 open() 函数。
# 方法1 cv2.VideoCapture('../video.avi') # 方法2 v = cv2.VideoCapture() v.open('../video.avi')
备注:如果将文件参数更变为ID,则可打开该摄像头,默认摄像头为 0
2)获取视频的帧
在视频采集过程中,VideoCapture 类可通过 read() 函数来获取视频的帧,每帧是一幅基于 BGR 格式的图像。
v = cv2.VideoCapture() v.read( [, image] )
抓取、解码并返回下一个视频帧。函数会返回视频帧,若没有抓住任何帧(相机已断开连接或视频文件中没有更多的帧),则该方法返回 false 并将该函数返回空图像。
3)获取视频的参数
一个视频的参数有尺寸、格式、帧率、总帧数等参数,get() 方法能够获取这些参数
v = cv2.VideoCapture() a = v.get(cv2.CAP_PROP_FPS) # 获取帧率 b = v.get(cv2.CAP_PROP_FRAME_WIDTH) # 视频流中的帧宽度 c = v.get(cv2.CAP_PROP_FRAME_HEIGHT)# 视频流中的帧高度 d = v.get(cv2.CAP_PROP_FRAME_COUNT)# 视频文件中帧的总数 e = v.get(cv2.CV_CAP_PROP_POS_MSEC)# 视频目前指针位置,为毫秒数或时间戳 f = v.get(cv2.CV_CAP_PROP_POS_FRAMES)# 将被下一步解压/获取的帧索引,以0为起点 g = v.get(cv2.CV_CAP_PROP_POS_FRAMES)# 视频文件的相对位置,0视频起始处、1视频结尾处
4)读取视频中具体位置处的图像
VideoCapture类的set方法可以允许我们取出视频中某个位置的帧,它有一些参数,可以按时间,也可以按帧号,还可以按视频长短的比例。
v = cv2.VideoCapture() # 第100帧 double position = 100.0 capture.set(CV2.CAP_PROP_POS_FRAMES, position) # 第1e6毫秒 double position = 1e6 capture.set(CV2.CAP_PROP_POS_MSEC, position) # 视频1/2位置 double position = 0.5 capture.set(CV2.CAP_PROP_POS_AVI_RATIO, position)
当然,set方法仅用于取视频帧的位置,还可以设置视频的帧率、亮度。
1.2.2 视频文件的写入操作
VideoWriter 类的 write() 函数可将图像写入到VideoWriter 类所指向的文件中。
下面的示例读取 MP4 文件的帧,并采用 YUV 颜色编码将其写入另一帧中。
import cv2 videoCapture = cv2.VideoCapture() videoCapture.open('MyInputVid.mp4') fps = videoCapture.get(cv2.CAP_PROP_FPS) size = (int(videoCapture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(videoCapture.get(cv2.CAP_PROP_FRAME_HEIGHT))) videoWriter = cv2.VideoWriter( 'MyOutputVid.avi', cv2.VideoWriter_fourcc('), fps, size ) success, frame = videoCapture.read() # loop until there are no more frames while success: videoWriter.write(frame) success, frame = videoCapture.read()
备注:
必须为 VideoWriter 类的构造函数指定视频文件名,若文件已存在,则被覆盖;
必须为视频指定编解码器,编解码器的可用性根据系统的不同而不同。
- cv2.VideoWriter_fourcc('I', '4', '2', '0') - 该选项是一个未压缩的 YUV 颜色编码,是4:2:0 色度子采样,这种编码有很好的兼容性,但会产生较大文件,扩展名.avi
- cv2.VideoWriter_fourcc('P', 'I', 'M', '1') - 该选项是MPEG-1编码类型,扩展名为.avi
- cv2.VideoWriter_fourcc('X', 'V', 'I', 'D') - 该选项是MPEG-4编码类型,若希望得到视频大小为平均值,推荐使用此项,扩展名.avi
- cv2.VideoWriter_fourcc('T', 'H', 'E', 'O') - 该选项是ogg vorbis,扩展名.ogv
- cv2.VideoWriter_fourcc('F', 'L', 'V', 'I') - 该选项是一个 flash 视频,扩展名.flv
必须指定帧速率、帧大小,因需要从另一个视频文件复制视频帧;这些属性可从 VideoCapture 类的get() 函数中获取。
1.3 捕捉摄像头的帧
VideoCapture 类可以获得摄像头的帧流,参数则为摄像头的设备索引(device index),下段代码为捕捉摄像头 10 秒的视频信息;并将其写入一个avi文件中。
import cv2 cameraCapture = cv2.VideoCapture(0) # 假设帧率为30 fps = 30 size = (int(cameraCapture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cameraCapture.get(cv2.CAP_PROP_FRAME_HEIGHT))) videoWriter = cv2.VideoWriter( 'MyOutputVid.avi', cv2.VideoWriter_fourcc('), fps, size ) success, frame = cameraCapture.read() numFrameRemaining = 10 * fps - 1 while success and numFrameRemaining > 0: videoWriter.write(frame) success, frame = cameraCapture.read() numFrameRemaining -= 1 cameraCapture.release()
备注1:
为了针对摄像头创建合适的VideoWriter 类,这里设置帧速率fps=30,主要是因为VideoCapture 类的 get() 方法不能返回摄像头帧速率的准确值,它总是返回0,
return Value for the specified property. Value 0 is returned when querying a property that is not supported by the backend used by the VideoCapture instance.
当 VideoCapture 类所使用的终端不支持所查询的这个属性时,则会返回0
另外,采用计时器来计量帧速率会更精确些。
1.4 在窗口显示图像
imshow(winname, mat)
该函数可实现指定窗口显示图像。但是该函数因代码的改变而实时改变窗口,必须再次运行代码后图像才能变化。
winname - 窗口的名称
mat - 要显示的图像。
例如下段代码
import cv2 img = cv2.imread('my_image.jpg') cv2.imshow('A pretty girl', img) cv2.waitKey() cv2.destroyAllWindows()
运行则在窗口显示
备注1:
如果窗口是使用cv::WINDOW_AUTOSIZE 标志flags创建的,则图像显示为原始大小,实际上,该值也为默认值。不过图像大小仍受屏幕分辨率的限制。
当然,图像也可按比例缩放以适合窗口,缩放图像取决于其深度。
- 如果图像是8位无符号的,它将按原样显示。
- 如果图像是16位无符号或32位整数,像素就除以256。也就是说,值范围[0,255*256]被映射到[0,255]。
- 如果图像是32位或64位浮点数,像素值将乘以255。也就是说,值范围[0,1]被映射到[0,255]。
备注2:
调用 destoryAllWindows() 函数可以释放由OpenCV 创建的所有窗口
提示:
- 按Ctrl + C将图像复制到剪贴板。
- 按Ctrl + S将显示保存图像的对话框。
1.5 在窗口显示摄像头帧
opencv 的常用函数及其功能。
- nameWindow() - 函数允许指定窗口来创建窗口
- imshow() - 函数允许指定窗口来显示窗口
- DestroyWindow() - 函数允许指定窗口来销毁窗口
- waitKey() - 函数可以获取键盘输入
- setMouseCallback() - 函数可以获取鼠标输入
下段代码可以实时显示摄像头帧
import cv2 clicked = False def clickMouse(event, x, y, flags, param): global clicked if event == cv2.EVENT_LBUTTONUP: clicked = True # 开启ID为0的摄像头 cameraCapture = cv2.VideoCapture(0) # 为窗口赋名MyWindow cv2.namedWindow('MyWindow') # 鼠标处理函数 cv2.setMouseCallback('MyWindow', clickMouse) print('Showing camera feed. click window or press any key to stop.') success, frame = cameraCapture.read() while success and cv2.waitKey(1) == -1 and not clicked: cv2.imshow('MyWindow', frame) success, frame = cameraCapture.read() cv2.destroyWindow('MyWindow') cameraCapture.release()
1.5.1 对鼠标操作函数 setMouseCallback()
setMouseCallback(windowName, onMouse [, param])
- windowName - 目标窗口名称
- onMouse - 鼠标响应函数,回调函数名
- param - 回调函数的参数,可选参数
在上述代码中,回调函数为clickMouse()。在回调函数中的参数可以取如下值,它们对应不同的鼠标事件。
假设定义回调函数为OnMouseAction()
def OnMouseAction(event,x,y,flags,param): pass
备注:鼠标操作函数为 cv2.setMouseCallback('img',OnMouseAction) ,其中 img 为被操作的窗口名称。
回调函数有 5 个参数,每一个参数对应释义如下。
Event(对应的鼠标标志参数有):
- cv2.EVENT_MOUSEMOVE - 鼠标移动
- cv2.EVENT_LBUTTONDOWN - 鼠标左键按下
- cv2.EVENT_RBUTTONDOWN - 鼠标右键按下
- cv2.EVENT_MBUTTONDOWN - 鼠标中间键按下
- cv2.EVENT_LBUTTONUP - 鼠标左键松开
- cv2.EVENT_RBUTTONUP - 鼠标右键松开
- cv2.EVENT_MBUTTONUP - 鼠标中间键松开
- cv2.EVENT_LBUTTONDBLCLK - 双击鼠标左键
- cv2.EVENT_RBUTTONDBLCLK - 双击鼠标右键
- cv2.EVENT_MBUTTONDBLCLK - 双击鼠标中间键
x, y 代表鼠标位于窗口坐标位置(x,y),即Point(x,y)
flags - 代表鼠标的拖拽事件,以及键盘鼠标联合事件,共有 32 件事件:
- cv2.EVENT_FLAG_LBUTTON - 按下鼠标左键
- cv2.EVENT_FLAG_RBUTTON - 按下鼠标右键
- cv2.EVENT_FLAG_MBUTTON - 按下鼠标中间键
- cv2.EVENT_FLAG_CTRLKEY - 按下Ctrl键
- cv2.EVENT_FLAG_SHIFTKEY - 按下Shift键
- cv2.EVENT_FLAG_ALTKEY - 按下Alt键
param - 函数指针,标识了所响应的事件函数,相当于自定义了一个 OnMouseAction() 函数的 ID;可选参数
然而,opencv 不提供任何处理窗口事件的方法,例如,当单击窗口的关闭按钮时,并不能关闭应用程序,由于 opencv 有限的事件处理能力和 GUI 处理能力,许多开发人员更喜欢将opencv集成到其他应用程序框架中。
更多内容可以参考【AI基础】python:openCV——处理鼠标事件(1)
1.5.2 waitKey()
imshow() 函数后面应该配套使用 cv::waitKey() 函数,如果没有该函数,则图像在屏幕上一闪即过;
waitKey() 或waitKey(0) 将无限显示窗口,任意键结束窗口显示,waitKey(25) 表示图像显示一个 25 毫秒的帧,之后显示将自动关闭。如果将其放在一个循坏中来阅读视频,它将逐帧显示视频。
返回值 -1(表示没有键被按下)或 ASCII 码。注意:ASCII码可以通过 python 提供的函数 old() 来实现字符转换为 ASCII 码。old('a') = 97。
opencv 的窗口函数和 waitKey() 函数相互依赖。opencv 的窗口只有在调用waitkey() 函数时才会更新,waitkey() 函数只有在 opencv 窗口成为活动窗口时,才能捕获输入信息。
======2019.01.19 更新========
在运行过程中发生
AttributeError: module 'cv2.cv2' has no attribute 'waitkey'
查找原因才发现问题出在 waitkey() 函数的书写上,该函数应该是 waitKey() ,K 为大写。
实际上,K 与 k的大小写很容易混肴,所以造成了报错。
============================
1.6 转载两个相关小程序代码
原创 OpenCV-Python:处理鼠标事件
1.6.1 查询鼠标事件方法
C:\Users>python>>> import cv2 >>> events = [i for i in dir(cv2) if 'EVENT' in i] >>> print(events) ['EVENT_FLAG_ALTKEY', 'EVENT_FLAG_CTRLKEY', 'EVENT_FLAG_LBUTTON', 'EVENT_FLAG_MBUTTON', 'EVENT_FLAG_RBUTTON', 'EVENT_FLAG_SHIFTKEY', 'EVENT_LBUTTONDBLCLK', 'EVENT_LBUTTONDOWN', 'EVENT_LBUTTONUP', 'EVENT_MBUTTONDBLCLK', 'EVENT_MBUTTONDOWN', 'EVENT_MBUTTONUP', 'EVENT_MOUSEHWHEEL', 'EVENT_MOUSEMOVE', 'EVENT_MOUSEWHEEL', 'EVENT_RBUTTONDBLCLK', 'EVENT_RBUTTONDOWN', 'EVENT_RBUTTONUP'] >>>
1.6.2 在窗口上绘制圆圈
import cv2 import numpy as np # 鼠标回调函数 def draw_circle(event,x,y,flags,param): if event == cv2.EVENT_LBUTTONDBLCLK: # 处理鼠标左键双击 cv2.circle(img,(x,y),100,(255,0,0),-1) # 在鼠标点击位置创建圆 # 创建一个黑背景图像 img = np.zeros((512,512,3), np.uint8) cv2.namedWindow('image') cv2.setMouseCallback('image',draw_circle) # 设置回调函数 while(1): cv2.imshow('image',img) if cv2.waitKey(20) & 0xFF == 27: # esc键退出 break cv2.destroyAllWindows()
运行
1.6.3 绘制长方形
import cv2 import numpy as np drawing = False # 鼠标左键是不是按下 mode = True # True,激活绘制正方形. False 激活绘制曲线;使用m键切换 ix,iy = -1,-1 # mouse callback function def draw_circle(event,x,y,flags,param): global ix,iy,drawing,mode if event == cv2.EVENT_LBUTTONDOWN: drawing = True ix,iy = x,y elif event == cv2.EVENT_MOUSEMOVE: if drawing == True: if mode == True: cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1) else: cv2.circle(img,(x,y),5,(0,0,255),-1) elif event == cv2.EVENT_LBUTTONUP: drawing = False if mode == True: cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1) else: cv2.circle(img,(x,y),5,(0,0,255),-1) img = np.zeros((512,512,3), np.uint8) cv2.namedWindow('image') cv2.setMouseCallback('image',draw_circle) while(1): cv2.imshow('image',img) k = cv2.waitKey(1) & 0xFF if k == ord('m'): mode = not mode elif k == 27: break cv2.destroyAllWindows()
运行
2 人脸跟踪与图像处理(Cameo项目)
为了开发人脸跟踪和图像处理的交互式作用,需要实时获取摄像头的输入;该类的应用覆盖了 opencv 大部分的功能。
就项目具体而言,该应用将实时进行脸部合并(facial merging) ,即对给定的两个摄像头的输入流(或选择性输入预先录制的视频),应用要对两个输入流的人脸进行叠加;为了使混合的场景看起来具有统一的感觉,可采用滤波器和扭曲(distortion)技术,用户会有一种融入现场表演的感觉。在这样的应用中,如果有帧率低、不精确跟踪等缺陷,用户会立即感受得到,为了达到最好的效果,可以尝试采用传统成像和深度成像的方法。
将本应用程序命名为Cameo
2.1 Cameo - 面向对象的设计
由上文可知,opencv 的图像 I/O 功能都是相似的,所以可创建CaptureManager 类和 WindowManager 类作为高级的 I/O 流接口。
CaptureManager 来读取新的帧,并将帧分派到一个或多个输出中,这些输出包括静止的图像文件、视频文件及窗口(可通过WindowManager 类实现)。
WindowManager 类使应用程序代码能以面向对象的形式处理窗口和事件。
CaptureManager 和 WindowManager 都具有可扩展性,因此实现时可不依赖OpenCV 的I/O。
2.2 使用manager.CaptureManager 提取视频流
未完待续2019.01.05