跳到主要内容

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屏幕和摄像头的显示问题。虽然过程曲折,但为后续实际编写手势识别算法奠定了坚实的基础。希望我的这些经验能对大家有所帮助!