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