行业资讯
📅 2026/8/18 11:06:59
服务机器人无接触手势交互控制系统开发
服务机器人无接触手势交互控制系统开发无接触手势交互是新一代商用服务机器人主流人机交互方案广泛应用于商场导购、养老陪护、政务大厅引导机器人无需物理触控即可调节语音播报音量、切换交互模式;基于视觉手势关键点识别开发无接触控制程序是服务机器人应用技术员人机交互模块开发核心工作内容要求实现手势距离与机器人语音输出参数线性映射保障机器人面向老人、儿童等群体交互流畅稳定。任务四:机器人手势无接触音量交互控制系统开发本任务基于实验箱外接摄像头通过识别拇指与食指指尖的距离线性映射为系统音量大小实现“手势滑动调节灯光”功能具体要求如下:1.调用外接摄像头在窗口内实时显示画面、手掌关键点(带连接线条)、拇指与食指指尖标记(紫色圆点)及两点连线(紫色线条)。2.手势控制逻辑:识别手掌关键点提取拇指指尖和食指指尖的像素坐标;利用勾股定理计算两点距离将距离范围[50300]线性映射为系统RGB灯光亮度(0%-100%);距离越远灯光越亮;距离越近灯光越暗实时同步调节。3.实时显示信息:窗口左下角显示灯光亮度百分比(格式:XX%字体大小30颜色蓝色);窗口左侧显示亮度进度条(蓝色矩形高度随音量变化);窗口左上角显示帧率(格式:FPS:XX字体大小30颜色蓝色)。4.程序稳定性:手掌识别准确率≥80%无频繁误触发;按下“ESC”键可正常退出程序释放摄像头及系统资源。创建名为04.py的文件存储至任务一的文件夹中现场演示并验证功能。程序如下导入试验箱RGB灯环驱动模块用来控制硬件LED灯环from exboard import RGB导入OpenCV计算机视觉库负责摄像头读取、图像处理、绘图操作import cv2导入numpy数值运算库用于数组、阈值矩阵、数学运算import numpy as np导入时间库用来计算画面帧率FPSimport time 【步骤1全局参数初始化】 设置摄像头设备编号为0选用本机0号USB摄像头CAM_ID 0拇指食指之间最小触发像素距离小于该距离亮度归零MIN_DIST 50拇指食指之间最大有效像素距离超过之后亮度达到最大值MAX_DIST 300计算有效识别距离区间的总跨度DIST_RANGE MAX_DIST - MIN_DISTOpenCV颜色定义颜色顺序为BGR紫色 (B255,G0,R255)PURPLE (255, 0, 255)蓝色 (B255,G0,R0)用于进度条、文字BLUE (255, 0, 0)灰色亮度进度条外框底色GRAY_COLOR (80, 80, 80)创建RGB灯环硬件实例rgb RGB()初始化24颗LED全部设置为黑色熄灭状态rgb.set([(0, 0, 0)] * 24)上一帧的时间戳用来计算每秒帧数prev_time 0HSV颜色空间下肤色下限阈值LOWER_SKIN np.array([0, 10, 60], np.uint8)HSV颜色空间下肤色上限阈值UPPER_SKIN np.array([25, 255, 255], np.uint8)手掌轮廓长宽比上限超过该数值判定为长条手臂轮廓直接舍弃MAX_HAND_RATIO 1.5手掌轮廓最小像素面积过滤微小噪点产生的无效轮廓MIN_HAND_AREA 1500 【步骤2封装工具函数】 def get_hand_contour(origin_frame):“”肤色分割 长宽比过滤只识别手掌、过滤长条手臂:param origin_frame: 原始BGR图像:return: 手部轮廓找不到返回None“”# 将BGR格式原图转换至HSV色彩空间方便进行肤色筛选hsv cv2.cvtColor(origin_frame, cv2.COLOR_BGR2HSV)# 根据肤色阈值生成二值掩码肤色区域变为白色其余区域黑色skin_mask cv2.inRange(hsv, LOWER_SKIN, UPPER_SKIN)# 高斯模糊处理掩码平滑画面、消除细小噪点skin_mask cv2.GaussianBlur(skin_mask, (7, 7), 0)# 创建3*3大小的卷积核用于形态学处理kernel np.ones((3, 3), np.uint8)# 闭运算先膨胀再腐蚀填补肤色掩码内部细小的空洞skin_mask cv2.morphologyEx(skin_mask, cv2.MORPH_CLOSE, kernel)# 开运算先腐蚀再膨胀清除画面外面零散的白色噪点skin_mask cv2.morphologyEx(skin_mask, cv2.MORPH_OPEN, kernel)# 查找掩码图像当中所有最外层的物体轮廓 contours, _ cv2.findContours(skin_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 如果没有检测出来任何轮廓直接返回空值 if len(contours) 0: return None # 创建列表用来存放筛选之后合格的手掌轮廓 valid_hand [] # 遍历每一条检测得到的轮廓 for cnt in contours: # 计算当前轮廓所占像素面积 area cv2.contourArea(cnt) # 如果轮廓面积小于手掌最低面积阈值则跳过该轮廓 if area MIN_HAND_AREA: continue # 获取轮廓外接矩形返回矩形左上角坐标、宽度、高度 x, y, w, h cv2.boundingRect(cnt) # 计算矩形长边与短边的比值 ratio max(w, h) / min(w, h) # 长宽比小于阈值代表形状接近手掌存入合格列表 if ratio MAX_HAND_RATIO: valid_hand.append(cnt) # 合格手掌轮廓列表为空返回None if len(valid_hand) 0: return None # 在合格轮廓当中选取面积最大的轮廓作为当前手掌 hand_cont max(valid_hand, keycv2.contourArea) # 返回筛选完成的手掌轮廓 return hand_contdef get_fingertips(hand_contour):“”“凸缺陷算法精准提取指尖坐标”“”# 获取手掌轮廓凸包关闭返回点模式仅返回轮廓索引hull cv2.convexHull(hand_contour, returnPointsFalse)# 根据凸包计算凸缺陷手掌指缝位置就是凸缺陷defects cv2.convexityDefects(hand_contour, hull)# 创建列表用来存储指尖坐标点fingertips []# 判断凸缺陷是否成功检测到if defects is not None:# 循环遍历全部凸缺陷for i in range(defects.shape[0]):# s起始点索引、e终点索引、f凹陷点索引、d凹陷深度s, e, f, d defects[i, 0]# 获取缺陷起始点坐标start tuple(hand_contour[s][0])# 获取缺陷终点坐标end tuple(hand_contour[e][0])# 将两个指尖坐标存入指尖数组fingertips.append(list(start))fingertips.append(list(end))# 当凸缺陷没有获取到指尖则直接读取凸包顶点当作指尖点位if len(fingertips) 0:# 获取凸包所有顶点坐标hull_points cv2.convexHull(hand_contour, returnPointsTrue)# 遍历凸包顶点存入指尖列表for point in hull_points:fingertips.append([point[0][0], point[0][1]])# 将指尖列表转换为numpy数组并且返回return np.array(fingertips)def calc_euclidean(p1, p2):“”“勾股定理计算两点像素距离”“”# 欧氏距离公式计算两个坐标点之间像素长度return np.sqrt(np.square(p1[0] - p2[0]) np.square(p1[1] - p2[1]))def distance_to_brightness(dist):“”“指尖距离线性映射灯光亮度0~100%”“”# 限制距离数值处于有效区间50~300防止数值越界dist np.clip(dist, MIN_DIST, MAX_DIST)# 根据距离换算得到亮度百分比percent ((dist - MIN_DIST) / DIST_RANGE) * 100# 保留一位小数之后返回亮度百分比return round(percent, 1)def draw_brightness_bar(frame, percent):“”“左侧绘制蓝色亮度进度条”“”# 设置进度条左上角坐标bar_x, bar_y 30, 50# 设置进度条宽度、整体高度bar_w, bar_h 40, 400# 绘制进度条灰色外边框cv2.rectangle(frame, (bar_x, bar_y), (bar_x bar_w, bar_y bar_h), GRAY_COLOR, 2)# 根据亮度百分比计算填充高度fill_h int(bar_h * (percent / 100))# 计算填充矩形的上边坐标进度条自下而上填充fill_y bar_y bar_h - fill_h# 绘制蓝色的亮度填充块cv2.rectangle(frame, (bar_x, fill_y), (bar_x bar_w, bar_y bar_h), BLUE, -1)# 返回绘制完成之后的画布return framedef draw_hand_connection(img, tip_points):“”“绘制手掌关键点连接线”“”# 循环遍历指尖点位依次连线相邻指尖for i in range(len(tip_points) - 1):# 取出第i个指尖的整型坐标p1 (int(tip_points[i][0]), int(tip_points[i][1]))# 取出后一个指尖整型坐标p2 (int(tip_points[i1][0]), int(tip_points[i1][1]))# 使用紫色线条连接两个指尖cv2.line(img, p1, p2, PURPLE, 2)# 循环绘制每一处指尖圆点标记for pt in tip_points:cv2.circle(img, (int(pt[0]), int(pt[1])),6,PURPLE,-1)# 返回绘制结束后的画布return img 【步骤3主程序循环】 def main():# 声明使用全局时间变量prev_timeglobal prev_time# 创建摄像头捕获对象开启指定编号摄像头cap cv2.VideoCapture(CAM_ID)# 设置摄像头输出画面宽度为640像素cap.set(cv2.CAP_PROP_FRAME_WIDTH,640)# 设置摄像头输出画面高度为480像素cap.set(cv2.CAP_PROP_FRAME_HEIGHT,480)# 判断摄像头是否开启失败 if not cap.isOpened(): # 在终端打印摄像头开启失败提示英文 print(Camera open failed, please modify CAM_ID) # 终止主函数 return # 摄像头图像无限循环读取 while True: # 读取一帧摄像头画面ret代表读取成功标记frame为图像矩阵 ret, frame cap.read() # 如果当前帧读取失败则跳过本次循环继续读取下一帧 if not ret: continue # 获取图像高度、宽度数值通道数舍弃 img_h, img_w frame.shape[:2] # 将画面水平镜像翻转适配人面对镜头时左右手方向 frame cv2.flip(frame,1) # 拷贝原图作为绘图画布防止原图被修改 canvas frame.copy() # 获取当前系统时间戳 cur_time time.time() # 计算当前画面帧率单位帧每秒 fps int(1/(cur_time - prev_time)) # 更新上一帧时间戳为本帧时间 prev_time cur_time # 初始化灯光亮度百分比默认值为0 brightness_percent 0.0 # 调用函数检测当前画面当中的手掌轮廓 hand_cont get_hand_contour(frame) # 判断已经成功检测到手部轮廓 if hand_cont is not None: # 在画布绘制紫色手掌轮廓边线 cv2.drawContours(canvas,[hand_cont],-1,PURPLE,2) # 获取当前手掌全部指尖点位数组 tip_array get_fingertips(hand_cont) # 控制台打印当前检测出来指尖的数量 print(fDetected fingertips quantity:{len(tip_array)}) # 指尖点位大于等于2才可以计算双指间距 if len(tip_array) 2: # 初始化指尖最大间距 max_dis 0 # 存储距离最远两个指尖的数组下标 index_a, index_b 0,1 # 双层循环遍历所有指尖组合 for i in range(len(tip_array)): for j in range(i1, len(tip_array)): # 计算当前一组指尖之间像素距离 d calc_euclidean(tip_array[i], tip_array[j]) # 如果距离大于当前最大距离则更新最大距离与下标 if d max_dis: max_dis d index_a,index_b i,j # 获取距离最远第一个指尖坐标 point1 tip_array[index_a] # 获取距离最远第二个指尖坐标 point2 tip_array[index_b] # 转为整型像素坐标 x1,y1 int(point1[0]),int(point1[1]) x2,y2 int(point2[0]),int(point2[1]) # 控制台输出两根指尖之间的像素距离 print(fFinger‑finger pixel distance:{max_dis}) # 在画布绘制紫色实心圆点标记第一个指尖 cv2.circle(canvas,(x1,y1),8,PURPLE,-1) # 在画布绘制紫色实心圆点标记第二个指尖 cv2.circle(canvas,(x2,y2),8,PURPLE,-1) # 使用紫色线条连接两处指尖标记点 cv2.line(canvas,(x1,y1),(x2,y2),PURPLE,3) # 通过指尖间距换算灯光亮度百分比 brightness_percent distance_to_brightness(max_dis) # 绘制所有指尖之间的连接线 canvas draw_hand_connection(canvas, tip_array) # 将0~100亮度百分比换算为LED通道数值范围0~255 led_val int((brightness_percent / 100) * 255) # 设置24颗灯环全部为当前亮度的白光 rgb.set([(led_val, led_val, led_val)] * 24) # 在画布左上角绘制当前帧率FPS文字 cv2.putText(canvas,fFPS: {fps},(10,35), cv2.FONT_HERSHEY_SIMPLEX,1,BLUE,2) # 在画布左下角输出当前灯光亮度百分比英文文本 cv2.putText(canvas,fBrightness: {brightness_percent}%,(20, img_h - 20), cv2.FONT_HERSHEY_SIMPLEX,1,BLUE,2) # 在画布左侧刷新可视化亮度进度条 canvas draw_brightness_bar(canvas, brightness_percent) # 设置预览窗口标题并且展示处理之后的画布画面 cv2.imshow(-----, canvas) # 等待键盘按键输入延时1毫秒 key cv2.waitKey(1) # 判断按下ESC按键ESC按键ASCII码为27结束主循环 if key 27: break # 程序结束释放摄像头硬件资源 cap.release() # 销毁所有OpenCV创建出来的图像窗口 cv2.destroyAllWindows() # 将RGB灯环所有LED熄灭 rgb.set([0, 0, 0] * 24) # 控制台打印程序退出硬件释放完毕提示 print(Program exit, camera and led‑ring released)程序入口运行主函数ifname “main”:main()