Jetson TX2
- 系统版本:JetPack 4.6.1 (对应 L4T R32.6.1)
- 架构:aarch64(ARM 64)
- CUDA 版本:10.2
- python 3.6
背景介绍
最近在做关于视频流处理方面项目,接触到NVIDIA
的边缘盒子,就尝试着看在边缘盒子上能不能运行 yolov8 或 Google 的 mediapipe 。
yolov8 是无法在这个版本的盒子运行(无法使用 cuda,CPU 推理卡的厉害),而且 Jetpack 4.6.1 版本的盒子无法升级 5.xx 版本,yolov8 最低需要 Python 3.8 版本,所以就选择 mediapipe 。
使用 mediapipe 最好就是使用 docker 来处理,自己在系统安装环境非常麻烦(CUDA10.2 + aarch64 问题),而且坑特别多根本跳不完的坑。
设置 docker
参考资料
-
安装nano(或任何其他文本编辑器):
sudo apt-get install nano
-
开放时间
/etc/docker/daemon.json
:sudo nano /etc/docker/daemon.json
-
编辑自:
json
{"runtimes": {"nvidia": {"path": "nvidia-container-runtime","runtimeArgs": []}} }
到:
json
{"runtimes": {"nvidia": {"path": "nvidia-container-runtime","runtimeArgs": []}}, "default-runtime": "nvidia" }
-
重启docker:
sudo systemctl restart docker
-
现在需要将 docker 添加用户到组:
sudo usermod -aG docker $USER
拉镜像
Python | OpenCV | mediapipe | 镜像标签 | 镜像文件 |
---|---|---|---|---|
3.6.9 | 4.8.0 | 0.8.5 | l4t32.7.1-py3.6.9-ocv4.8.0-mp0.8.5 | Dockerfile |
docker pull ghcr.io/lanzani/mediapipe:l4t32.7.1-py3.6.9-ocv4.8.0-mp0.8.5
启动容器
参考资料
显示支持
运行容器之前:
export DISPLAY=:0
xhost +
然后运行你的容器。
启动命令:
sudo docker run -it --rm \--runtime nvidia \--network host \-v /tmp/argus_socket:/tmp/argus_socket \-v /etc/enctune.conf:/etc/enctune.conf \-v /etc/nv_tegra_release:/etc/nv_tegra_release \-v /tmp/nv_jetson_model:/tmp/nv_jetson_model \-v /tmp/.X11-unix/:/tmp/.X11-unix \-v /tmp/.docker.xauth:/tmp/.docker.xauth \-v /home/ubuntu/google-media/data:/opt \--device /dev/snd \--device /dev/bus/usb \-e DISPLAY=:0 \-e XAUTHORITY=/tmp/.docker.xauth \
ghcr.io/lanzani/mediapipe:l4t32.7.1-py3.6.9-ocv4.8.0-mp0.8.5 \
python3.6 /opt/pcv3.py
参数说明
1️⃣ 基本选项
参数 | 作用 | 说明 |
---|---|---|
sudo |
提权执行 docker | 因为 Docker 默认需要 root 权限,或者用户在 docker 组中 |
docker run |
启动容器 | 核心命令 |
-it |
交互式终端 | -i 保持 STDIN 打开,-t 分配伪终端,-t 换成 -d 可后台运行 |
--rm |
容器退出后自动删除 | 避免堆积临时容器,测试使用,没问题后可删除这个选择 |
2️⃣ GPU/NVIDIA 相关
参数 | 作用 | 说明 |
---|---|---|
--runtime nvidia |
使用 NVIDIA Docker runtime | 让容器访问 GPU。Jetson 的 L4T 环境通常也需要这个 |
--device /dev/snd |
音频设备 | 允许容器访问宿主机的音频设备,方便 TTS 或播放 |
--device /dev/bus/usb |
USB 设备 | 允许访问 USB 摄像头或其他设备 |
3️⃣ 网络
参数 | 作用 | 说明 |
---|---|---|
--network host |
容器共享宿主机网络 | 对于访问本地摄像头、USB 或 V4L2 流媒体方便 |
4️⃣ 挂载卷(Volume)
Volume 主要是让容器访问宿主机文件或设备。
参数 | 作用 | 说明 |
---|---|---|
-v /tmp/argus_socket:/tmp/argus_socket |
Argus 摄像头 socket | Jetson 的摄像头 API(Argus)需要这个 socket |
-v /etc/enctune.conf:/etc/enctune.conf |
GPU/编码配置 | Jetson 的编码器调优文件 |
-v /etc/nv_tegra_release:/etc/nv_tegra_release |
系统版本信息 | 让容器识别 Jetson 系统版本 |
-v /tmp/nv_jetson_model:/tmp/nv_jetson_model |
Jetson 硬件信息 | 某些 SDK 需要读取型号信息 |
-v /tmp/.X11-unix/:/tmp/.X11-unix |
X11 socket | 允许 GUI 程序在宿主机显示 |
-v /tmp/.docker.xauth:/tmp/.docker.xauth |
X11 认证 | 避免权限问题 |
-v /home/ubuntu/google-media/data:/opt |
数据文件 | 容器内 /opt 可访问宿主机数据(你写的 pcv3.py 就在这里) |
5️⃣ 显示相关环境变量
参数 | 作用 | 说明 |
---|---|---|
-e DISPLAY=:0 |
X11 显示 | 指定 GUI 输出到宿主机显示器 |
-e XAUTHORITY=/tmp/.docker.xauth |
X11 认证 | 配合挂载 .docker.xauth 文件使用 |
6️⃣ 镜像和命令
参数 | 作用 | 说明 |
---|---|---|
ghcr.io/lanzani/mediapipe:l4t32.7.1-py3.6.9-ocv4.8.0-mp0.8.5 |
Docker 镜像 | 这个镜像包含 MediaPipe、OpenCV 4.8、Python3.6.9,适配 Jetson L4T 32.7.1 |
python3.6 /opt/pcv3.py |
容器启动后执行 | 执行你挂载在 /opt 的 Python 文件 |
✅ 总结
这个命令做了几件事情:
- 启动一个基于 Jetson 的 MediaPipe 镜像。
- 容器可以访问 GPU、音频和 USB 设备。
- 容器共享宿主机网络和 X11 显示,可以直接用 GUI。
- 容器自动挂载宿主机文件和配置,运行 Python 脚本
/opt/pcv3.py
。
测试代码
pcv3.py
# -*- coding: utf-8 -*-
import cv2
import sys
import os
import mediapipe as mpprint("pwd:", os.getcwd())print("mediapipe pwd:", mp.__file__)# === RTSP 地址(请改成你的摄像头地址) ===
RTSP_URL = "rtsp://admin:123456@192.168.0.1:554/stream1"# === 初始化 MediaPipe Pose ===
mp_drawing = mp.solutions.drawing_utils
mp_pose = mp.solutions.pose# 启动 RTSP 视频流
cap = cv2.VideoCapture(RTSP_URL)if not cap.isOpened():print("xxxx stop")exit()print("start")# 初始化 Pose 模型(static_image_mode=False 表示视频流)
with mp_pose.Pose(static_image_mode=False,model_complexity=1,min_detection_confidence=0.5,min_tracking_confidence=0.5
) as pose:while True:success, frame = cap.read()if not success:print("dddiu")break# 转换为 RGB(MediaPipe 要求输入 RGB)image_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)# 推理results = pose.process(image_rgb)# 可视化骨架if results.pose_landmarks:mp_drawing.draw_landmarks(frame, results.pose_landmarks, mp_pose.POSE_CONNECTIONS,landmark_drawing_spec=mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2),connection_drawing_spec=mp_drawing.DrawingSpec(color=(255, 0, 0), thickness=2))# 显示画面#cv2.imshow('Pose Detection (RTSP)', frame)# 按 q 退出if cv2.waitKey(1) & 0xFF == ord('q'):breakcap.release()
cv2.destroyAllWindows()
开合跳代码
通过关键点,计算开合跳数量
# -*- coding: utf-8 -*-
import cv2
import threading
import queue
import math
import time
import requests
import mediapipe as mp# ======================
# 配置
# ======================
RTSP_URL = "rtsp://admin:123456@127.0.0.1:554/stream1?transport=tcp"
SERVER_URL = "http://127.0.0.1:8080/upload"
JPEG_QUALITY = 80 # JPEG 编码质量
QUEUE_MAXSIZE = 1 # 队列长度 1,保持最新帧# ======================
# 队列
# ======================
frame_queue = queue.Queue(maxsize=QUEUE_MAXSIZE)
upload_queue = queue.Queue(maxsize=QUEUE_MAXSIZE)# 计算两点之间的欧式距离(用于判断双脚分开的程度)
def calc_distance(p1, p2):return math.sqrt((p1.x - p2.x)**2 + (p1.y - p2.y)**2)# ======================
# 上传函数
# ======================
def upload_jpeg_bytes(image_bytes, server_url):try:files = {'image': ('uploaded_image.jpg', image_bytes, 'image/jpeg')}response = requests.post(server_url, files=files, timeout=2)response.raise_for_status()except Exception as e:print("上传失败:", e)# ======================
# 抓帧线程
# ======================
def frame_grabber():cap = cv2.VideoCapture(RTSP_URL)if not cap.isOpened():print("无法打开 RTSP 流")returnwhile True:success, frame = cap.read()if not success:continue# 丢弃旧帧if frame_queue.full():try:frame_queue.get_nowait()except queue.Empty:passframe_queue.put(frame)# ======================
# 上传线程
# ======================
def uploader():while True:try:frame_bytes = upload_queue.get()upload_jpeg_bytes(frame_bytes, SERVER_URL)except Exception as e:print("上传线程异常:", e)# ======================
# 主线程处理
# ======================
def main_loop():mp_drawing = mp.solutions.drawing_utilsmp_pose = mp.solutions.posejumping_jacks = 0 # 开合跳次数计数器stage = "down" # 动作阶段:down=手脚收拢,up=手脚张开with mp_pose.Pose(static_image_mode=False,model_complexity=1,min_detection_confidence=0.5,min_tracking_confidence=0.5) as pose:while True:if frame_queue.empty():time.sleep(0.001)continueframe = frame_queue.get()# 转 RGBimage_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)# Mediapipe 推理results = pose.process(image_rgb)# 绘制骨架if results.pose_landmarks:mp_drawing.draw_landmarks(frame,results.pose_landmarks,mp_pose.POSE_CONNECTIONS,landmark_drawing_spec=mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2),connection_drawing_spec=mp_drawing.DrawingSpec(color=(255, 0, 0), thickness=2))# 如果检测到人体关键点if results.pose_landmarks:landmarks = results.pose_landmarks.landmark# 取出关键点(左右手腕、鼻子、左右脚踝)left_wrist = landmarks[15]right_wrist = landmarks[16]nose = landmarks[0] # 头顶附近点left_ankle = landmarks[27]right_ankle = landmarks[28]# ===== 手部逻辑 =====# 当手腕的 y 坐标小于鼻子时,说明手举过头顶hands_up = left_wrist.y < nose.y and right_wrist.y < nose.y# ===== 脚部逻辑 =====# 计算左右脚踝之间的距离ankle_dist = calc_distance(left_ankle, right_ankle)print(r"ankle_dis",ankle_dist)# 如果距离大于阈值,说明双脚分开legs_open = ankle_dist > 0.10# ===== 动作阶段判断 =====# 如果手举起且脚分开,认为处于 "up" 状态if hands_up and legs_open:stage = "up"# 如果手放下且脚收拢,并且之前是 "up",说明完成一次开合跳elif not hands_up and not legs_open and stage == "up":stage = "down"jumping_jacks += 1 # 次数 +1# 在左上角显示开合跳次数cv2.putText(frame,f'Jumping Jacks: {jumping_jacks}', # 显示文本(20, 50), # 位置 (x,y)cv2.FONT_HERSHEY_SIMPLEX, # 字体1.2, # 字号(0, 255, 0), # 颜色 (绿色)3 # 粗细)# 编码 JPEG 并放入上传队列_, buffer = cv2.imencode('.jpg', frame, [int(cv2.IMWRITE_JPEG_QUALITY), JPEG_QUALITY])frame_bytes = buffer.tobytes()if upload_queue.full():try:upload_queue.get_nowait()except queue.Empty:passupload_queue.put(frame_bytes)# ======================
# 启动线程
# ======================
if __name__ == "__main__":threading.Thread(target=frame_grabber, daemon=True).start()threading.Thread(target=uploader, daemon=True).start()main_loop()
注意事项
一、目录挂载问题
-v /home/ubuntu/google-media/data:/opt \
将宿主机的测试 python
代码挂载到容器 opt
目录下,所以/home/ubuntu/google-media/data
这个目录是自己创建的。
二、Can't find file: mediapipe/modules/pose_detection/pose_detection.tflite
Mediapipe 模型加载问题,没有找到这个文件,python3.6 /opt/pcv3.py
是在容器的 opt
目录下执行的,在当前工作目录下默认是没有这个 mediapipe/modules/pose_detection/pose_detection.tflite
文件,简单粗暴解决办法:
通过 docker exec 容器ID
/bin/bash
,进入容器创建 /opt/mediapipe/modules/pose_detection/
从 dist-packages
拷贝一个份
mkdir -p /opt/mediapipe/modules/pose_detection/
cp /usr/local/lib/python3.6/dist-packages/mediapipe/modules/pose_detection/pose_detection.tflite /opt/mediapipe/modules/pose_detection/
启动日志:
WARNING: Logging before InitGoogleLogging() is written to STDERR
I20251016 06:39:17.998665 24 gl_context_egl.cc:163] Successfully initialized EGL. Major : 1 Minor: 5
I20251016 06:39:18.044948 42 gl_context.cc:331] GL version: 3.2 (OpenGL ES 3.2 NVIDIA 32.6.1)
I20251016 06:39:18.045231 24 gl_context_egl.cc:163] Successfully initialized EGL. Major : 1 Minor: 5
I20251016 06:39:18.072178 43 gl_context.cc:331] GL version: 3.2 (OpenGL ES 3.2 NVIDIA 32.6.1)
W20251016 06:39:18.075790 42 tflite_model_loader.cc:32] Trying to resolve path manually as GetResourceContents failed: ; Can't find file: mediapipe/modules/pose_detection/pose_detection.tflite
INFO: Created TensorFlow Lite delegate for GPU.
三、eglChooseConfig() returned no matching EGL configuration for RGBA8888 D16 ES2 request
AI 回答:
MediaPipe 默认会尝试用 GPU (OpenGL ES) 模式运行 Pose 模型。
但 Jetson TX2 某些驱动 / EGL 库 版本(尤其 JetPack 4.6.x)下:
- EGL 配置不匹配
- X server 或 DISPLAY 环境未正确设置
- 无法创建 GLES3 / GLES2 上下文
最终 GPU 初始化失败,导致 MediaPipe Pose 图计算图无法启动。
实际情况是在启动 docker 容器时缺少了一些必要参数:
-v /tmp/argus_socket:/tmp/argus_socket \
-v /etc/enctune.conf:/etc/enctune.conf \
-v /etc/nv_tegra_release:/etc/nv_tegra_release \
-v /tmp/nv_jetson_model:/tmp/nv_jetson_model \
-v /tmp/.X11-unix/:/tmp/.X11-unix \
-v /tmp/.docker.xauth:/tmp/.docker.xauth \
--device /dev/snd \
--device /dev/bus/usb \
-e DISPLAY=:0 \
-e XAUTHORITY=/tmp/.docker.xauth \
参考资料
- Jetson torch 下载中心
- Jetson 开发文档
- 手动安装 Google MediaPipe Github
- Google MediaPipe
扩展信息
查看版本信息
cat /etc/nv_tegra_release
查看系统架构
uname -m
查看 cuda 信息
nvcc --version
查看 CPU、GPU、内存使用情况
sudo tegrastats
诊断系统 OpenCV 是否支持 cuda,也可以自容器执行
python3.6 -c "import cv2; print('--- System OpenCV Info ---'); print(cv2.getBuildInformation())"
查看 mediapipe 版本信息
python3.6 -c "import mediapipe as mp; print(mp.__version__)"
PyTorch(适用于 JetPack 4.6.1):是一个针对使用 GPU 和 CPU 进行深度学习而优化的张量库
NVIDIA JetPack™ :是 NVIDIA Jetson™ 平台的官方软件堆栈,为您提供构建 AI 驱动的边缘应用程序所需的一整套工具和库。