基于余弦定理的凸包检测算法
凸包与凸缺陷
凸包(ConvexHull)是计算几何当中的一个数学概念。简单来讲,所谓“凸包”就是能够“包裹”平面区域内所有点的一个凸多边形。如图所示,假设平面上有p0∼p12共13个点,当我们连接p0、p1、p3、p10、p12这些点作多边形时,该多边形能够把该区域内所有点都“包”起来,我们就称该凸多边形为“凸包”,而点p0、p1、p3、p10、p12称之为凸包点
通常我们使用Graham扫描法来计算图像中的凸包点,在opencv中,内置函数cv2.convexHull可用来计算图像的凸包点,而函数cv2.convexityDefects则用来计算图像中的凸包缺陷点,如图所示,A、B、C、D、E、F、G称为凸包缺陷点,用上述凸包检测法可检测出凸包M、N,则可由余弦定理公式3-2计算出MC与CN的夹角,设定该夹角的阈值,当阈值小于某值时,即可判定手势数字。使用该算法的优点是算法实现简单,但缺点也很明显,该算法所能识别的种类局限性太大,且对前期图像预处理的要求较高。
代码实例
# 基于凸包余弦的手势分类算法(对处理出来的手势图像要求较高)
def gesture_convexityDefects(img,cnt):
hull = cv.convexHull(cnt, returnPoints=False) # 计算凸包
defects = cv.convexityDefects(cnt, hull) # 计算凹陷点,返回凹陷点的起始,结束,最远,以及最远距离
moments = cv.moments(cnt) # 求最大区域轮廓的各阶矩
center = (int(moments['m10'] / moments['m00']), int(moments['m01'] / moments['m00'])) # 画出质心
cv.circle(img, center, 8, (0, 0, 255), -1) # 画出重心
count=0
if defects is None:
return img,-1
else:
for i in range(defects.shape[0]):
s, e, f, d = defects[i][0]
start = cnt[s][0]
end = cnt[e][0]
far = cnt[f][0]
cv.line(img, (start[0], start[1]), (end[0], end[1]), (0, 255, 0), 2)
cv.line(img, (start[0], start[1]), (far[0], far[1]), (0, 0, 255), 2)
cv.line(img, (end[0], end[1]), (far[0], far[1]), (0, 0, 255), 2)
a = math.sqrt((end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2)
b = math.sqrt((far[0] - start[0]) ** 2 + (far[1] - start[1]) ** 2)
c = math.sqrt((end[0] - far[0]) ** 2 + (end[1] - far[1]) ** 2)
angle = math.acos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c)) # cosine theorem
if angle <= math.pi / 2:
count += 1
cv.circle(img, far, 8, [211, 84, 0], -1)
return img,count
效果如下:
基于HU不变矩的相似度计算
矩最初是力学当中的一个概念,后来在统计学中则用于表示随机变量的分布,而在图像中,我们也可以借高等数学用积分计算矩的方法,用离散求和来计算图像的矩,由此我们可以计算图像的面积、质心等特征[11]。
1962年,HU提出了7个不变矩组,称为HU不变矩,该不变矩组使得图像在大小、位置、平移和旋转特性上具有不变性[11]。其定义如式3-12所示。
换言之,对于一幅图像中的某个物体,如果该物体仅仅只是在大小、位置或方向上发生了变化,而其形状不发生改变,则该图像的上述不变矩组具有不变性。如当我们在一幅图像中捕获到一只手掌,而在另一幅图像中该手掌仅仅只是发生了平移和旋转,那么这两幅图像的不变矩特征是相似的[12]。
由此我们可以便可构造出一种类似于模板匹配的算法来对获取的手势图像进行分类识别。不同的是,传统模板匹配算法是分别遍历模板图与待匹配图的各像素点进行匹配,该匹配算法的缺陷是显而易见的,因为一旦待匹配图当中的手势仅仅只是发生了旋转或者大小发生了变化就会使得与模板图像难以匹配成功,而HU不变矩特征则刚好克服了该特性,我们只需要“匹配”模板图与待匹配图的不变矩特性即可。显然,相对于凸包余弦手势分类算法,该算法可扩充手势库的识别种类。
$$
\begin{aligned}
\Phi_{1}= & \eta_{20}+\eta_{02} \
\Phi_{2}= & \left(\eta_{20}-\eta_{02}\right)^{2}-4 \eta_{11}^{2} \
\Phi_{3}= & \left(\eta_{30}-3 \eta_{12}\right)^{2}+\left(3 \eta_{21}+\eta_{03}\right)^{2} \
\Phi_{4}= & \left(\eta_{30}+\eta_{12}\right)^{2}+\left(\eta_{21}+\eta_{03}\right)^{2} \
\Phi_{5}= & \left(\eta_{30}-3 \eta_{12}\right)\left(\eta_{30}-\eta_{12}\right) \times\left[\left(\eta_{30}+\eta_{12}\right)^{2}-3\left(\eta_{21}+\eta_{03}\right)^{2}\right] \
& +\left(3 \eta_{21}-\eta_{03}\right) \times\left(\eta_{21}+\eta_{03}\right)\left[3\left(\eta_{30}+\eta_{12}\right)^{2}-\left(\eta_{21}+\eta_{03}\right)^{2}\right] \
\Phi_{6}= & \left(\eta_{20}-\eta_{02}\right)\left[\left(\eta_{30}+\eta_{12}\right)^{2}-\left(\eta_{21}+\eta_{03}\right)^{2}\right]+4 \eta_{11}\left(\eta_{30}+\eta_{12}\right)\left(\eta_{21}+\eta_{03}\right) \
\Phi_{7}= & \left(\eta_{21}-\eta_{03}\right)\left(\eta_{30}+\eta_{12}\right) \times\left[\left(\eta_{30}+\eta_{12}\right)^{2}-3\left(\eta_{21}+\eta_{03}\right)^{2}\right] \
& +\left(3 \eta_{12}-\eta_{30}\right) \times\left(\eta_{21}+\eta_{03}\right)\left[3\left(\eta_{30}+\eta_{12}\right)^{2}-\left(\eta_{21}+\eta_{03}\right)^{2}\right]
\end{aligned}
$$
分别计算模板和待匹配图的不变矩,之后计算这两个不变矩特征的距离,该距离可用欧式距离衡量也可用概率论当中的相似度进行衡量,(本文的测试代码采用概率论当中的相似度概念进行衡量),之后设定距离阈值,当最后的计算结果小于该阈值时我们就判断待匹配图像与模板图像是同一类别。
该算法的效果图如图所示。经最终测试发现,该算法虽然可以扩充手势库,但是由于阈值的影响,使得分类过程中对某些手势无法区别开来,如在测试过程中发现握拳手势与手掌手势有时无法区分,点赞手势与食指手势有有时无法区分。
代码实例
# HU不变矩手势识别算法
def HU_recognition(cnt):
names = os.listdir(path0)
# print(names)
img_name = []
for i in range(len(names)):
img_name.append(path + names[i])
# print(img_name)
img = []
for i in range(len(img_name)):
src = cv.imread(img_name[i])
# src = cv.GaussianBlur(src, (3, 3), 1) # 高斯平滑处理
img.append(src)
read_data = pd.read_csv(abs_path + r"\dataBase\data.csv")
# print(read_data)
scores=[]
sum_score=[0 for i in range(len(img_name))]
for j in range(3):#取三次识别结果平均值作为最终识别结果,以消除偶然误差
for i in range(len(img_name)):
column = ['x' + str(i), 'y' + str(i)]
c = read_data[column].dropna() # 删除无效值
tem_cnt = np.expand_dims(c, axis=1) # 升维,将dataframe转换为矩阵
tem_cnt = tem_cnt.astype(np.int64) # 转换矩阵数据类型为int64
score = cv.matchShapes(cnt, tem_cnt, cv.CONTOURS_MATCH_I2, 0) # 利用不变矩计算相似度,相似度越小匹配度越高
# print(score)
# cv_show("imgMatch",img[i])
scores.append(score)
sum_score = [(sum_score[k] + scores[k]) for k in range(len(img_name))]
avg_score=[sum_score[t]/3 for t in range(len(sum_score))]
min=np.min(avg_score)
index=np.argmin(avg_score)
if min>0 and min<=0.89:
return img[index],index,min
else:
return "error",-1,-1
效果: