🍓 第十一章 AI 实验范例(一):计算机视觉+AI 识别-OpenCV+YOLO
📄 一句话概括

OpenCV 的世界里图就是 NumPy 数组:读图、存图、灰度、缩放、裁剪都是【换个角度看数组】——认死两条规矩(BGR 顺序、高在前宽在后),后面的 AI 实验全是这套基本功的组合。

📝 本节和前面几节的关系

11-2 装好了库,本节练基本功。10-4 的 capture_array() 已经让你见过【画面=数组】(shape 是(高, 宽, 3));本节把这条路走全——picamera2 负责把画面递进来,OpenCV 负责在数组上做手术。6-6 的切片规则在这里原样复用(ROI 裁剪就是二维切片)。

一、两条刻进肌肉记忆的规矩

  1. BGR,不是 RGB:OpenCV 按蓝、绿、红顺序存颜色通道(历史原因,改不了了)。(255, 0, 0) 在 OpenCV 里是纯蓝。与别的库交换数据时用 cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 转换——颜色诡异发蓝发红,八成是忘了这条
  2. shape 是(高, 宽, 通道):img[100, 200] 是【第 100 行、第 200 列】的像素——行号在前。切片同理:img[y1:y2, x1:x2],写反了裁出来的不是你想要的区域(还不报错,是最阴的一类 bug)

二、读图、存图、看图

import cv2

img = cv2.imread("photo.jpg")            # 读图 → NumPy 数组;路径错返回 None(不报错!)
print(type(img), img.shape)              # <class 'numpy.ndarray'> (480, 640, 3)

cv2.imwrite("copy.jpg", img)             # 存图:按扩展名自动选格式

两个坑:

三、三把常用的刀:灰度、缩放、裁剪

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)     # 灰度:3 通道变 1 通道
print(gray.shape)                                 # (480, 640) —— 通道没了

small = cv2.resize(img, (320, 240))              # 缩放:注意参数是【宽, 高】!
print(small.shape)                                # (240, 320, 3) —— 又变回【高, 宽】

roi = img[100:200, 300:500]                      # 裁剪:行在前(y 范围),列在后(x 范围)
print(roi.shape)                                  # (100, 200, 3) —— 高 100 宽 200

resize 是本节最容易写反的地方:参数顺序是 (宽, 高),而 shape 显示是 (高, 宽)——同一个尺寸的两种表达,OpenCV 里并存,写错不报错只是图变形。

灰度图为什么 AI 爱用:通道从 3 变 1,数据量直接砍三分之二(0.9 MB → 0.3 MB,按 11-2 的账);而【有没有脸】【有没有物体】这类判断不太需要颜色——11-4 的人脸检测第一步就是转灰度。

四、视频循环骨架:所有实时视觉程序的模板

import cv2
import numpy as np
from picamera2 import Picamera2

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

while True:
    frame = picam2.capture_array()       # 取一帧(RGB888 格式)
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)   # picamera2 给 RGB,OpenCV 要 BGR!
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)   # 在这行之后随意加工……
    print(gray.shape)
    break                                # 无头环境先跑通一轮;桌面版换 while True + waitKey

两个要点:

用 USB 摄像头的版本(10-6 的 OpenCV 取流):把前四行换成 cap = cv2.VideoCapture(0)、取帧换 ret, frame = cap.read()——框架不变,帧的来源随意,这就是【数组是通用语言】的威力。

五、动手 Demo:给摄像头画面做【三连加工】

把摄像头一帧画面加工成三个文件,验收基本功:

# cv_basic.py
import cv2
from picamera2 import Picamera2

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

frame = picam2.capture_array()                       # RGB 数组
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)       # 转 BGR(OpenCV 世界)

h, w = frame.shape[:2]
small  = cv2.resize(frame, (w // 2, h // 2))         # 缩一半
gray   = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)     # 灰度
roi    = frame[h//4 : h*3//4, w//4 : w*3//4]         # 中央 1/2 区域

cv2.imwrite("f_small.jpg", small)
cv2.imwrite("f_gray.jpg", gray)
cv2.imwrite("f_roi.jpg", roi)
print("原尺寸:", frame.shape, "| 缩放:", small.shape,
      "| 灰度:", gray.shape, "| 裁剪:", roi.shape)
picam2.stop()

看到什么算成功:当前目录多出三个 jpg;打印的四组 shape 分别是(原尺寸)、(一半)、(少一维)、(中央区域)。

读结果:对照数字读——缩放版【高宽各减半】、灰度版【shape 少了第三维】、裁剪版【行范围 h/4 到 3h/4=高一半,列同理】。每个数字都能用 shape 规矩推出来,推不出来回第二节重读。

六、30 秒实验:裁剪坐标的账

对一张 640 × 480 的图执行 img[100:200, 300:500]:

  1. 裁出区域的尺寸是多少?
  2. 想裁出【右下角 160 × 120】的区域,切片怎么写?

参考答案:1. shape = (100, 200, 3)——行范围 200−100=100(高),列范围 500−300=200(宽)。2. img[360:480, 480:640]——高 480−360=120、宽 640−480=160,含头不含尾(6-6 的切片规矩原样适用)。

七、常见误区

  1. 【OpenCV 里红色是 (255, 0, 0)】 —— 那是蓝!BGR 顺序,红色是 (0, 0, 255)。
  2. 【img[300:500, 100:200] 裁出宽 400 的区域】 —— 切片是行在前:这是【第 300~500 行、第 100~200 列】=高 200 宽 100;x、y 写反不报错。
  3. 【resize(frame, (480, 640))】 —— resize 参数是(宽, 高),与 shape 的显示顺序相反;写反只是图变形。
  4. 【imread 返回的图可以直接用】 —— 路径写错它只返回 None 不报错,炸点在后面;读后先判 img is None。
  5. 【picamera2 的帧直接喂 OpenCV 函数】 —— 中间要 cv2.cvtColor(frame, cv2.COLOR_RGB2BGR);省了这步,颜色通道全反。
自测三问(点开对答案) 1. `img[240, 320]` 和 `img[320, 240]` 是同一个像素吗? 2. 为什么 AI 实验常先转灰度? 3. 无头 SSH 环境里怎么验收视觉结果? **参考答案** 1. 不是。shape 是(高, 宽)=(行, 列):前者是第 240 行第 320 列,后者是第 320 行第 240 列——**写反了拿的是另一个位置的像素**。 2. 通道 3→1,数据量砍三分之二;而检测【有没有脸/物体】不太依赖颜色——更快且够用(11-4 第一步就是它)。 3. imshow 开不了窗口;用 `imwrite` 存盘后 `scp` 拷回 Windows 看(3-6),或干脆打印 shape/判断结果。

相关笔记