🍓 第十章 相机模块与视频串流
📄 一句话概括

Python 控制相机的官方库是 picamera2(系统预装)——老库 picamera 已随旧相机栈一起死亡;三步骨架【建配置 → 应用 → 启动】之后,拍照、录像、拿 NumPy 数组各是一行。

📝 本节和前面几节的关系

10-3 的命令行适合【手工拍一张】,Python 版适合【让程序自己拍】——比如配合 7-3 的按钮【按下就拍照】。6-5 的 PEP 668 与 venv 套路在本节原样重演(venv 必须 --system-site-packages);6-2 的脚本写法、6-6 的列表思维(照片文件名按时间生成)都有实战位。10-5 的浏览器串流也要用 picamera2,本节是它的地基。

一、库的换代:picamera 已死,picamera2 当立

库 地位 现状
picamera(老库) 绑定老式 MMAL 相机栈 已废弃,新系统上装了也用不了
picamera2(现役) 官方为 libcamera 栈写的 Python 库 Bookworm 起系统预装,apt 维护

为什么老库必死:它直接调用被删除的旧相机栈——皮之不存毛将焉附。网上老代码 from picamera import PiCamera 在新系统上装不上也跑不动,见到直接换本节写法。

二、又是 PEP 668:装库的老规矩原样适用

# ✅ 正路:apt 装(系统预装过,一般无需执行)
sudo apt install -y python3-picamera2

# ❌ 别做:pip install picamera
#    ① 装的是已废弃的老库;② 即便装 picamera2 也会被 PEP 668 拦下

要在 venv 里用(比如 Flask 项目),6-5 的铁律原样成立:

python3 -m venv venv --system-site-packages   # 不加这个,venv 看不见 apt 装的 picamera2
source venv/bin/activate

验证库在不在:

python3 -c "import picamera2; print(picamera2.__version__ if hasattr(picamera2, '__version__') else 'ok')"

三、三步走骨架:先配置、再应用、后启动

picamera2 与 10-3 命令行的心智模型不同:命令行是【按一次快门】,picamera2 是【把相机开成一条流水线,随时取货】。

from picamera2 import Picamera2

picam2 = Picamera2()                              # ① 建对象
config = picam2.create_still_configuration()      # ② 选一套出厂配置(拍照档)
picam2.configure(config)                          # ③ 应用配置
picam2.start()                                    # ④ 启动流水线

picam2.capture_file("py_photo.jpg")               # ⑤ 取货:存一张 JPEG
picam2.stop()                                     # ⑥ 收工:释放相机

两个出厂配置档:create_still_configuration()(拍照档,高分辨率)与 create_video_configuration()(录像档,1080p 高帧率)。同一时刻只能挂一档,换档要重新 configure——这跟 7-2【一根针同时只能有一个主人】是同一种【独占】思维。

四、录像:一个函数连拍带停

from picamera2 import Picamera2

picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration())
picam2.start()

picam2.start_and_record_video("py_clip.mp4", duration=5)   # 录 5 秒,自动收尾
picam2.stop()

duration=0 或不写=一直录到手动 picam2.stop_recording()。

五、真正的杀手锏:画面就是 NumPy 数组

import time
from picamera2 import Picamera2

picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration())
picam2.start()
time.sleep(1)                     # 让自动曝光先稳一下

frame = picam2.capture_array()    # 当前画面直接变成 NumPy 数组!
print(frame.shape)                # 例如 (1080, 1920, 3):高、宽、三通道颜色
picam2.stop()

这一行是 picamera2 的价值所在:画面从【文件】变成了【变量】——逐帧分析、认颜色、找物体全由此展开(进阶与 OpenCV 打通,10-6 的 USB 摄像头天然走这条数组路)。注意 shape 是(高, 宽, 通道),跟直觉的【宽 × 高】顺序相反,用错了一排查半天。

六、动手 Demo:按钮触发拍照——第七章与本章合体

接线:按钮一端接 BCM 27(物理针 13)、另一端接 GND(7-3 的上拉规矩原样成立)。

from gpiozero import Button
from picamera2 import Picamera2
import time

button = Button(27)                       # 内部上拉,按下接 GND(7-3 的默认接法)
picam2 = Picamera2()
picam2.configure(picam2.create_still_configuration())
picam2.start()
time.sleep(1)                             # 曝光稳定

count = 0

def take_photo():
    global count
    count += 1
    name = time.strftime("photo_%Y%m%d_%H%M%S") + ".jpg"   # 文件名带时间,永不覆盖
    picam2.capture_file(name)
    print("已保存:", name)

button.when_pressed = take_photo           # 记得:传函数本身,不加括号(7-3 的坑)

try:
    print("按按钮拍照,Ctrl+C 退出")
    while True:
        time.sleep(0.1)
finally:
    picam2.stop()                          # 无论怎么退出,都把相机还回去

看到什么算成功:每按一次按钮,终端打印【已保存: photo_20260924_××××××.jpg】,文件真实存在且能打开;Ctrl+C 退出后相机被释放(再跑别的相机程序不报占用)。

读结果:三个旧知识各就各位——when_pressed = take_photo 不加括号(7-3:把动作交出去,不是现在执行)、global count(6-5:函数内改外面要声明)、文件名用时间字符串(6-6:strftime 拼接)。

七、30 秒实验:一帧 1080p 画面在内存里多大

capture_array() 返回 1080p(1920 × 1080)三通道数组,每个像素 3 字节:

  1. 一帧多少字节?合多少 MiB?
  2. 若每秒处理 30 帧,每秒要过多少数据?

参考答案:1. 1920 × 1080 × 3 = 6,220,800 字节 ≈ 5.9 MiB。2. × 30 = 186,624,000 字节/秒 ≈ 178 MiB/秒——这解释了两件事:为什么树莓派做逐帧处理要挑低分辨率、为什么 picamera2 的录像档默认给你硬件编码好的 h264 流而不是裸数组。

八、常见误区

  1. 【pip install picamera 装库】 —— 装到的是死库;新系统上正确做法是 apt 装 python3-picamera2(通常已预装)。
  2. 【venv 里 import 报 ModuleNotFoundError】 —— 建 venv 时漏了 --system-site-packages;6-5 的坑,GPIO 项目和相机项目都中招。
  3. 【capture_file 前忘了 start()】 —— 流水线没开就取货,报错或挂起;configure → start → capture 的顺序是骨架,别跳步。
  4. 【每拍一张就新建一次 Picamera2()】 —— 对象复用即可;反复建销会慢且容易跟其他进程抢相机(同一时刻只能有一个主人)。
  5. 【shape 是(宽, 高, 通道)】 —— NumPy 是(行, 列, 通道)=(高, 宽, 通道),处理像素坐标 (x, y) 时别反。
自测三问(点开对答案) 1. 老代码里 `from picamera import PiCamera`,怎么迁移? 2. 两套出厂配置档分别叫什么、给谁用? 3. 按钮拍照 Demo 里 `button.when_pressed = take_photo` 为什么不写括号? **参考答案** 1. 换 `from picamera2 import Picamera2`,并把【直接调用拍照】改写成【configure → start → capture_file → stop】骨架;老库绑定已删除的旧栈,装不上也跑不动。 2. `create_still_configuration()` 拍照档(高分辨率低帧率)、`create_video_configuration()` 录像档(1080p 高帧率);同一时刻只能挂一档,换档重新 configure。 3. 写括号=立刻执行一次并把返回值 None 赋给事件——**回调要传函数本身**(7-3 的名场面)。

相关笔记