“手部检测”案例源码详解 (Python)
testhand.py用于检测手部,本案例可与testface对照阅读
- 打开testhand手部检测案例
- 预处理图像
- 在原图上绘制网格和可选的外接矩形
- 检测手部关键点:手腕、手指、手掌
- 指定模型路径和输入形状
- 设置相机并开始读图
- 启动模型,分支处理是否检测到手的情况
打开testhand手部检测案例
在VScode中进入代码编辑状态。
导入相关库
引用模块介绍
- math模块提供了许多对浮点数的数学运算函数。
- cv2模块是OpenCV 2.0的简写,在计算机视觉项目的开发中,OpenCV作为较大众的开源库,拥有了丰富的常用图
像处理函数库,采用C/C++语言编写,可以运行在Linux/Windows/Mac等操作系统上,能够快速的实现一些图像处理和识别的任务。 - remi是一个用于python应用程序的gui库,它将应用程序的接口转换成html并在web浏览器中呈现。这消除了特定于平台的依赖关系,使用户可以轻松地在python中开发跨平台应用程序
- sys模块包括了一组非常实用的服务,内含很多函数方法和变量,用来处理Python运行时配置以及资源,从而可以与前当程序之外的系统环境交互。
- NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库。在机器学习算法中大部分都是调用Numpy库来完成基础数值计算的。
- blazeface:来自谷歌的研究人员通过改造mobileNet提出更为紧凑的轻量级特征提取方法、结合适用于移动端GPU高效运行的新型锚框机制,以及代替非极大值抑制的加权方法保证检测结果的稳定性,在移动端上实现了超高速的高性能人脸检测BlazeFace,最快不到一毫秒的检测速度为众多人脸相关的应用提供了更广阔的发展空间。
- cvs图形控件模块
- tflite_gpu,GPU加速代码由AID提供,TensorFlow Lite 支持多种硬件加速器。GPU 是设计用来完成高吞吐量的大规模并行工作的。因此,它们非常适合用在包含大量运算符的神经网络上,一些输入张量可以容易的被划分为更小的工作负载且可以同时执行,通常这会导致更低的延迟。在最佳情况下,用 GPU 在实时应用程序上做推理运算已经可以运行的足够快,而这在以前是不可能的。
预处理图像
#用于tflite32的图像预处理(图像,图像大小)
#返回图像
def preprocess_image_for_tflite32(image, model_image_size=300):
#cv2.cvtColor(p1,p2) 是颜色空间转换函数,p1是需要转换的图片,p2是转换成何种格式。
#cv2.COLOR_BGR2RGB 将BGR格式转换成RGB格式
#cv2.COLOR_BGR2GRAY 将BGR格式转换成灰度图片
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
#cv2.resize(src, dsize, dst=None, fx=None, fy=None, interpolation=None) —— 将原始图像调整为指定大小。
#src是原始图像,dsize输出图像的尺寸(元组方式),这里参数与模型图像保持一致。
image = cv2.resize(image, (model_image_size, model_image_size))
#np.expand_dims通过在指定位置插入新的轴来扩展数组形状,axis=0是最高维度
##设置axis为0,矩阵从二维矩阵变成了三维矩阵,新的维度长度为1
image = np.expand_dims(image, axis=0)
#使用这个函数将RGB值转化成零到一的值
image = (2.0 / 255.0) * image - 1.0
#此处转化类型至三十二位浮点
image = image.astype('float32')
return image
#图式识别方法(传递参数 图像、检测目标、是否描绘关键点==是)
#返回x、y分别的最大最小值(四个数)
def plot_detections(img, detections, with_keypoints=True):
#输出图像
output_img = img
#numpy 创建的数组都有一个shape属性,它是一个元组,返回各个维度的维数。有时候我们可能需要知道某一维的特定维数
#二维情况可以看到y是一个两行三列的二维数组,y.shape[0]代表行数,y.shape[1]代表列数。
#可以看到x是一个包含了3个两行三列的二维数组的三维数组,x.shape[0]代表包含二维数组的个数,x.shape[1]表示二维数组的行数,x.shape[2]表示二维数组的列数。
print(img.shape)
#初始化x、y坐标为
x_min=[0,0]
x_max=[0,0]
y_min=[0,0]
y_max=[0,0]
hand_nums=len(detections)
# if hand_nums >2:
# hand_nums=2
print("Found %d hands" % hand_nums)
#若检测到多余两只手,则只处理两只
if hand_nums >2:
hand_nums=2
for i in range(hand_nums):
ymin = detections[i][ 0] * img.shape[0]
xmin = detections[i][ 1] * img.shape[1]
ymax = detections[i][ 2] * img.shape[0]
xmax = detections[i][ 3] * img.shape[1]
w=int(xmax-xmin)
h=int(ymax-ymin)
h=max(h,w)
h=h*224./128.
# ymin-=0.08*h
# xmin-=0.25*w
# xmax=xmin+1.5*w;
# ymax=ymin+1.0*h;
x=(xmin+xmax)/2.
y=(ymin+ymax)/2.
xmin=x-h/2.
xmax=x+h/2.
ymin=y-h/2.-0.18*h
ymax=y+h/2.-0.18*h
# if w<h:
# xmin=xmin-(h+0.08*h-w)/2
# xmax=xmax+(h+0.08*h-w)/2
# ymin-=0.08*h
# # ymax-=0.08*h
# else :
# ymin=ymin-(w-h)/2
# ymax=ymax+(w-h)/2
# h=int(ymax-ymin)
# ymin-=0.08*h
# landmarks_xywh[:, 2:4] += (landmarks_xywh[:, 2:4] * pad_ratio).astype(np.int32) #adding some padding around detection for landmark detection step.
# landmarks_xywh[:, 1:2] -= (landmarks_xywh[:, 3:4]*0.08).astype(np.int32)
x_min[i]=int(xmin)
y_min[i]=int(ymin)
x_max[i]=int(xmax)
y_max[i]=int(ymax)
p1 = (int(xmin),int(ymin))
p2 = (int(xmax),int(ymax))
# print(p1,p2)
#画矩形()
cv2.rectangle(output_img, p1, p2, (0,255,255),2,1)
# cv2.putText(output_img, "Face found! ", (p1[0]+10, p2[1]-10),cv2.FONT_ITALIC, 1, (0, 255, 129), 2)
# if with_keypoints:
# for k in range(7):
# kp_x = int(detections[i, 4 + k*2 ] * img.shape[1])
# kp_y = int(detections[i, 4 + k*2 + 1] * img.shape[0])
# cv2.circle(output_img,(kp_x,kp_y),4,(0,255,255),4)
return x_min,y_min,x_max,y_max
#preprocess_img_pad,预处理图像外边缘,以免丢失边缘和角落的信息。
def preprocess_img_pad(img,image_size=128):
# fit the image into a 128x128 square
# img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#np.r_是按列连接两个矩阵,就是把两矩阵上下相加,要求列数相等。
#img.shape 参数不写的话默认读入三通道图片,例如(640,640,3)
#这里相当于把元组(640,640,3)转换为一维数组[640,640,3]
shape = np.r_[img.shape]
#shape [640 480 3]
#uint32型为无符号32位整数,占4个字节,取值范围在0~4,294,967,295之间
#shape.max()最大值为640, shape[:2]的结果为[640,480],所以pad_all的值为[0,160]
pad_all = (shape.max() - shape[:2]).astype('uint32')
#执行除法并向下取整
#pad指图像边缘需要补的长度和宽度,pad的值为[0,80]
pad = pad_all // 2
# print ('pad_all',pad_all)
#pad(array, pad_width, mode, **kwargs)
#返回值:数组
#在卷积神经网络中,为了避免因为卷积运算导致输出图像缩小和图像边缘信息丢失,常常采用图像边缘填充技术,
# 即在图像四周边缘填充0,使得卷积运算后图像大小不会缩小,同时也不会丢失边缘和角落的信息。在Python的numpy库中,常常采用numpy.pad()进行填充操作
#在卷积神经网络中,通常采用constant填充方式
#‘constant’——表示连续填充相同的值,每个轴可以分别指定填充值,constant_values=(x, y)时前面用x填充,后面用y填充,缺省值填充0,
#img_pad_ori保存填充后的图,填充原始彩色图像外边缘
img_pad_ori = np.pad(
img,
((pad[0],pad_all[0]-pad[0]), (pad[1],pad_all[1]-pad[1]), (0,0)),
mode='constant')
#处理完变成黑白图片
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
#img_pad填充黑白图外边缘
img_pad = np.pad(
img,
((pad[0],pad_all[0]-pad[0]), (pad[1],pad_all[1]-pad[1]), (0,0)),
mode='constant')
#src是原始图像,dsize输出图像的尺寸(元组方式),这里默认参数是128*128