闲的无聊做的一个小玩意,可以调用你的计算机相机,识别框内的手势(剪刀、石头和布),提供一个判决平台,感兴趣的可以继续完善。
用到的参考小文献:
具体实现结果如下
并且我另写了一个框架平台,可以进行下一步的功能拓展,发在我的资源界面了;
手势识别与控制系统
简介
我们系统的手势识别与控制功能主要采用 OpenCV库实现 , OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库, 可以运行在Linux, Windows, Android和Mac-OS操作系统上. 它轻量级而且高效—由一系列 C 函数和少量 C++类构成, 同时提供了Python, Ruby, MATLAB等语言的接口, 实现了图像处理和计算机视觉方面的很多通用算法。
本系统主要使用了OpenCV的视频采集, 图像色域转换, 颜色通道分割, 高斯滤波, OSTU自动阈值, 凸点检测, 边缘检测, 余弦定理计算手势等功能。
手势检测:#pic_center
水平翻转
设置ROI
ROI是region of interest首字母的简写,翻译为感性趣的区域。ROI是在一定图片中取一小部分进行重点处理的区域。图片就是一个二维数组,把数组分块求取就可以得到你想要的ROI区域。
定义肤色范围
人为定义RGB颜色范围,截取红黄蓝三原色上下限选出符合人肤色的区域。
提取肤色
在ROI选区范围内提取出肤色转化为灰度图像
在进行颜色空间转换时,RGD各个通道的范围应当根据实际 需要来进行归一化,如RGB转LUV通道时,需要将RGB归一化为32位浮点数,即各通道的值的变化范围为0到1,转化为HSV时也是这样。
RGB空间下标(R、G、B)代表着相应的颜色空间,且R、G、B∈(0,1).OpenCV中从RGB颜色空间转换为HSV颜色空间按照下式进行:
V
=
m
a
x
(
R
,
G
,
B
)
V=max(R,G,B)
V=max(R,G,B)
f
(
x
)
=
{
V
−
min
(
R
,
G
,
B
)
V
,
i
f
V
≠
0
0
,
i
f
otherwise
f(x)=\begin{cases}\dfrac{V-\min(R,G,B)}{V},&\quad if V\neq0\\ 0,&\quad if \textit{otherwise}\end{cases}
f(x)=⎩
⎨
⎧VV−min(R,G,B),0,ifV=0ifotherwise
H
=
{
60
(
G
−
B
)
V
−
min
(
R
,
G
,
B
)
,
i
f
V
=
R
120
+
60
(
B
−
R
)
V
−
min
(
R
,
G
,
B
)
,
i
f
V
=
G
250
+
60
(
R
−
G
)
V
−
min
(
R
,
G
,
B
)
,
i
f
V
=
B
H=\begin{cases}\dfrac{60(G-B)}{V-\text{min}(R,G,B)},ifV=R\\ 120+\dfrac{60(B-R)}{V-\text{min}(R,G,B)},ifV=G\\ 250+\dfrac{60(R-G)}{V-\text{min}(R,G,B)},ifV=B\end{cases}
H=⎩
⎨
⎧V−min(R,G,B)60(G−B),ifV=R120+V−min(R,G,B)60(B−R),ifV=G250+V−min(R,G,B)60(R−G),ifV=B
HSV色表如下:
图形经过膨胀与腐蚀后进行高斯滤波模糊图像
实现原理
图像的膨胀(Dilation)和腐蚀(Erosion)是两种基本的形态学运算,主要用来寻找图像中的极大区域和极小区域。其中膨胀类似于“领域扩张”,将图像中的高亮区域或白色部分进行扩张,其运行结果图比原图的高亮区域更大;腐蚀类似于“领域被蚕食”,将图像中的高亮区域或白色部分进行缩减细化,其运行结果图比原图的高亮区域更小。
膨胀的运算符是”⊕”,其定义如下:
A
⊕
B
=
{
x
∣
(
B
)
x
∩
A
≠
Θ
}
\mathrm{A}\oplus\mathrm{B}=\left\{\mathrm{x}\left|\left(\mathrm{B}\right)_x\cap\mathrm{A}\neq\Theta\right\}\right.
A⊕B={x∣(B)x∩A=Θ}
该公式表示用B来对图像A进行膨胀处理,其中B是一个卷积模板或卷积核,其形状可以为正方形或圆形,通过模板B与图像A进行卷积计算,扫描图像中的每一个像素点,用模板元素与二值图像元素做“与”运算,如果都为0,那么目标像素点为0,否则为1。从而计算B覆盖区域的像素点最大值,并用该值替换参考点的像素值实现膨胀。
图像腐蚀
该公式表示图像A用卷积模板B来进行腐蚀处理,通过模板B与图像A进行卷积计算,得出B覆盖区域的像素点最小值,并用这个最小值来替代参考点的像素值。
a. 图像二值化处理,将图像的灰度值根据阈值进行0,1处理得到的图像;
b. 卷积核,对应信号处理中的高低频滤波器。常用numpy函数去设置,np.ones((m,n), np.uint8) 表示指定m*n的卷积核;
c. 图像的膨胀或腐蚀,cv2.dilate(二值化图像, 卷积核, 迭代次数),完成图片的高斯滤波模糊。
轮廓检测和查找最大面积轮廓
轮廓处理的话主要用到函数cv2.findContours和这两个函数的使用使用方法很容易搜到就不说了,这部分主要的问题是提取到的轮廓有很多个,但是我们只需要手的轮廓,所以我们要用sorted函数找到最大的轮廓。
估算轮廓然后绘制轮廓曲线
查找手指凸包
凸包(Convex Hull)是一个计算几何(图形学)中的概念,它的严格的数学定义为:在一个向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。
在图像处理过程中,我们常常需要寻找图像中包围某个物体的凸包。凸包跟多边形逼近很像,只不过它是包围物体最外层的一个凸集,这个凸集是所有能包围这个物体的凸集的交集。
在opencv中,通过函数convexHulll能很容易的得到一系列点的凸包,比如由点组成的轮廓,通过convexHull函数,我们就能得到轮廓的凸包。寻找图像的凸包,就能够实现手势识别的功能。
对如下手势进行凸包查找:
手指凹陷查找{求三角形三边长度、点和曲线凸处的距离、余弦定理、去噪、手指轮廓描述曲线}
利用opencv提供的 convexityDefects 凹点检测函数检测图像凹陷的点, 然后利用, 然后根据凹陷点中的 (开始点, 结束点, 远点)的坐标, 利用余弦定理计算两根手指之间的夹角, 其必为锐角, 根据锐角的个数判别手势.
其中,锐角个数为0 ,表示 手势是 拳头 或 一,
锐角个数为0 ,表示 手势是 拳头 或 一,
锐角个数为1 ,表示 手势是 剪刀
锐角个数为2 ,表示 手势是 三,
锐角个数为3 ,表示 手势是 四,
锐角个数为4 ,表示 手势是 布
具体原理如下:利用findContours检测图像中的轮廓, 其中返回值contours包含了图像中所有轮廓的坐标点,它们的值分别为起点,终点,最远的点,到最远点的近似距离。根据图像中凹凸点中的 (开始点, 结束点, 远点)的坐标, 利用余弦定理计算两根手指之间的夹角, 其必为锐角, 根据锐角的个数判别手势。
输出手势
划拳(三次训练):
- 构造3*3的结构元素,得到ROI分析块
- 将膨胀后的图像和腐蚀相减得到轮廓边(灰度图)
- 将上面结果二进制化并进行反色
- 对反色结果进行中值滤波
- 计算手势面积
- 设定一定的判定时间,等到倒计时结束的时候将取得的图片进行处理和判定,经过训练三次得到判定的结果。
代码实现过程
import cv2
import time
import os
def judge():
# 构造一个3×3的结构元素
# return 0 stone ,1 jiandao, 2 bu
img = cv2.imread("wif.jpg", 0)
element = cv2.getStructuringElement(cv2.MORPH_RECT, (11, 11)) # 矩形:MORPH_RECT 交叉形:MORPH_CROSS 椭圆形:MORPH_ELLIPSE
dilate = cv2.dilate(img, element)
erode = cv2.erode(img, element)
# 将两幅图像相减获得边,第一个参数是膨胀后的图像,第二个参数是腐蚀后的图像
result = cv2.absdiff(dilate, erode);
# 上面得到的结果是灰度图,将其二值化以便更清楚的观察结果
retval, result = cv2.threshold(result, 40, 255, cv2.THRESH_BINARY);
# 反色,即对二值图每个像素取反 图像非运算的效果:一个二值图,将黑色转为白色,白色转为黑色。
result = cv2.bitwise_not(result);
# 非线性过滤——中值滤波
result = cv2.medianBlur(result, 23)
# 计算手势面积
a = []
posi = []
width = []
count = 0
area = 0
for i in range(result.shape[1]):
for j in range(result.shape[0]):
if (result[j][i] == 0):
area += 1
for i in range(result.shape[1]):
if (result[5 * result.shape[0] // 16][i] == 0 and result[5 * result.shape[0] // 16][i - 1] != 0):
count += 1
width.append(0)
posi.append(i)
if (result[5 * result.shape[0] // 16][i] == 0):
width[count - 1] += 1 # 如果在这里报错,是因为背景问题,请让手的背景尽量整洁
print('the pic width is ', result.shape[1], '\n')
for i in range(count):
print('the ', i, 'th', ' ', 'is')
print('width ', width[i])
print('posi ', posi[i], '\n')
print(count, '\n')
print('area is ', area, '\n')
cv2.line(result, (0, 5 * result.shape[0] // 16), (214, 5 * result.shape[0] // 16), (0, 0, 0))
cv2.namedWindow("fcuk")
cv2.imshow("fcuk", result)
cv2.waitKey(0)
# 判定时间
width_length = 0
width_jiandao = True
for i in range(count):
if width[i] > 45:
return 2;
if width[i] <= 20 or width[i] >= 40:
width_jiandao = False
width_length += width[i]
if width_jiandao == True and count == 2:
return 1;
if (area < 8500):
print('shi tou')
return 0;
print("width_leng", width_length)
if (width_length < 35):
# 这个时候说明照片是偏下的,所以需要重新测定。
a = []
posi = []
width = []
count = 0
for i in range(result.shape[1]):
if (result[11 * result.shape[0] // 16][i] == 0 and result[11 * result.shape[0] // 16][i - 1] != 0):
count += 1
width.append(0)
posi.append(i)
if (result[11 * result.shape[0] // 16][i] == 0):
width[count - 1] += 1
width_length = 0
width_jiandao = True
for i in range(count):
if width[i] > 45:
print('bu1')
return 2;
if width[i] <= 20 or width[i] >= 40:
width_jiandao = False
width_length += width[i]
if width_jiandao == True and count == 2:
return 1;
if (area > 14000 or count >= 3):
print('bu2')
return 2;
if (width_length < 110):
print('jian dao')
return 1;
else:
print('bu3')
return 2;
def show():
box = []
box.append("石头")
box.append("剪刀")
box.append("布")
capture = cv2.VideoCapture(0)
cv2.namedWindow("camera", 1)
start_time = time.time()
while (1):
ha, img = capture.read()
end_time = time.time()
cv2.rectangle(img, (426, 0), (640, 250), (0, 255, 0))
# (426, 0), (640, 250), 分别代表左上角和右下角的两个坐标点 (0, 255, 0)分别代指B G R
cv2.putText(img, str(int(5 - (end_time - start_time))), (100, 100), cv2.FONT_HERSHEY_TRIPLEX, 2, 255, 0)
# cv2.putText(图片, 添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细)
# cv2.FONT_HERSHEY_SIMPLEX 字体效果 https://www.pianshen.com/article/2216678823/
cv2.imshow("camera", img)
if (end_time - start_time > 5):
break
if (cv2.waitKey(30) >= 0):
break
ha, img = capture.read()
capture.release()
cv2.imshow("camera", img)
img = img[0:210, 426:640]
cv2.imwrite("wif.jpg", img)
p1 = judge()
print('你出的是',box[p1],'\n')
cv2.destroyAllWindows()
def main():
i=0
while (i<5):
i=i+1
# 清空屏幕
os.system('cls')
show()
main()
编写不易,求个点赞!!!!!!!
“你是谁?”
“一个看帖子的人。”
“看帖子不点赞啊?”
“你点赞吗?”
“当然点了。”
“我也会点。”
“谁会把经验写在帖子里。”
“写在帖子里的那能叫经验贴?”
“上流!”
cheer!!!