5、picamera的高级使用
下面的这些实例包含了picamera的一些高级使用方式,可能需要有一些图像开发经验才能掌握。所以请随时提出改进或更多的实例。
5.1、无损格式图像采集(YUV格式)
如果你不想损失拍摄图像的细节(由于jpeg是有损压缩),那么你可以通过PNG来接收拍摄的图像(PNG为无损压缩格式),然而某些应用需要YUV(YUV是被欧洲电视系统所采用的一种颜色编码方法)这种数字压缩格式的图像,对于这点需求可以用‘yuv’格式来压缩这些数据:
import time
import picamera
with picamera.PiCamera() as camera:
camera.resolution = (100, 100)
camera.start_preview()
time.sleep(2)
camera.capture('image.data', 'yuv')
YUV具体是采用YUV420【还有YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV411等】的格式来压缩,这意味着,首先,数据的Y(亮度)值,这个值必须在全分辨率中都包含(用于计算分辨率中每一个像素的Y值),然后是U(色彩饱和度)值,亮度作用于色彩饱和度之上,最后是V(色度)值。每一个色彩与色度之上都包含四分之一的亮度。表格如下:
需要注意的是,输出到未编码格式时,摄像头需要图片的分辨率,水平分辨率上位32位的本书,垂直分辨率为16的倍数。例如,如果请求分辨率为100X100,那么实际捕获到的图像为分辨率128X112的像素数据。
鉴于YUV420格式的每个像素都包含1.5个字节的数据(每个像素包含1个字节的Y值,每四个像素包含一个UV值),并考虑到分辨率,一个100x100的yuv图像的大小将是:
- 128.0 水平32的倍数
- 112.0 垂直16的倍数
- 1.5 1.5bytes的yuv数据(YUV比为4:2:0)
- =21504.0 bytes
前14336字节的数据为Y值,然后3584字节的数据(128x112/4)为U值,最后3584字节数据为V值。
下面这个实例演示了捕捉YUV图像数据,并将数据加载到numpy,然后将其转换为有效的RGB图像格式:
from __future__ import division
import time
import picamera
import numpy as np
width = 100
height = 100
stream = open('image.data', 'w+b')
# 捕获格式为YUV的图像
with picamera.PiCamera() as camera:
camera.resolution = (width, height)
camera.start_preview()
time.sleep(2)
camera.capture(stream, 'yuv')
# 像流指针指向开始
stream.seek(0)
# 计算实际图像的像素数
fwidth = (width + 31) // 32 * 32
fheight = (height + 15) // 16 * 16
# 然后从流中读出Y的值
Y = np.fromfile(stream, dtype=np.uint8, count=fwidth*fheight).\
reshape((fheight, fwidth))
# 最后将流中UV的值读出
U = np.fromfile(stream, dtype=np.uint8, count=(fwidth//2)*(fheight//2)).\
reshape((fheight//2, fwidth//2)).\
repeat(2, axis=0).repeat(2, axis=1)
V = np.fromfile(stream, dtype=np.uint8, count=(fwidth//2)*(fheight//2)).\
reshape((fheight//2, fwidth//2)).\
repeat(2, axis=0).repeat(2, axis=1)
# 将堆栈中的图像转换为实际的分辨率
YUV = np.dstack((Y, U, V))[:height, :width, :].astype(np.float)
YUV[:, :, 0] = YUV[:, :, 0] - 16 # Offset Y by 16
YUV[:, :, 1:] = YUV[:, :, 1:] - 128 # Offset UV by 128
# 将YUV转换成ITU-R BT.601版本(SDTV)的数据
# Y U V
M = np.array([[1.164, 0.000, 1.596], # R
[1.164, -0.392, -0.813], # G
[1.164, 2.017, 0.000]]) # B
# 最后输出RGB数据
RGB = YUV.dot(M.T).clip(0, 255).astype(np.uint8)
你可能注意到,在实例中我们创建文件使用了open方法,而不是io.open(),这是因为numpy的fromfile()只接受真实的文件对象。
现在这个实例已经封装在PiYUVArray类中,所以代码可以简化成:
import time
import picamera
import picamera.array
with picamera.PiCamera() as camera:
with picamera.array.PiYUVArray(camera) as stream:
camera.resolution = (100, 100)
camera.start_preview()
time.sleep(2)
camera.capture(stream, 'yuv')
# 显示YUV图像大小
print(stream.array.shape)
# 显示转换成RGB图像后文件的大小
print(stream.rgb_array.shape)
最后可以通过camera.capture(stream, 'rgb')
来直接让摄像头输出rgb数据,来替代以上脚本。
注意,在版本1.0中的format格式“raw”现在已经变更为YUV,若使用最新的库,请将格式修改成最新版。
从1.5版以后加入了picamera.array模块
5.2、无损格式图像采集(RGB格式)
RGB格式与YUV格式现在争议比较大,不过都是相当有益的讨论。在picamera上输出RGB格式的数据非常简单,只需要跳动capture函数将捕获的图像格式设置为RGB即可。
import time
import picamera
with picamera.PiCamera() as camera:
camera.resolution = (100, 100)
camera.start_preview()
time.sleep(2)
camera.capture('image.data', 'rgb')
计算RGB图像数据的大小与YUV相同,首先会调整分辨率(参考YUV分辨率调整),其次,每个像素块在RGB上是占用3bytes的数据(红绿蓝三色各占1byte的数据),因此捕获一个100x100的图像,产生的数据如下:
- 128.0 水平32的倍数
- 112.0 垂直16的倍数
- 3 每个像素块占3byte的数据
- =43008.0 bytes
由此可见,RGB的数据是由红绿蓝三色的数据结合产生,其顺序为,第一个字节为红色(0,1)第二个字节为绿色(0,0)最后一个是蓝色的字节。
然后若想将RGB数据转换成Numpy的话如下:
from __future__ import division
width = 100
height = 100
stream = open('image.data', 'w+b')
# 设置捕获类型为RGB
with picamera.PiCamera() as camera:
camera.resolution = (width, height)
camera.start_preview()
time.sleep(2)
camera.capture(stream, 'rgb')
# 将指针指向数据开始
stream.seek(0)
# 计算实际的图片大小
fwidth = (width + 31) // 32 * 32
fheight = (height + 15) // 16 * 16
# 将图像读取进numpy之中
image = np.fromfile(stream, dtype=np.uint8).\
reshape((fheight, fwidth, 3))[:height, :width, :]
# 如果你希望将图像的字节浮点数控制在0到1之间,添加如下代码
image = image.astype(np.float, copy=False)
image = image / 255.0
现在这个实例已经被封装到pirgbarray类中,所以可以简化成如下代码:
import time
import picamera
import picamera.array
with picamera.PiCamera() as camera:
with picamera.array.PiRGBArray(camera) as stream:
camera.resolution = (100, 100)
camera.start_preview()
time.sleep(2)
camera.capture(stream, 'rgb')
# 输出rgb图像的大小
print(stream.array.shape)
注意,在版本1.0中的format格式“raw”现在已经变更为RGB,若使用最新的库,请将格式修改成最新版。
从1.5版以后加入了picamera.array模块。
5.3、图像的快速捕捉及快速处理
树莓派的摄像头可以快速的捕捉一组图像序列,将之解码成jpeg格式(通过设置usb_video_port参数),但是使用这个功能需要注意几点:
- 当使用video-port来捕捉图像的时候,在某些情况下,所捕捉的图像大小及清晰度可能会不如正常捕获的图片(可以参考相机模式和视频模式的区别)
- 所捕捉的图像没办法嵌入EXIF信息。
- 所捕获的图像可能会非常不清晰,噪点很大。若希望能捕获更清晰的图片,可以使用比较慢的获取方式,或者采取更先进的降噪算法。
所有的捕捉方法都支持use_video_port选项,但方法不同所捕捉图像的能力也有所不同。所以虽然capturehe和capture_continuous方法都支持use_video_prot功能,但最好使用capture_continuous来实现快速捕捉图片这个功能(因为capture_continuous不会每次都初始化解码器)。作者在测试时,这个方法最高可以支持在30fps下获取分辨率为1024x768的图片。
通常情况下,capture_continuous方法特别适合与捕获固定帧数的图像,比如下面这个例子:
import time
import picamera