3、Python与OpenCV基础:环境配置与图像采集

好,咱们进入实战前的最后一块拼图——Python环境和OpenCV的配置。说实话,这部分我踩过的坑比后面写算法时还多。你想想看,环境没配好,后面代码写得再漂亮也跑不起来,对吧?

3.1 Miniforge:轻量级Python环境管理

我个人习惯用Miniforge而不是Anaconda。为什么?因为Jetson是ARM架构,Anaconda官方对ARM的支持一直不太友好。Miniforge基于conda-forge,在Jetson上跑得特别顺。

安装步骤:

# 下载Miniforge安装脚本
wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-aarch64.sh

# 运行安装
bash Miniforge3-Linux-aarch64.sh

# 安装完成后,激活base环境
source ~/miniforge3/bin/activate

# 创建项目专用环境(我习惯用Python 3.8,兼容性最好)
conda create -n jetson_det python=3.8
conda activate jetson_det
我的小技巧: 每次创建新环境时,我都会顺手把pip源换成清华的。不然下载包的速度,嗯,你懂的。
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

3.2 OpenCV编译安装:带CUDA加速

这里要重点说一下。Jetson上直接pip install opencv-python装出来的版本,不带CUDA加速。说白了,就是没有用上GPU。我在项目中遇到过,同样的模型,没加速的版本跑起来只有5帧,加速后直接飙到30帧。

编译前的准备工作:

# 安装依赖
sudo apt-get update
sudo apt-get install -y build-essential cmake git pkg-config
sudo apt-get install -y libjpeg-dev libtiff-dev libpng-dev
sudo apt-get install -y libavcodec-dev libavformat-dev libswscale-dev
sudo apt-get install -y libgtk2.0-dev libcanberra-gtk-module
sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev

编译OpenCV(带CUDA):

# 下载源码
git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git

cd opencv
mkdir build && cd build

cmake -D CMAKE_BUILD_TYPE=RELEASE \
      -D CMAKE_INSTALL_PREFIX=/usr/local \
      -D WITH_CUDA=ON \
      -D WITH_CUDNN=ON \
      -D OPENCV_DNN_CUDA=ON \
      -D ENABLE_FAST_MATH=1 \
      -D CUDA_FAST_MATH=1 \
      -D WITH_CUBLAS=1 \
      -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \
      -D BUILD_opencv_python3=ON \
      -D PYTHON3_EXECUTABLE=$(which python3) \
      -D PYTHON3_INCLUDE_DIR=$(python3 -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())") \
      -D PYTHON3_PACKAGES_PATH=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())") \
      ..

# 编译(-j4表示用4个核心,Jetson Nano建议用-j2)
make -j4
sudo make install
sudo ldconfig
注意: 编译过程大概需要1-2小时。我曾经因为散热没做好,编译到一半Jetson过热关机了...所以建议用风扇吹着,或者分步编译。

验证安装:

python3 -c "import cv2; print(cv2.getBuildInformation())"

看到输出中有CUDA: YESCUDNN: YES,就说明成功了。

3.3 摄像头调用与图像采集

Jetson上常用的摄像头有两种:USB摄像头和CSI摄像头。我个人更推荐CSI摄像头,延迟低、带宽大。但USB摄像头胜在方便,随便插个UVC摄像头就能用。

USB摄像头调用:

import cv2

# 打开摄像头(0表示第一个摄像头)
cap = cv2.VideoCapture(0)

# 设置分辨率(我一般设640x480,平衡速度和精度)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv2.CAP_PROP_FPS, 30)

while True:
    ret, frame = cap.read()
    if not ret:
        print("无法获取图像")
        break
    
    cv2.imshow("Camera", frame)
    
    # 按'q'退出
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

CSI摄像头调用(Jetson专用):

import cv2

# 使用GStreamer管道
def gstreamer_pipeline(
    sensor_id=0,
    capture_width=640,
    capture_height=480,
    display_width=640,
    display_height=480,
    framerate=30,
    flip_method=0,
):
    return (
        "nvarguscamerasrc sensor-id=%d ! "
        "video/x-raw(memory:NVMM), width=(int)%d, height=(int)%d, framerate=(fraction)%d/1 ! "
        "nvvidconv flip-method=%d ! "
        "video/x-raw, width=(int)%d, height=(int)%d, format=(string)BGRx ! "
        "videoconvert ! "
        "video/x-raw, format=(string)BGR ! appsink"
        % (
            sensor_id,
            capture_width,
            capture_height,
            framerate,
            flip_method,
            display_width,
            display_height,
        )
    )

cap = cv2.VideoCapture(gstreamer_pipeline(), cv2.CAP_GSTREAMER)

while True:
    ret, frame = cap.read()
    if not ret:
        break
    
    cv2.imshow("CSI Camera", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
避坑指南: 我曾经在CSI摄像头上折腾了一整天,最后发现是排线没插紧。Jetson的CSI接口比较紧,插的时候要听到"咔哒"一声才算到位。

3.4 图像预处理:缩放、归一化、色彩空间转换

图像预处理是目标检测前的关键步骤。说白了,就是把原始图像转换成模型能"看懂"的格式。我见过不少新手,模型训练得好好的,一到推理就出问题,十有八九是预处理没做对。

图像缩放:

import cv2
import numpy as np

# 读取图像
img = cv2.imread("test.jpg")

# 方法1:直接缩放(会变形)
resized = cv2.resize(img, (224, 224))

# 方法2:保持宽高比缩放(推荐)
def resize_with_aspect_ratio(img, target_size=224):
    h, w = img.shape[:2]
    scale = min(target_size / h, target_size / w)
    new_w = int(w * scale)
    new_h = int(h * scale)
    resized = cv2.resize(img, (new_w, new_h))
    
    # 填充到正方形
    delta_w = target_size - new_w
    delta_h = target_size - new_h
    top, bottom = delta_h // 2, delta_h - delta_h // 2
    left, right = delta_w // 2, delta_w - delta_w // 2
    color = [0, 0, 0]
    padded = cv2.copyMakeBorder(resized, top, bottom, left, right, 
                                cv2.BORDER_CONSTANT, value=color)
    return padded

img_padded = resize_with_aspect_ratio(img, 224)

归一化:

# 方法1:简单归一化(0-1范围)
normalized = img_padded.astype(np.float32) / 255.0

# 方法2:均值标准差归一化(YOLO常用)
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
normalized = (img_padded.astype(np.float32) / 255.0 - mean) / std

# 方法3:OpenCV的blobFromImage(一步到位)
blob = cv2.dnn.blobFromImage(img_padded, 1/255.0, (224, 224), 
                             swapRB=True, crop=False)
关键点: 归一化的均值和标准差必须和训练时保持一致。我见过有人训练用ImageNet的均值,推理时却用了自己的均值,结果模型完全失效。

色彩空间转换:

# BGR转RGB(OpenCV默认是BGR,模型通常用RGB)
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# BGR转灰度
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# BGR转HSV(用于颜色检测)
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 完整的预处理流水线
def preprocess_image(img_path, input_size=640):
    # 读取图像
    img = cv2.imread(img_path)
    
    # 缩放
    img = resize_with_aspect_ratio(img, input_size)
    
    # BGR转RGB
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    # 归一化
    img = img.astype(np.float32) / 255.0
    
    # 增加batch维度(模型输入要求)
    img = np.expand_dims(img, axis=0)
    
    return img
预处理步骤 常用参数 注意事项
缩放 640x640(YOLO)、224x224(分类) 保持宽高比,避免变形
归一化 除以255或使用均值标准差 必须与训练时一致
色彩空间 BGR→RGB OpenCV默认BGR,模型通常用RGB
数据类型 float32 模型推理通常需要浮点数
性能提示: 在Jetson上,预处理尽量用OpenCV的C++后端,不要用Python的PIL。我测试过,同样的缩放操作,OpenCV比PIL快3倍以上。

好了,环境配好了,摄像头能用了,预处理也搞定了。下一章咱们就开始真正跑目标检测模型。说实话,前面这些准备工作虽然枯燥,但打好基础后面就顺了。你想想看,要是环境没配好,后面代码写得再漂亮也跑不起来,对吧?