1. 图像与原始字节之间的转换
从概念上讲,一个字节能表示0到255的整数。目前,对于多有的实时图像应用而言,虽然有其他的表示形式,但一个像素通常由每个通道的一个字节表示。
一个OpenCV图像是.array类型的二维或三维数组。8位的灰度图像是一个含有字节值的二维数组。一个24位的BGR图像是一个三维数组,它也包含了字节值。可使用表达式访问这些值,如image[0,0]或image[0,0,0]。第一个值代表像素的y坐标啊或行,0表示顶部;第二个值是像素的x坐标或列,0表示最左边;第三个值(如果可用的话)表示颜色通道。如,对于一个左上角有白色像素的8位灰度图像而言,image[0,0]的值为255. 对于一个左上角有蓝色像素的24位BGR图像而言,image[0,0]是[255,0,0]。
可以用另外一个表示,如image[0,0]或image[0,0]=128,还可表示成image.item((0,0))或image.setitem((0,0),128)。对于单像素操作,第二种表示方式更有效。
若一幅图像的每个通道为8位,则可将其显示转换为标准的一维Python bytearray格式:
byteArray = bytearray(image)
反之,bytearray含有恰当顺序的字节,可以通过显示转换和重构,得到numpy.array形式的图像:
garyImage = numpy.array(garyByteArray ).reshape(height, width)
bgrImage = numpy.array(bgrByteArray ).reshape(height, width, 3)
下面介绍将含有随机字节的bytearray转换为灰度图像和BGR图像:
import cv2 import numpy as np import os # 创建一个120000个随机字节的数组 randomByteArray = bytearray(os.urandom(120000)) #os.urandom(n) 返回n个随机byte值的string,作为加密使用 flatNumpyArray = np.array(randomByteArray) # 将数组转换为400 x 300的灰度图像 garyImage = flatNumpyArray.reshape(300, 400) cv2.imwrite('randomGary.png', garyImage) # 将数组转换为400 x 300的彩色图像 bgrImage = flatNumpyArray.reshape(100, 400, 3) cv2.imwrite('randomColor.png', bgrImage)
运行该程序,将会在程序所在目录中生成两张灰度图像(如下所示)。尺寸分别为400 x 100,400 x 400
使用Python标准的os.urandom()函数可随机生成原始字节,随后会把该字节转换为NumPy数组。需要注意的是,诸如numpy.random.randint(0, 256, 120000).reshape(400, 300)语句也能直接(并且更高效地)随机生成NumPy数组。使用os.urandom()函数的原因是该语句有助于展示原始字节的转换。
2. 使用numpy.array访问图像数据
加载OpenCV图像最简单的方式是使用imread()函数,该函数会返回一幅图像,这幅图像是一个数组(根据imread()函数输入参数的不同,该图像可能是二维数组,也可能是三维数组)。
y.array结构针对数组操作有很好的优化,它允许某些块(bulk)操作,这些操作在通常的Python中不可用这些特定的.array操作在OpenCV的图像处理中会很方便。利用numpy.array函数来转换数组比用普通的Python数组转换要快得多。
import cv2 import numpy as np img = cv2.imread('flower.jpg') img[0,0] = [255, 255, 255] cv2.imshow('my image', img) cv2.waitKey()
在图像左上方会出现一个白点。
假设想要改变一个特定像素的蓝色值,numpy.array提供了item()方法。该函数有3个参数:x(或左)位置,y(或顶部)位置以及(x,y)位置的数组索引(注意,在BGR图像中,某一位置的数据是按B,G,R的顺序保存的三元数组),该函数能返回索引函数的值。另一个方法是通过itemset()函数可设置指定像素在指定通道的值(itemset()有两个参数:一个三元组(x,y和索引)和要设定的值)。如下例子将坐标(150,120)的当前蓝色值127变为255
import cv2 import numpy as np img = cv2.imread('flower.jpg') print(img.item(150, 120, 0)) # 打印当前坐标点的蓝色值 img.itemset((150, 120, 0), 255) print(img.item(150, 120, 0))
建议使用内置的滤波器和方法来处理整个图像,上述方法只适合于处理特定的小区域。
下面介绍操作通道:将指定通道(B,G,R)的所有值置为0.(注:通过循环来处理Python数组的效率非常低,应该尽量避免这样的操作。使用数组索引可以高效地操作像素。像素操作是一个高代价的低效操作,特别是在视频数据处理时,会发现要等很久才能得到结果。可用索引(indexing)来解决该问题)
以下代码可将图像所有的G(绿色)值设为0
import cv2 import numpy as np img = cv2.imread('flower.jpg') img[:, :, 1] = 0 cv2.imshow('my image', img) cv2.waitKey()
运行结果为:
通过NumPy数组的索引访问原始像素,还可设定感兴趣区域(Region Of Interest, ROI)。一旦设定了该区域,就可以执行许多操作,例如,将该区域与变量绑定,然后设定第二个区域,并将第一个区域的值分配给第二个区域(将图像的一部分拷贝到该图像的另一个位置):
import cv2 import numpy as np img = cv2.imread('flower.jpg') roi = img[0:100, 0:100] img[100:200, 100:200] = roi # 此处需考虑所用图像的尺寸,不能超过,并确保两个区域的大小一样 cv2.imshow('my image', img) cv2.waitKey()
运行结果为:
此外,还可使用numpy.array来获得图像其他属性。
shape:NumPy返回包含宽度、高度和通道数(如果图像是彩色的)数组,这在调试图像类型时很有用;如果图像是单色或灰度的,将不包含通道值;
size:该属性是指图像像素的大小;
datatype:该属性会得到图像的数据类型(通常为一个无符号整数类型的变量和该类型占的位数,比如unit8类型)
import cv2 import numpy as np img = cv2.imread('flower.jpg') print(img.shape) print(img.size) print(img.dtype)
运行结果为:
(220, 252, 3)
166320
uint8
3.视频文件的读/写
OpenCV提供了VideoCapture类和VideoWriter类来支持各种格式的视频文件。支持的格式类型会因系统的不同而变化,但应该都支持AVI格式。在到达视频文件末尾之前,VideoCapture类可通过read()函数来获取新的帧,每帧都是一幅基于BGR格式的图像。
可将一幅图像传递给VideoWriter类的write()函数,该函数会将这幅图像加到VideoWriter类所指向的文件中。
如下示例读取AVI文件的帧,并采用YUV颜色编码将其写入另一帧中:
import cv2 videoCapture = cv2.VideoCapture('myvideo.avi') 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('I', '4', '2', '0'), fps, size) success, frame = videoCapture.read() while success: # 循环直到所有帧结束 videoWriter.write(frame) success, frame = videoCapture.read()
要特别注意:必须要为VideoWriter类的构造函数指定视频文件名,这个文件名对应的文件若存在,会被覆盖。也必须指定视频编解码器。编解码器的可用性根据系统不同而不同。下面是一些常用选项:
cv2.VideoWriter_force('I', '4', '2', '0'):该选项是一个未压缩的YUV颜色编码,是4:2:0色度子采样。这种编码有很好的兼容性,但会产生较大文件,文件扩展名为.avi。
cv2.VideoWriter_force('P', 'I', 'M', '1'):该选项是MPEG-1编码类型,文件扩展名为.avi。
cv2.VideoWriter_force('X', 'V', 'I', 'D'):该选项是MPEG-4编码类型,如果希望得到的视频大小为平均值,推荐使用此选项,文件扩展名为.avi。
cv2.VideoWriter_force('T', 'H', 'E', 'O'):该选项是Ogg Vorbis,文件扩展名应为.ogv。
cv2.VideoWriter_force('F', 'L', 'V', '1'):该选项是一个Flash视频,文件扩展名应为.flv。
帧速率和帧大小也必须要指定,因为需要从另一个视频文件复制视频帧,这些属性可以通过VideoCapture类的get()函数得到。
4. 捕获摄像头的帧
VideoCapture类可以获得摄像头的帧流。但对摄像头而言,通常不是用视频的文件名来构造VideoCapture类,而是需要传递摄像头的设备索引(device index)。
下面的例子会捕获摄像头10秒的视频信息,并将其写入一个AVI文件中:
import cv2 cameraCapture = cv2.VideoCapture(0) 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('I', '4', '2', '0'), fps, size) success, frame = cameraCapture.read() numFramesRemaining = 10 * fps - 1 while success and numFramesRemaining > 0: videoWriter.write(frame) success, frame = cameraCapture.read() numFramesRemaining -= 1 cameraCapture.release()
然而,VideoCapture类的get()方法不能反悔摄像头帧速率的准确值,它总是返回0。
为了针对摄像头创建合适的VideoWriter类,要么对帧速率做出假设,要么使用计时器来测量。摄像头的数量和顺序由系统决定,但OpenCV没有提供任何查询摄像头数量和属性的方法。如果使用无效索引构造了VideoCapture类,就不会得到帧,VideoCapture的read()函数会返回(false, None)。为了不让read()函数从没有正确打开的VideoCapture类中获取数据,可在执行该函数之后使用VideoCapture.isOpened方法做一个判断,该方法返回一个Boolean值。
当需要同步一组摄像头或一个多头摄像头(例如立体摄像头或Kinect)时,read()方法就不再适合了,可用grab()和retrive()方法代替它。对于一组摄像头,可以使用以下代码:
success0 = cameraCapture0.grab() success1 = cameraCapture1.grab() if success0 and success1: frame0 = cameraCapture0.retrive() frame1 = cameraCapture1.retrive()
5. 在窗口显示图像
用imshow()函数实现显示图像的操作。imshow()函数有两个参数:显示图像的帧名字以及要显示的图像本身。
import cv2 import numpy as np img = cv2.imread('flower.jpg') cv2.imshow('my image', img) cv2.waitKey() cv2.destroyAllWindows() # 释放由OpenCV创建的所有窗口
6. 在窗口显示摄像头帧
OpenCV的namedWindow()、imshow()和DestoryWindow()函数允许指定窗口名来创建、显示和销毁(destroy)窗口。此外,任何窗口都可以通过waitKey()函数来获取键盘输入,通过setMouseCallback()函数来获取鼠标输入。以下代码可实时显示摄像头帧:
import cv2 clicked = False def onMouse(event, x, y, flags, param): global clicked if event == cv2.EVENT_LBUTTONUP: clicked = True cameraCapture = cv2.VideoCapture(0) cv2.namedWindow('MyWindow') cv2.setMouseCallback('MyWindow', onMouse) 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()
waitKey()的参数为等待键盘触发的时间,单位为毫秒,其返回值为-1(表示没有键被按下)或ASCII码。另外,Python提供了一个标准函数ord(),该函数可以将字符转换为ASCII码。(注:在一些系统中,waitKey()的返回值可能比ASCII码的值更大(在Linux系统中,如果OpenCV使用GTK作为后端的GUI库,就会出现bug),在所有系统中,可以通过读取返回值的最后一个字节来保证肢体去ASCII码,代码为:
keycode = cv2.waitkey(1)
if keycode != -1:
keycode &= 0xff )
OpenCV的窗口函数和waitKey()函数相互依赖。OpenCV的窗口只有在调用waitKey()函数时才会更新,waitKey()函数只有在OpenCV窗口成为活动窗口时,才能捕获输入信息。
鼠标回调函数setMouseCallback()有5个参数,param是可选参数,它是setMouseCallback()函数的第三个参数,默认情况下,该参数是0.回调时间参数可以取如下的值,它们分别对应不同的鼠标事件。
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:该事件随影双击鼠标中间键
鼠标回调的标志参数可能是以下事件的按位组合:
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键