picamera 1.10 教程及api中文简译(四)picamera的高级使用

转自:点击打开链接



5、picamera的高级使用

下面的这些实例包含了picamera的一些高级使用方式,可能需要有一些图像开发经验才能掌握。所以请随时提出改进或更多的实例。

5.1、无损格式图像采集(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')


如果你不想损失拍摄图像的细节(由于jpeg是有损压缩),那么你可以通过PNG来接收拍摄的图像(PNG为无损压缩格式),然而某些应用需要YUV(YUV是被欧洲电视系统所采用的一种颜色编码方法)这种数字压缩格式的图像,对于这点需求可以用‘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

with picamera.PiCamera() as camera:
    camera.resolution = (1024, 768)
    camera.framerate = 30
    camera.start_preview()
    time.sleep(2)
    camera.capture_sequence([
        'image1.jpg',
        'image2.jpg',
        'image3.jpg',
        'image4.jpg',
        'image5.jpg',
        ])

我们可以细化一下这个例子,可以指定一个循环序列,这样我们就没必要去手动指定每一个文件名了:

import time
import picamera

frames = 60

with picamera.PiCamera() as camera:
    camera.resolution = (1024, 768)
    camera.framerate = 30
    camera.start_preview()
    # 摄像头预热
    time.sleep(2)
    start = time.time()
    camera.capture_sequence([
        'image%02d.jpg' % i
        for i in range(frames)
        ], use_video_port=True)
    finish = time.time()
print('Captured %d frames at %.2ffps' % (
    frames,
    frames / (finish - start)))

然而,这仍然无法满足我们在一定的条件下捕捉任意帧数的图片,为此我们需要生成一个方法,通过调用方法来获取文件名:

import time
import picamera

frames = 60

def filenames():
    frame = 0
    while frame < frames:
        yield 'image%02d.jpg' % frame
        frame += 1

with picamera.PiCamera() as camera:
    camera.resolution = (1024, 768)
    camera.framerate = 30
    camera.start_preview()
    # 摄像头预热
    time.sleep(2)
    start = time.time()
    camera.capture_sequence(filenames(), use_video_port=True)
    finish = time.time()
print('Captured %d frames at %.2ffps' % (
    frames,
    frames / (finish - start)))


首先,因为树莓派的io读写速度优先,其次jpeg比H.264的效率更低(也就说图像字节,若使用h264解码来获取图片,则图片的清晰度会大大提高),所以树莓派无法支持在更高频率(比30fps更高)或更高分辨率(800x600)下快速抓取图片。

如果你打算逐帧来处理图片的话,最好是通过拍摄的视频来解码抓取视频中帧组成图片,而不是通过快速抓取的jpeg来分析图片,或者你也可以通过网络将视频或图片传输到另外的设备上来处理,然而,若你不需要快速的完成图像的处理的话,那么你尽可省去上面说的两个步骤。我们可以通过生成一个新的函数,然后通过并行线程来处理我们的图像刘,这样处理的速度和解码都大大的加快了。

import io
import time
import threading
import picamera

# 创建一个图像处理序列
done = False
lock = threading.Lock()
pool = []

class ImageProcessor(threading.Thread):
    def __init__(self):
        super(ImageProcessor, self).__init__()
        self.stream = io.BytesIO()
        self.event = threading.Event()
        self.terminated = False
        self.start()

    def run(self):
        # 这是一个单独运行的线程
        global done
        while not self.terminated:
            # Wait for an image to be written to the stream
            if self.event.wait(1):
                try:
                    self.stream.seek(0)
                    # 这里去执行图片的处理过程
                    #done=True
                finally:
                    # Reset the stream and event
                    self.stream.seek(0)
                    self.stream.truncate()
                    self.event.clear()
                    # 将处理完的图片加载到序列中。
                    with lock:
                        pool.append(self)

def streams():
    while not done:
        with lock:
            if pool:
                processor = pool.pop()
            else:
                processor = None
        if processor:
            yield processor.stream
            processor.event.set()
        else:
            # 当pool序列为空是,我们等待0.1秒
            time.sleep(0.1)

with picamera.PiCamera() as camera:
    pool = [ImageProcessor() for i in range(4)]
    camera.resolution = (640, 480)
    camera.framerate = 30
    camera.start_preview()
    time.sleep(2)
    camera.capture_sequence(streams(), use_video_port=True)

# 处理完成,释放所有处理序列
while pool:
    with lock:
        processor = pool.pop()
    processor.terminated = True
    processor.join()


5.4、图像的快速捕捉及转换成数据流

下面这个例子是5.5的实例的扩展,我们可以通过摄像头快速捕捉一组图像,并将之转换为网络数据流。服务器的脚本不变,客户端脚本会通过 capture_continuous()方法设置use_video_port来快速获取图片:

import io
import socket
import struct
import time
import picamera

client_socket = socket.socket()
client_socket.connect(('my_server', 8000))
connection = client_socket.makefile('wb')
try:
    with picamera.PiCamera() as camera:
        camera.resolution = (640, 480)
        camera.framerate = 30
        time.sleep(2)
        start = time.time()
        stream = io.BytesIO()
        # 将模式设置为video-port
        for foo in camera.capture_continuous(stream, 'jpeg',
                                             use_video_port=True):
            connection.write(struct.pack('<L', stream.tell()))
            connection.flush()
            stream.seek(0)
            connection.write(stream.read())
            if time.time() - start > 30:
                break
            stream.seek(0)
            stream.truncate()
    connection.write(struct.pack('<L', 0))
finally:
    connection.close()
    client_socket.close()



使用这个实例,我们将以10fps的速度获取一组640x480的图片。但是上面的脚本有些缺陷,这个例子里,是先捕捉图像然后再进行传输(虽然我们尽可能的不缓冲图片直接传输),这样导致了一些延迟,为了更加高效的同时处理图像的捕捉和传输,可以尝试下面这个示例:
import io
import socket
import struct
import time
import threading
import picamera

client_socket = socket.socket()
client_socket.connect(('spider', 8000))
connection = client_socket.makefile('wb')
try:
    connection_lock = threading.Lock()
    pool_lock = threading.Lock()
    pool = []

    class ImageStreamer(threading.Thread):
        def __init__(self):
            super(ImageStreamer, self).__init__()
            self.stream = io.BytesIO()
            self.event = threading.Event()
            self.terminated = False
            self.start()

        def run(self):
            # 这是个独立运行的线程
            while not self.terminated:
                # 等待图像被写入流
                if self.event.wait(1):
                    try:
                        with connection_lock:
                            connection.write(struct.pack('<L', self.stream.tell()))
                            connection.flush()
                            self.stream.seek(0)
                            connection.write(self.stream.read())
                    finally:
                        self.stream.seek(0)
                        self.stream.truncate()
                        self.event.clear()
                        with pool_lock:
                            pool.append(self)

    count = 0
    start = time.time()
    finish = time.time()

    def streams():
        global count, finish
        while finish - start < 30:
            with pool_lock:
                if pool:
                    streamer = pool.pop()
                else:
                    streamer = None
            if streamer:
                yield streamer.stream
                streamer.event.set()
                count += 1
            else:
                # 当缓存pool长度为0时,等待0.1秒
                time.sleep(0.1)
            finish = time.time()

    with picamera.PiCamera() as camera:
        pool = [ImageStreamer() for i in range(4)]
        camera.resolution = (640, 480)
        camera.framerate = 30
        time.sleep(2)
        start = time.time()
        camera.capture_sequence(streams(), 'jpeg', use_video_port=True)

    # 关闭流
    while pool:
        streamer = pool.pop()
        streamer.terminated = True
        streamer.join()

    # 写入一个长度为0的数据包,告知服务器停止
    with connection_lock:
        connection.write(struct.pack('<L', 0))

finally:
    connection.close()
    client_socket.close()

print('Sent %d images in %d seconds at %.2ffps' % (
    count, finish-start, count / (finish-start)))


使用相同的固件,这个脚本最高能够达到15fps的速度来获取数据流,如果使用更新的固件脚本或许能够有更快的速度。

5.5、拍摄录像同时拍摄图像

树莓派的摄像头支持在录制视频的时候,同时拍摄静态图片。但是如果在拍摄模式中尝试获取静态图像,则有可能造成视频的丢帧现象。这是因为在拍摄视频式由于中途为了拍摄静态图像需要改变模式,从而停止了获取录像而造成丢帧。

然而,如果使用use_video_prot参数从拍摄的视频中直接捕获图像的话,则拍摄的视频将不会丢帧,下面这个例子演示了在拍摄视频的同时获取静态图像:

import picamera

with picamera.PiCamera() as camera:
    camera.resolution = (800, 600)
    camera.start_preview()
    camera.start_recording('foo.h264')
    camera.wait_recording(10)
    camera.capture('foo.jpg', use_video_port=True)
    camera.wait_recording(10)
    camera.stop_recording()

上面这段代码演示了,录制20秒的视频,从10秒处捕获一个静态的图像,使用了快速捕捉的参数,所以不会造成视频的丢帧,但若使用非jpeg模式或者使用更高分辨率的话,还是会引起一些丢帧的现象。

5.6、同时录制多种分辨率的视频

树莓派的摄像头支持同时使用多个视频分配器在不同的分辨率进行录制,这个需求可以支持在低分辨率下进行分析,而高分辨率同时进行存储或观看。

下面这个实例演示了利用start_recording()方法的splitter_port参数,开始两个同步线程同时录制图像,每一个图像录制使用不同的分辨率:

import picamera

with picamera.PiCamera() as camera:
    camera.resolution = (1024, 768)
    camera.framerate = 30
    camera.start_recording('highres.h264')
    camera.start_recording('lowres.h264', splitter_port=2, resize=(320, 240))
    camera.wait_recording(30)
    camera.stop_recording(splitter_port=2)
    camera.stop_recording()


树莓派最多支持4个视频分配器(端口编号分为0,1,2,3),普通视频录制默认使用了1号端口分配器,而拍摄静态图像则默认使用0号分配器(use_video_port也是使用0号),同一个视频分配器不能同时进行视频的录制和静态图像的捕获,所以你应该尽量避免占用0号分配器,除非你永远不打算在拍摄视频的同时捕获静态图像。

这个功能支持1.3及以后的版本。

5.7、记录运动矢量诗句

树莓派的摄像头能够输出对运动矢量数据的预估值,可以在录制h264视频的同时将运动矢量数据计算出来并输出到文件,输出运动矢量数据可以使用start_recording方法设置motion_output参数来输出一个矢量数据文件。

import picamera

with picamera.PiCamera() as camera:
    camera.resolution = (640, 480)
    camera.framerate = 30
    camera.start_recording('motion.h264', motion_output='motion.data')
    camera.wait_recording(10)
    camera.stop_recording()


运动数据包含在一个宏块数据,(一段MPEG视频宏表示所包含一块16x16的像素区域)并包括一个额外的数据列。因此,如果在640x480分辨率以上就会产生运动数据,数据长度为41列(640/16+1),30行(480/16)。

单个运动数据的大小为4字节,包括1字节的x矢量,1字节的y向量,和2字节的SAD(Sum of Absolute Differences绝对差值)。因此在上面这个例子中,每一帧数据将产生4920字节的运动数据(41*30*4),假设该视频包含300帧,则运动该数据总大小为1476000字节。

下面这个示例演示了加载运动数据,并将其转换为三维numpy阵列,第一维表示帧,后两维表示数据的行列。将数据机构化以后,可以比较简单的分析运动数的x,y和SAD值:

from __future__ import division

import numpy as np

width = 640
height = 480
cols = (width + 15) // 16
cols += 1 # 这里增加一个额外的列
rows = (height + 15) // 16

motion_data = np.fromfile(
    'motion.data', dtype=[
        ('x', 'i1'),
        ('y', 'i1'),
        ('sad', 'u2'),
        ])
frames = motion_data.shape[0] // (cols * rows)
motion_data = motion_data.reshape((frames, rows, cols))

# 得到数据的第一帧
motion_data[0]

# 获取数据第5帧的x矢量值
motion_data[4]['x']

# 获取数据第10帧的sad值
motion_data[9]['sad']


得到数据的值以后,可以通过毕达哥拉斯定理来计算运动幅度。而SAD值可用于确定视频中的初始基准帧。

以下代码演示了使用从上面那个例子中得到的运动数据,然后使用PIL分析每一帧的图像,生成出一组PNG图像。

from __future__ import division

import numpy as np
from PIL import Image

width = 640
height = 480
cols = (width + 15) // 16
cols += 1
rows = (height + 15) // 16

m = np.fromfile(
    'motion.data', dtype=[
        ('x', 'i1'),
        ('y', 'i1'),
        ('sad', 'u2'),
        ])
frames = m.shape[0] // (cols * rows)
m = m.reshape((frames, rows, cols))

for frame in range(frames):
    data = np.sqrt(
        np.square(m[frame]['x'].astype(np.float)) +
        np.square(m[frame]['y'].astype(np.float))
        ).clip(0, 255).astype(np.uint8)
    img = Image.fromarray(data)
    filename = 'frame%03d.png' % frame
    print('Writing %s' % filename)
    img.save(filename)

我们可以利用picamera.array模块中的PiMotionArray类来简化上面的这个例子:

import numpy as np
import picamera
import picamera.array
from PIL import Image

with picamera.PiCamera() as camera:
    with picamera.array.PiMotionArray(camera) as stream:
        camera.resolution = (640, 480)
        camera.framerate = 30
        camera.start_recording('/dev/null', format='h264', motion_output=stream)
        camera.wait_recording(10)
        camera.stop_recording()
        for frame in range(stream.array.shape[0]):
            data = np.sqrt(
                np.square(stream.array[frame]['x'].astype(np.float)) +
                np.square(stream.array[frame]['y'].astype(np.float))
                ).clip(0, 255).astype(np.uint8)
            img = Image.fromarray(data)
            filename = 'frame%03d.png' % frame
            print('Writing %s' % filename)
            img.save(filename)


最后,使用下面这个例子,可以利用生成的png图像生成一个ffmpeg的动画。(从树莓派上生成这组数据将会话费大量的时间,所以若你希望能够加快分析速度,则需要将数据移植到更快的机器上来进行分析)

avconv -r 30 -i frame%03d.png -filter:v scale=640:480 -c:v libx264 motion.mp4

这个功能支持1.5及以后的版本。

5.8、分割loop视频流

下面这个例子是简历在录制loop视频流的同时也在捕捉图像的安全应用上。和以前一样,用PiCameraCircularIO来让录制的视频流一直保持是最新的几秒视频。在录像的同时,通过分配器端口同时捕捉运动检测算法的程序(也就是说检测视频中有是否有运动的物体切入)。

一旦检测到有运动的物体,则将最后10秒钟的视频写入磁盘,然后录像通过运动检测算法,分割出运动部分的录像。然后循环检测运这里写代码片动,直到不再检测到运动:

import io
import random
import picamera
from PIL import Image

prior_image = None

def detect_motion(camera):
    global prior_image
    stream = io.BytesIO()
    camera.capture(stream, format='jpeg', use_video_port=True)
    stream.seek(0)
    if prior_image is None:
        prior_image = Image.open(stream)
        return False
    else:
        current_image = Image.open(stream)
        # 从current_image到prior_image来检测两张图片是否有运动的物体切入
        # 这一部分作为练习留给读者(哈哈哈)
        result = random.randint(0, 10) == 0
        # 若没有检测到运动,则更新prior_image为当前秒图像
        prior_image = current_image
        return result

def write_video(stream):
    # 同时指定一个文件为loop循环视频缓冲区,在锁定的时候我们不会写入
    with io.open('before.h264', 'wb') as output:
        for frame in stream.frames:
            if frame.frame_type == picamera.PiVideoFrameType.sps_header:
                stream.seek(frame.position)
                break
        while True:
            buf = stream.read1()
            if not buf:
                break
            output.write(buf)
    # 指针指向头则完成loop流的擦除
    stream.seek(0)
    stream.truncate()

with picamera.PiCamera() as camera:
    camera.resolution = (1280, 720)
    stream = picamera.PiCameraCircularIO(camera, seconds=10)
    camera.start_recording(stream, format='h264')
    try:
        while True:
            camera.wait_recording(1)
            if detect_motion(camera):
                print('Motion detected!')
                # 如果我们检测到运动的物体时锁定当前录像,并将loop流分割到after.h264文件中。
                camera.split_recording('after.h264')
                # 录制10秒的运动数据
                write_video(stream)
                # 等待运动消失时,将loop流在移回before.h264中进行录制
                while detect_motion(camera):
                    camera.wait_recording(1)
                print('Motion stopped!')
                camera.split_recording(stream)
    finally:
        camera.stop_recording()


此实例还说明read1()方法用于写入录像数据是一个比较优化的方法

需要注意的是,read1()方法在读写的时候不会返回读写字节的数量。

这个功能支持1.0及以后的版本。

5.9、自定义输出

在picamera的库中,能够接受接受输出到一个文件,或输出到一个IO流中,所以简历自定义输出是非常容易的,而且在某种情况下也非常有用。比如说我们可以构造一个自定义的简单对象,只要对象包含一个write方法,并且方法接受一个简单参数,和一个flush(没有参数)方法都可以被picamera方法所调用,使之成为一个输出方法。

在自定义输出类中,write方法为自定义的输出流方法,它至少会被调用一次被用来接收picamera所抛出的每一帧动画,而无需自建一个编码器。但是需要记住的是,write中不能包含太复杂的业务逻辑,因为该方法的处理必须要迅速且有效(这个方法必须快速处理并在下一帧捕捉之前返回)。

下面这个例子定义了一个非常简单的自定义输出类,并在flush时打印输出的字节:

from __future__ import print_function

import picamera

class MyOutput(object):
    def __init__(self):
        self.size = 0

    def write(self, s):
        self.size += len(s)

    def flush(self):
        print('%d bytes would have been written' % self.size)

with picamera.PiCamera() as camera:
    camera.resolution = (640, 480)
    camera.framerate = 60
    camera.start_recording(MyOutput(), format='h264')
    camera.wait_recording(10)
    camera.stop_recording()


下面这个例子演示了如何通过自定义输出类,来构造一个简单的运动检测程序。我们将捕捉的运动矢量数据作为输出对象。然后将其加载到numpy的数据阵列中进行分析。并打印到控制台来判断是否有运动的物体。由于我们并不需要实际输出到文件,所以在本实例中使用了/dev/null/作为保存的路径:
rom __future__ import division

import picamera
import numpy as np

motion_dtype = np.dtype([
    ('x', 'i1'),
    ('y', 'i1'),
    ('sad', 'u2'),
    ])

class MyMotionDetector(object):
    def __init__(self, camera):
        width, height = camera.resolution
        self.cols = (width + 15) // 16
        self.cols += 1 # 加入一个额外的列
        self.rows = (height + 15) // 16

    def write(self, s):
        # 将运动数据加载到numpy阵列中
        data = np.fromstring(s, dtype=motion_dtype)
        # 计算每个向量的数据
        data = data.reshape((self.rows, self.cols))
        data = np.sqrt(
            np.square(data['x'].astype(np.float)) +
            np.square(data['y'].astype(np.float))
            ).clip(0, 255).astype(np.uint8)
        # 如果运动幅度超过10向量或幅度更大,且总体大于60次,则证明我们捕捉到了运动中的物体。
        if (data > 60).sum() > 10:
            print('Motion detected!')
        # 返回字节数
        return len(s)

with picamera.PiCamera() as camera:
    camera.resolution = (640, 480)
    camera.framerate = 30
    camera.start_recording(
        # 抛弃掉视频数据,但保证我们的格式为h264
        '/dev/null', format='h264',
        # Record motion data to our custom output object
        motion_output=MyMotionDetector(camera)
        )
    camera.wait_recording(30)
    camera.stop_recording()


然后我们也可以利用picamera.array中的PiMotionAnalysis类来简化上面的代码:
import picamera
import picamera.array
import numpy as np

class MyMotionDetector(picamera.array.PiMotionAnalysis):
    def analyse(self, a):
        a = np.sqrt(
            np.square(a['x'].astype(np.float)) +
            np.square(a['y'].astype(np.float))
            ).clip(0, 255).astype(np.uint8)
        # 如果运动幅度超过10向量或幅度更大,且总体大于60次,则证明我们捕捉到了运动中的物体。
        # than 60, then say we've detected motion
        if (a > 60).sum() > 10:
            print('Motion detected!')

with picamera.PiCamera() as camera:
    camera.resolution = (640, 480)
    camera.framerate = 30
    camera.start_recording(
        '/dev/null', format='h264',
        motion_output=MyMotionDetector(camera)
        )
    camera.wait_recording(30)
    camera.stop_recording()


这个功能支持1.5及以后的版本。

5.10、自定义解码器

picamera支持开发者重写或者扩展图像或视频的解码器类,这样在视频的采集解码中可以直接运行您的代码进行解码。

但不同于自定义输出,自定义解码器是相当复杂的,在大多数情况下,我们还是建议使用默认的解码器,因为自定义解码器几乎没有什么好处,可能唯一的好处就是能更改自定义输出时无法更改的缓冲区head(说实话我也没怎么搞懂。。。)。

import picamera
import picamera.mmal as mmal


# 重写PiVideoEncoder类
class MyEncoder(picamera.PiCookedVideoEncoder):
    def start(self, output, motion_output=None):
        self.parent.i_frames = 0
        self.parent.p_frames = 0
        super(MyEncoder, self).start(output, motion_output)

    def _callback_write(self, buf):
        # Only count when buffer indicates it's the end of a frame, and
        # it's not an SPS/PPS header (..._CONFIG)
        if (
                (buf[0].flags & mmal.MMAL_BUFFER_HEADER_FLAG_FRAME_END) and
                not (buf[0].flags & mmal.MMAL_BUFFER_HEADER_FLAG_CONFIG)
            ):
            if buf[0].flags & mmal.MMAL_BUFFER_HEADER_FLAG_KEYFRAME:
                self.parent.i_frames += 1
            else:
                self.parent.p_frames += 1
        # Remember to return the result of the parent method!
        return super(MyEncoder, self)._callback_write(buf)


# Override PiCamera to use our custom encoder for video recording
class MyCamera(picamera.PiCamera):
    def __init__(self):
        super(MyCamera, self).__init__()
        self.i_frames = 0
        self.p_frames = 0

    def _get_video_encoder(
            self, camera_port, output_port, format, resize, **options):
        return MyEncoder(
                self, camera_port, output_port, format, resize, **options)


with MyCamera() as camera:
    camera.start_recording('foo.h264')
    camera.wait_recording(10)
    camera.stop_recording()
    print('Recording contains %d I-frames and %d P-frames' % (
            camera.i_frames, camera.p_frames))


5.11、Raw Bayer 数据捕获

以下文档对我来说太复杂,暂时没搞定怎么翻译。。

5.12、摄像头闪光灯的调用

一些树莓派的摄像头模块支持在拍摄时调用LED闪光灯进行场景补光。对于LED灯可以自行配置GPRO的引脚。

树莓派摄像头有两个可操作的led灯

  • 一个用于驱动LED闪光灯
  • 一个用于标识隐私拍摄的led红灯,在拍照时标识该摄像头被启用。

这些引脚可以通过videoCore设备列表进行查看,首选在使用led灯之前需要安装设备支持的库:

$ sudo apt-get install device-tree-compiler
$ wget http://www.raspberrypi.org/documentation/configuration/images/dt-blob.dts


树莓派支持的设备与树莓派的版本有关,若想支持摄像头,可以参照下面的表格对应自己的树莓派版本进行修改。
树莓派类型 目录
Raspberry Pi Model B revision 1 /videocore/pins_rev1
Raspberry Pi Model A /videocore/pins_rev2
Raspberry Pi Model B revision 2 /videocore/pins_rev2
Raspberry Pi Model A+ /videocore/pins_bplus
Raspberry Pi Model B+ /videocore/pins_bplus
Raspberry Pi 2 Model B /videocore/pins_bplus

根据不同的树莓派类型,你会发现需要修改pin_config和pin_defines,根据pin_config部分,你需要配置led闪光灯的GPIO针脚,然后在pin_defines关联这些针脚与FLASH_0_ENABLE和FLASH_0_INDICATOR进行关联,来控制摄像头的开启与关闭。

例如,我们配置GPIO17作为闪光灯的针脚。那么我们在树莓派2B+对应的配置文件/videocore/pins_bplus中需要添加如下部分。

pin@p17 { function = "output"; termination = "pull_down"; };


GPIO针脚的定义根据 树莓派博通芯片针脚定义进行安装,并在/videocore/pins_rev2/pin_defines中定义出GPIO17为闪光灯pin:
pin-define@FLASH_0_ENABLE {
    type = "internal";
    number = <17>;
};


然后我们需要更新设备,并将我们的配置文件编译成二进制数据写入固件中,参考以下命令行:
dtc -I dts -O dtb dt-blob.dts -o dt-blob.bin
命令行参数解释如下:
  • dtc - 执行编译命令
  • -I dts -通过输入文件来编译设备列表
  • -O dtb -输出二进制文件格式
  • dt-blob.dts -将之设定为默认设备列表文件
  • -o dt-blob.bin -输出的文件

执行完以后命令行会输出一下内容:

DTC: dts->dtb  on file "dt-blob.dts"

但是,如果使用的是NOOBS安装包来安装树莓派的话,需要进行分区恢复,代码如下:
$ sudo mkdir /mnt/recovery
$ sudo mount /dev/mmcblk0p1 /mnt/recovery
$ sudo cp dt-blob.bin /mnt/recovery
$ sudo umount /mnt/recovery
$ sudo rmdir /mnt/recovery

需要注意的是,设备列表的放置位置和文件名都是固定的,不能自定义也不能写错,二进制文件必须被命名为 dt-blob.bin(全部小写),并必须将其放在boot分区的根目录下。 

以上就设置完闪光灯的一系列要求,然后就可以在picamera中开启闪光灯参数了。

import picamera

with picamera.PiCamera() as camera:
    camera.flash_mode = 'on'
    camera.capture('foo.jpg')


你也可以不是用GPIO闪光灯,而使用模块自带的led闪光灯。这不需要任何的硬件改动,虽然禁止了pin_define下设置led灯的驱动( 

相机的LED脚已被定义为使用下拉终止),但你需要设置CAMERA_0_LED和CAMERA_0_LED的参数来手动启动led灯。

pin_define@CAMERA_0_LED {
    type = "internal";
    number = <5>;
};
pin_define@FLASH_0_ENABLE {
    type = "absent";
};

修改成
<code class="hljs rust has-numbering">pin_define@CAMERA_0_LED {
    <span class="hljs-keyword">type</span> = <span class="hljs-string">"absent"</span>;
};
pin_define@FLASH_0_ENABLE {
    <span class="hljs-keyword">type</span> = <span class="hljs-string">"internal"</span>;
    number = <<span class="hljs-number">5</span>>;
};</code>

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值