跳到主要内容

3 篇博文 含有标签「手势识别」

查看所有标签

DshanPI-A1 测评四:开源手势项目改造

· 阅读需 8 分钟
世玉轩
100askTeam yuxuan.

本次所有的项目改造主要基于兼容性、流畅性、屏幕显示方式和摄像头调用等方面。

显示输出适配​

主要的适配问题​

1. RK3576运行纯Wayland环境​

RK3576运行纯Wayland环境,没有X11和libGL支持,无法使用传统的cv2.imshow()显示图像。

【解决方案】

采用FIFO + GStreamer + Wayland的显示管线:

# Python端代码
fifo = open('/tmp/gesture_fifo', 'wb')
while True:
_, jpeg = cv2.imencode('.jpg', processed_frame,
[cv2.IMWRITE_JPEG_QUALITY, 85])
fifo.write(jpeg.tobytes())
fifo.flush()
# Shell端代码
# GStreamer从管道读取JPEG流并显示
gst-launch-1.0 filesrc location=/tmp/gesture_fifo ! \
jpegparse ! jpegdec ! videoconvert ! waylandsink fullscreen=true

2. IMX415摄像头第二次启动色彩异常​

IMX415摄像头第二次启动时出现色彩异常,内核报错"no first iq setting"。

【解决方案】

在每次打开摄像头前重启rkaiq_3A_server:

def restart_3a(self):
os.system("killall rkaiq_3A_server 2>/dev/null")
time.sleep(2)
os.system("rm -f /tmp/.rkaiq_3A* 2>/dev/null")
os.system("/etc/init.d/S40rkaiq_3A start >/dev/null 2>&1")
time.sleep(5)

项目一:贪吃蛇游戏​

项目开源地址:Project2/SnakeGame/main.py at main · WLHSDXN/Project2

改造过程​

1. 多层级检测器架构​

为了适配不同的依赖环境,设计了三层检测器回退机制:

优先级1: cvzone (MediaPipe封装,精度高)
↓ 不可用
优先级2: 原生MediaPipe (21个关键点)
↓ 不可用
优先级3: HSV肤色检测 (轻量级备用)

代码实现:

# 检测器选择逻辑
if USE_CVZONE:
detector = CvzoneHandDetector(detectionCon=0.8, maxHands=1)
elif USE_MEDIAPIPE:
detector = MediapipeHandDetector(maxHands=1,
detectionCon=0.5,
drawLandmarks=False)
else:
detector = SimpleHandDetector() # HSV备用方案

2. MediaPipe集成与封装​

实现了MediapipeHandDetector类,返回与cvzone兼容的数据格式:

class MediapipeHandDetector:
def findHands(self, frame, flipType=False):
img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = self.hands.process(img_rgb)

hands = []
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
# 提取21个关键点坐标
lmList = []
for lm in hand_landmarks.landmark:
x_px = int(lm.x * width)
y_px = int(lm.y * height)
lmList.append([x_px, y_px, lm.z])

hands.append({'lmList': lmList})

return hands, frame

【关键点】

食指指尖是lmList[8],直接用作蛇头控制点。

3. HSV肤色检测备用方案​

当MediaPipe不可用时,使用简单的肤色检测:

def detect_hand_simple(frame):
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, [0, 30, 60], [255, 255, 255])

# 形态学去噪
kernel = np.ones((7, 7), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=3)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=2)

contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
if contours:
c = max(contours, key=cv2.contourArea)
hull = cv2.convexHull(c)
# 提取最高点作为"指尖"
topmost = hull[hull[:, :, 1].argmin()][0]
return topmost

4. MediaPipe性能调优​

优化1: 使用轻量级模型​
self.hands = mp.solutions.hands.Hands(
model_complexity=0, # 0=lite, 1=full (默认)
max_num_hands=1,
min_detection_confidence=0.5, # 降低阈值换速度
min_tracking_confidence=0.5
)
优化2: 关闭可视化绘制​
# 移除耗时的关键点绘制
# mp_drawing.draw_landmarks(frame, landmarks, connections) # 注释掉
drawLandmarks=False # 新增开关
优化3: 减少输入分辨率​
# 640x480 已经是最优平衡点
# 若进一步降低到320x240可提升FPS,但会影响检测精度
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
优化4: 优化相机缓冲​
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)  # 减少延迟

【优化效果】

FPS从5-10提升至15-25 FPS,满足游戏交互需求。

5. 文件控制接口​

由于FIFO模式下无法直接捕获键盘,采用控制文件方式:

# Shell脚本部分
# 读取按键并写控制文件
stty echo icanon
while read n1 t 0.1 key; do
if [ "$key" = "r" ]; then
touch /tmp/snake_restart
elif [ "$key" = "q" ]; then
touch /tmp/snake_quit
fi
done
# Python部分
# 检测控制文件
if os.path.exists('/tmp/snake_quit'):
print('Quit command detected')
break

if os.path.exists('/tmp/snake_restart'):
os.remove('/tmp/snake_restart')
# 重置游戏状态
self.game.gameOver = False
self.game.points = []
self.game.previousHead = (0, 0)

效果展示​

代码和演示视频均在附件当中

img

img

项目二:虚拟画板​

开源项目:【基于手部关键点检测,隔空控制鼠标/隔空绘画】https://www.bilibili.com/video/BV1364y1h7PS?vd_source=a16ca768198c38baa684546cf5060811

改造过程​

1. 核心逻辑提取​

手势识别逻辑:​
fingers = detector.fingersUp()  # 返回5个值,1表示手指伸直

# 模式1: 选择工具(食指+中指伸直)
if fingers[1] and fingers[2]:
if y1 < 153: # 在顶部工具栏区域
if 0 < x1 < 320: color = [50, 128, 250] # 蓝色
elif 320 < x1 < 640: color = [0, 0, 255] # 红色
elif 640 < x1 < 960: color = [0, 255, 0] # 绿色
elif 960 < x1 < 1280: color = [0, 0, 0] # 橡皮擦

# 模式2: 绘画(仅食指伸直)
elif fingers[1] and not fingers[2]:
cv2.line(imgCanvas, (xp, yp), (x1, y1), color, brushThickness)
画布合成逻辑:​
# 1. 将画布转为灰度图并二值化
imgGray = cv2.cvtColor(imgCanvas, cv2.COLOR_BGR2GRAY)
_, imgInv = cv2.threshold(imgGray, 50, 255, cv2.THRESH_BINARY_INV)

# 2. 使用位运算合成
img = cv2.bitwise_and(img, imgInv) # 摄像头画面保留非绘画区域
img = cv2.bitwise_or(img, imgCanvas) # 叠加绘画内容

2. 显示系统重构​

复用贪吃蛇游戏的显示方案: FIFO + GStreamer

3. 工具栏内置化​

原项目依赖4张PNG图片作为工具栏,在嵌入式系统上不便管理外部资源。

【解决方案】

用OpenCV绘图API生成工具栏

def create_header(self):
"""动态生成工具栏"""
header = np.zeros((100, self.width, 3), np.uint8)
header[:] = (200, 200, 200) # 灰色背景

tools = [
((250, 128, 50), "Blue"), # BGR格式
((0, 0, 255), "Red"),
((0, 255, 0), "Green"),
((0, 0, 0), "Eraser")
]

section_width = self.width // 4
for i, (color, label) in enumerate(tools):
x1 = i * section_width
x2 = (i + 1) * section_width

# 绘制颜色块
cv2.rectangle(header, (x1 + 10, 20), (x2 - 10, 80), color, -1)
cv2.rectangle(header, (x1 + 10, 20), (x2 - 10, 80),
(255, 255, 255), 2) # 白色边框

# 文字标签
cv2.putText(header, label, (x1 + 20, 95),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (50, 50, 50), 1)

return header

这样就可以对外部产生零依赖,更有利于移植和项目开发!

4. 摄像头与3A服务适配​

复用贪吃蛇游戏的解决办法

5. 分辨率与性能权衡​

【原版配置】:

width = 1280, height = 720
画布: imgCanvas = np.zeros((720, 1280, 3), np.uint8)

【RK3576优化】

width = 640, height = 480  # 降低50%分辨率
画布: imgCanvas = np.zeros((480, 640, 3), np.uint8)

【原因】

  1. MediaPipe在640x480下FPS提升约2倍
  2. 画板应用对分辨率要求不如视觉识别高
  3. JPEG编码/传输速度更快

【工具栏适配】

原版: 顶部153像素高,分4个320像素宽的区域 RK3576: 顶部100像素高,分4个160像素宽的区域

section_width = self.width // 4  # 自适应宽度
if y1 < 100: # 工具栏高度
if 0 < x1 < section_width:
self.color = (250, 128, 50) # 蓝色
elif section_width < x1 < section_width * 2:
self.color = (0, 0, 255) # 红色
# ...

6. 交互控制改进​

1. 清空画布机制​

【原版】

if all(x >= 1 for x in fingers):
imgCanvas = np.zeros((720, 1280, 3), np.uint8)

【问题】

误触发率高,不便于精细控制。

【RK3576改进】

使用按键控制:

# Shell端
while read -n1 -t 0.1 key; do
if [ "$key" = "c" ]; then
touch /tmp/painter_clear
fi
done
# Python端
if os.path.exists('/tmp/painter_clear'):
os.remove('/tmp/painter_clear')
self.imgCanvas = np.zeros((self.height, self.width, 3), np.uint8)
print("Canvas cleared")
2. 退出控制​

【原版】

只能通过cv2.waitKey(1)捕获键盘,依赖窗口焦点。

【RK3576】

双重退出机制:

  1. 按键控制: touch /tmp/painter_quit → Python检测并退出

  2. Ctrl+C: Shell脚本trap信号 → kill所有进程 → 清理FIFO

效果展示​

代码和演示视频均在附件当中

img

img

技术总结与经验​

  1. 跨平台显示适配 PC上的GUI方案不适用嵌入式,必须根据系统特性(Wayland/Framebuffer)选择输出方式。

  2. 资源内置化 嵌入式系统倾向于单文件部署,外部资源应转为代码生成或打包进程序。

  3. 性能分级优化

    • 算法层: 轻量级模型
    • 实现层: 关闭非必要绘制
    • 硬件层: 缓冲区/分辨率调优
  4. 交互方式适配 GUI依赖的键盘/鼠标事件需改为文件控制或GPIO触发。

DshanPI-A1 测评三:OpenCV 调试与 CPU 推理识别手势

· 阅读需 10 分钟
世玉轩
100askTeam yuxuan.

前面我们已经调试好了摄像头和屏幕,终于可以开始我们的手势识别啦!

这次我会在RK3576 Buildroot系统上实现一个基于OpenCV的实时手势识别系统。系统能够识别五种手势(拳头/一指、二指、三指、四指、五指),并在屏幕上实时显示处理结果。

由于嵌入式系统的特殊性,我们将重点讲解如何在无X11、无OpenGL的Wayland环境下实现图像显示。

手势识别算法​

原理​

因为我们张开的手指之间会形成凹陷,通过计算凹陷点的角度和深度,可以准确识别手指数量。

1. 肤色检测​

使用HSV色彩空间提取肤色区域:

def detect_hand(self, frame):
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, [0, 30, 60], [25, 255, 255]) # 肤色范围

# 形态学处理去噪
kernel = np.ones((7, 7), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel, iterations=3)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=2)

# 查找最大轮廓
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
c = max(contours, key=cv2.contourArea)
if cv2.contourArea(c) > 3000: # 面积阈值过滤噪声
return c, mask
return None, mask

2. 手指计数(凸包缺陷法)​

通过检测手掌轮廓的凹陷点来识别手指:

def recognize(self, contour):
hull = cv2.convexHull(contour, returnPoints=False)
defects = cv2.convexityDefects(contour, hull)

finger_count = 0
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
start = tuple(contour[s][0]) # 凸点1
end = tuple(contour[e][0]) # 凸点2
far = tuple(contour[f][0]) # 凹点(指间)

# 计算角度判断是否为有效指尖
a = np.linalg.norm(np.array(start) - np.array(end))
b = np.linalg.norm(np.array(start) - np.array(far))
c = np.linalg.norm(np.array(end) - np.array(far))
angle = np.arccos((b**2 + c**2 - a**2) / (2 * b * c))
if angle <= np.pi / 2.2 and d > 8000: # 角度和深度阈值
finger_count += 1

gestures = ["Fist/One", "Two", "Three", "Four", "Five"]
return gestures[finger_count]

如何显示OpenCV处理的图像​

有了前面的理论知识还是不够的,我们还需要实践才可以啊!如何显示OpenCV处理的图像这是本教程的重点。通常我们用cv2.imshow()显示图像,但在无X11/OpenGL的嵌入式系统上,这个方法不可用。我们需要使用GStreamer+Wayland方案(这个也是我们上一篇文章的方案)。

方案探索过程​

方案A: stdin管道传输​

最直观的想法是通过stdin管道传输图像数据:

proc = subprocess.Popen(['gst-launch-1.0', 'fdsrc', '!', ...], stdin=subprocess.PIPE)
proc.stdin.write(frame_data)

结果:频繁出现Broken pipe错误,数据传输不稳定,这个我怀疑是管道超时自动关闭了又或者是我的格式不对。所以我直接尝试用fifo来传输原始的数据

方案B: 命名管道(FIFO)传输原始数据​

尝试用FIFO传输原始RGB数据:

mkfifo /tmp/video_fifo

img

很遗憾啊,这个太容易动不动就内核奔溃了。到这个时候我面临几个问题:如果我单纯的用命令行看识别结果,我就不知道我的摄像头能不能正常运行;但是如果OpenCV和GStreamer同时读取摄像头是不可以的(摄像头只能被一个进程读取)。后面又觉得GStreamer的显示不需要实时读取摄像头的画面,我只要显示OpenCV处理过的就好了。说干就干,于是我尝试直接在OpenCV里面把处理后的图像通过GStreamer传到屏幕上面,但技术不达标无果,管道不是报错就是关闭。停下来慢慢思考,最后有了方案C(搞到这步已经花费三天了)。

方案C: 多文件序列​

改变思路,将处理后的图像保存为JPEG文件序列:

# Python端
cv2.imwrite(f'/dev/shm/gesture_frames/frame_{frame_index:03d}.jpg', processed)
# GStreamer端
gst-launch-1.0 multifilesrc location=frame_%03d.jpg loop=true ! jpegdec ! ...

结果:屏幕终于可以有变动了,开心坏了!但是效果很差,重影严重,而且因为是循环读出文件夹的图片导致一直在循环播放,影响美观和体验度。于是我打算利用FIFO,基于前面的思路升级为现在的方案:

OpenCV处理完一帧 → 立即编码JPEG → 直接写入FIFO → GStreamer立即解码显示

方案D: FIFO+JPEG流(最终方案!)​

# Python端: 直接往FIFO写JPEG数据
fifo = open('/tmp/gesture_fifo', 'wb')
_, jpeg = cv2.imencode('.jpg', processed, [cv2.IMWRITE_JPEG_QUALITY, 85])
fifo.write(jpeg.tobytes())
fifo.flush()
# GStreamer端: 用jpegparse自动分割JPEG帧
gst-launch-1.0 filesrc location=/tmp/gesture_fifo ! jpegparse ! jpegdec ! ...

补充:为什么JPEG流可行?

  1. JPEG格式自带开始(0xFFD8)和结束(0xFFD9)标记
  2. GStreamer的jpegparse插件能自动识别边界,分割独立的JPEG帧
  3. 避免了原始数据流的粘包问题

结果:总算是可以流畅地观察到画面了(流程不卡顿,甚至比直接点屏幕上的摄像头图标看摄像头画面都流畅)。演示视频和代码我会放在附件里面。

img img

番外篇—摄像头第二次启动色彩偏绿偏暗​

问题描述​

我在RK3576 Buildroot系统上使用IMX415摄像头,通过GStreamer+Wayland显示画面。遇到了一个诡异的问题:第一次启动摄像头色彩正常,但第二次启动后画面就变得偏暗偏绿。

img

初步分析:对比启动日志​

我首先对比了两次启动的kernel日志,发现了关键差异:

第一次启动(色彩正常):

[20.528641] rkisp_hw 27c00000.isp: set isp clk = 594000000Hz
[20.529097] rkcif-mipi-lvds 3: stream[0] start streaming
[20.529317] rockchip-csi2-dphy 3: dphy3, data_rate_mbps 892
[20.529356] imx415 3-0037: s_stream: 1.3864x2192, hdr: 0, bpp: 10

第二次启动(色彩异常):

[79.209321] rkisp_hw 27c00000.isp: set isp clk = 594000000Hz
[79.209967] rkisp rkisp-vir3: first params buf queue
[79.210051] rkisp rkisp-vir3: id: 0 no first iq setting cfg_upd: c000dfecc7fe473b en_upd: 0 en s: 5ffcc7fe473b
[79.210351] rkcif-mipi-lvds 3: stream[0] start streaming

关键发现:第二次启动多了一条警告 no first iq setting。这说明ISP的图像质量参数没有正确加载,导致使用了错误的默认参数,造成色彩偏暗偏绿。

问题解决过程​

第一阶段:尝试硬件层面解决​

一开始我以为是ISP驱动状态没有正确复位,尝试了几种方法:

  1. 尝试unbind/bind ISP驱动:

    echo "27c00000.isp" > /sys/bus/platform/drivers/rkisp_hw/unbind
    echo "27c00000.isp" > /sys/bus/platform/drivers/rkisp_hw/bind

    结果:摄像头直接打不开了,操作太激进导致驱动状态完全错乱。

  2. 尝试使用v4l2-ctl重置、media-ctl reset等方法,都没有解决问题。

第二阶段:深入诊断系统配置​

我开始系统性地诊断整个摄像头子系统:

# 查找IQ参数文件
find / -name "*imx415*.xml" -o -name "*imx415*.json" 2>/dev/null
# 结果: 找到了 /etc/iqfiles/imx415_CMK-OT2022-PX1_IR0147-50IRC-8M-F20.json

# 检查3A服务器
ps aux | grep rkaiq_3A_server
# 结果: 服务器正在运行

# 查看设备拓扑
v4l2-ctl --list-devices
# 确认 /dev/video-camera0 -> video11

关键发现:

  • IQ参数文件存在
  • 3A服务器(rkaiq_3A_server)正在运行
  • 但为什么IQ参数没有加载?

第三阶段:抓取3A服务器日志​

我决定前台运行3A服务器,查看详细输出:

killall rkaiq_3A_server
/usr/bin/rkaiq_3A_server 2>&1 &

启动日志显示:

DBG: get rkisp-isp-subdev devname: /dev/v4l-subdev3
DBG: get rkisp-input-params devname: /dev/video18
DBG: get rkisp-statistics devname: /dev/video17
XCORE: K: cid[1] rk_aiq_uapi2_sysctl_init success. iq: /etc/iqfiles//imx415_CMK-OT2022-PX1_IR0147-50IRC-8M-F20.json
XCORE: K: cid[1] rk_aiq_uapi2_sysctl_prepare success. mode: 0
DBG: /dev/media1: wait stream start event..

重大发现:3A服务器实际上工作正常!IQ文件已经成功加载了!

这时我进行了第二次摄像头启动测试,观察到:

[625.216117] rkisp-vir3: waiting on params stream one event timeout

真相大白:第二次启动时,3A服务器超时无响应!

第四阶段:找到根本原因​

通过多次测试和日志分析,我终于理解了问题的本质:

第一次启动流程(正常):

  1. 系统启动时,3A服务器自动启动
  2. 3A服务器加载IQ参数文件到内存
  3. 3A服务器预先准备好IQ参数缓冲区
  4. GStreamer启动摄像头
  5. ISP请求IQ参数
  6. 3A服务器立即响应并推送IQ参数
  7. 色彩正常

第二次启动流程(异常):

  1. 停止第一次的GStreamer进程
  2. 3A服务器还在运行,但进入了某种等待状态
  3. IQ参数缓冲区已经被消费
  4. 立即重启GStreamer
  5. ISP请求IQ参数
  6. 3A服务器来不及响应或状态异常
  7. ISP使用默认参数处理第一帧
  8. 出现no first iq setting警告
  9. 色彩偏暗偏绿

解决方案​

问题的根源是:3A服务器在摄像头第一次运行后进入异常状态,无法正确响应第二次启动的IQ参数请求。

最终的解决方法很简单:每次启动摄像头前,重启3A服务器。

我编写了一个封装脚本:

#!/bin/sh

echo "=== Starting Camera with 3A Server Reset ==="

# 1. 停止所有摄像头进程
pkill -9 gst-launch 2>/dev/null

# 2. 重启3A服务器
killall rkaiq_3A_server 2>/dev/null
sleep 2
rm -f /tmp/.rkaiq_3A*

# 3. 启动3A服务器
/etc/init.d/S40rkaiq_3A start
echo "Waiting for 3A server to initialize..."
sleep 5

# 4. 确认3A服务器运行正常
if ! pgrep rkaiq_3A_server > /dev/null; then
echo "ERROR: 3A server failed to start!"
exit 1
fi

echo "3A server ready, starting camera..."

# 5. 启动摄像头
gst-launch-1.0 v4l2src device=/dev/video11 ! \
video/x-raw,format=NV12,width=640,height=480,framerate=30/1 ! \
waylandsink

echo "Camera stopped"
exit 0

img

验证结果​

img

使用新脚本后,连续多次启动摄像头,色彩始终正常,日志中不再出现no first iq setting或超时错误。

经验总结​

  1. 对比日志是发现问题的关键:通过对比正常和异常情况的日志,快速定位到no first iq setting这个关键线索

  2. 系统性诊断:不要盲目尝试,先检查各个组件(IQ文件、3A服务器、设备节点)的状态

  3. 前台运行看详细日志:很多后台服务的问题需要前台运行才能看到详细输出

  4. 理解组件间的协作关系:RK平台的摄像头涉及ISP驱动、3A服务器、IQ参数文件三者的协作,任何一个环节出问题都会导致异常

  5. 状态管理很重要:嵌入式系统的服务重启问题往往是状态机管理不当导致的,彻底重置是最可靠的方案。

DshanPI-A1 测评二:手势识别编程环境搭建与屏幕调试

· 阅读需 6 分钟
世玉轩
100askTeam yuxuan.

本次测评我将会安装手势识别系统的必要工具和调试屏幕

硬件与环境准备​

在开始之前,我们先明确手头的装备和环境:

  • 核心板:Dshanpi-A1,主控为瑞芯微RK3576芯片。

  • 屏幕:一块480x800分辨率的MIPI屏幕。

  • 系统:Buildroot Linux系统。

  • 官方的SDK

安装开发工具​

下面是本次的清单:

软件包/配置类别推荐选项与作用
Python环境python3: 核心解释器。 python-pip: 用于安装未包含在Buildroot中的Python包。 python-numpy: 为OpenCV等库提供高效的数值计算支持。 python-setuptools: 一些Python包的基础构建依赖。
计算机视觉与图像处理opencv4: 务必启用 python3 支持。提供核心的计算机视觉库,用于图像处理和手势识别算法。 opencv4 贡献模块: 包含额外的、更先进的算法。
摄像头与显示支持gstreamer1 及相关插件: 构建摄像头图像采集和屏幕显示的管道。 gst1-plugins-base, gst1-plugins-good, gst1-plugins-bad, gst1-plugins-ugly: 提供丰富的编解码器和功能元件。 gst1-python: 允许在Python中创建和操作GStreamer管道。

SDK配置流程​

1. 选择芯片类型​

./build.sh chip

img

img

2. 进入buildroot配置​

cd buildroot
make menuconfig

img

3. 选择Target packages​

img

4. 安装Python环境​

当找不到安装路径时,可按/键进入搜索:

img

输入python3进行搜索:

img

进入显示的Location路径进行配置:

img

img

5. 保存配置并编译​

make

回到SDK主目录运行:

./build.sh rootfs
./build.sh updateimg

最后烧录运行到开发板

开发板调试​

检查工具安装情况​

python3 --version
pip3 --version
python3 -c "import numpy; print('NumPy version:', numpy.__version__)"
python3 -c "import cv2; print('OpenCV version:', cv2.__version__)"

img

屏幕调试​

问题分析​

系统已运行Weston合成器,这意味着我们有一个图形界面环境。尝试直接操作FrameBuffer(/dev/fb0)无效,因为Weston已占用显示接口。

通过系统检查发现:

  • /dev/fb0设备存在
  • 屏幕状态为connected
  • 分辨率为480x800

img

解决方案:GStreamer + Wayland​

GStreamer基础测试​

gst-launch-1.0 videotestsrc pattern=smpte ! video/x-raw,width=480,height=800 ! waylandsink sync=false

img

摄像头直连显示测试​

gst-launch-1.0 v4l2src device=/dev/video11 ! video/x-raw,width=640,height=480 ! videoconvert ! waylandsink sync=false

img

注意:请将device参数替换为您的摄像头设备节点

测试脚本​

#!/usr/bin/env python3
# fixed_display_test.py

import subprocess
import time
import os

def check_camera_devices():
"""检查可用的摄像头设备"""
print("=== 摄像头设备检查 ===")

try:
# 使用v4l2-ctl检查设备
result = subprocess.run(["v4l2-ctl", "--list-devices"],
capture_output=True, text=True)
if result.returncode == 0:
print("找到的视频设备:")n print(result.stdout)
else:
print("v4l2-ctl命令执行失败")
except Exception as e:
print(f"检查摄像头设备失败: {e}")

# 测试常见的摄像头设备
camera_devices = ["/dev/video11", "/dev/video0", "/dev/video1", "/dev/video2"]
print("\n测试摄像头设备:")

for device in camera_devices:
if os.path.exists(device):
print(f"测试设备: {device}")
try:
# 尝试使用GStreamer测试摄像头
cmd = [
"gst-launch-1.0",
"-v",
"v4l2src", f"device={device}", "!",
"video/x-raw,width=640,height=480,framerate=15/1", "!",
"videoconvert", "!",
"waylandsink", "sync=false"
]

process = subprocess.Popen(cmd)
time.sleep(3) # 显示3秒
process.terminate()
process.wait()
print(f" {device}: 摄像头工作正常")
return device

except Exception as e:
print(f"{device}: 测试失败 - {e}")
else:
print(f"{device}: 设备不存在")

return None

def test_static_patterns():
"""测试静态图案(不会变化)"""
print("\n=== 静态图案测试 ===")

# 设置Wayland环境
os.environ['WAYLAND_DISPLAY'] = 'wayland-0'

# 测试静态图案(不会变化)
static_patterns = [
("smpte100", "SMPTE 100%色彩条"),
("ball", "时钟图案"),
("blink", "闪烁图案"),
("pinwheel", "风车图案"),
("spokes", "辐条图案"),
]

for pattern, description in static_patterns:
print(f"显示: {description}")
try:
cmd = [
"gst-launch-1.0",
"videotestsrc", f"pattern={pattern}", "!",
"video/x-raw,width=480,height=800,framerate=15/1", "!",
"waylandsink", "sync=false"
]

process = subprocess.Popen(cmd)
time.sleep(3)
process.terminate()
process.wait()
print(f"{description} 显示成功")

except Exception as e:
print(f"{description} 显示失败: {e}")

def test_custom_resolution():
"""测试自定义分辨率显示"""
print("\n=== 自定义分辨率测试 ===")

resolutions = [
(480, 800, "竖屏 480x800"),
(800, 480, "横屏 800x480"),
(640, 480, "标准 640x480"),
(400, 800, "竖屏 400x800"),
]

for width, height, desc in resolutions:
print(f"测试分辨率: {desc}")
try:
cmd = [
"gst-launch-1.0",
"videotestsrc", "pattern=smpte100", "!",
f"video/x-raw,width={width},height={height},framerate=15/1", "!",
"videoconvert", "!",
"waylandsink", "sync=false"
]

process = subprocess.Popen(cmd)
time.sleep(2)
process.terminate()
process.wait()
print(f" {desc} 显示成功")

except Exception as e:
print(f" {desc} 显示失败: {e}")

if __name__ == "__main__":
print("=" * 50)

# 1. 检查摄像头
camera_device = check_camera_devices()

# 2. 测试静态图案
test_static_patterns()

# 3. 测试不同分辨率
test_custom_resolution()

print("\n" + "=" * 50)
if camera_device:
print(f"可用的摄像头设备: {camera_device}")
else:
print("未找到可用的摄像头设备")
print("所有测试完成")

img

img

运行结果​

image-20251222165424798

image-20251222165428939

演示视频我会放在附件里面

番外篇:FileZilla文件传输​

FileZilla连接设置​

FileZilla - The free FTP solution

检查SSH服务​

ss -tuln | grep 22

img

网络共享设置​

img

img

img

选择能上网的网络,点击属性:

img

image-20251222165505322

连接开发板​

ifconfig

img

使用FileZilla连接:

  • IP:开发板IP地址
  • 用户名:root
  • 密码:rockchip
  • 端口:22

总结​

回顾整个调试过程,以下几个关键点值得特别注意:

  1. 显示路径选择:在运行Weston这类合成器的系统上,优先使用GStreamer + waylandsink的方案来显示图像,而非直接操作FrameBuffer。

  2. 摄像头设备节点:务必使用v4l2-ctl --list-devices命令确认摄像头对应的设备节点,并在代码中正确指定。

  3. 屏幕分辨率:注意系统识别到的屏幕分辨率(可通过cat /sys/class/drm/card0-DSI-1/modes查询)可能与物理分辨率略有不同,在创建显示帧时需以此为准或进行调整。

至此,我们成功地在Dshanpi A1开发板上搭建起了手势识别的编程环境,并解决了MIPI屏幕和摄像头的显示问题。虽然过程曲折,但为后续实际编写手势识别算法奠定了坚实的基础。希望我的这些经验能对大家有所帮助!