使用 OpenCV 和 Python 进行手指检测和跟踪

2025-06-08

使用 OpenCV 和 Python 进行手指检测和跟踪

TL;DR。代码在这里

手指检测是许多计算机视觉应用的重要功能。在本应用中,我们采用基于直方图的方法将手从背景帧中分离出来。为了获得最佳效果,我们使用阈值和滤波技术进行背景消除。

我在检测手指时面临的挑战之一是区分手部与背景,并识别指尖。我将向你展示我在这个项目中使用的手指追踪技术。想了解手指检测和追踪的具体操作,请观看此视频。

在需要追踪用户手部运动的应用中,肤色直方图非常有用。该直方图可用于从图像中减去背景,仅保留包含肤色的部分。

检测皮肤的一个更简单的方法是找到特定 RGB 或 HSV 范围内的像素。如果您想了解更多关于此方法的信息,请关注此处

上述方法的问题在于,光照条件和肤色的变化会严重影响皮肤检测。而直方图则更准确,并且能够将当前的光照条件考虑在内。

交出矩形

框架上绘制了绿色矩形,用户将手放在这些矩形内。应用程序从用户手上采集肤色样本,然后创建直方图。

矩形使用以下函数绘制:

def draw_rect(frame):
rows, cols, _ = frame.shape
global total_rectangle, hand_rect_one_x, hand_rect_one_y, hand_rect_two_x, hand_rect_two_y
hand_rect_one_x = np.array(
[6 * rows / 20, 6 * rows / 20, 6 * rows / 20, 9 * rows / 20, 9 * rows / 20, 9 * rows / 20, 12 * rows / 20,
12 * rows / 20, 12 * rows / 20], dtype=np.uint32)
hand_rect_one_y = np.array(
[9 * cols / 20, 10 * cols / 20, 11 * cols / 20, 9 * cols / 20, 10 * cols / 20, 11 * cols / 20, 9 * cols / 20,
10 * cols / 20, 11 * cols / 20], dtype=np.uint32)
hand_rect_two_x = hand_rect_one_x + 10
hand_rect_two_y = hand_rect_one_y + 10
for i in range(total_rectangle):
cv2.rectangle(frame, (hand_rect_one_y[i], hand_rect_one_x[i]),
(hand_rect_two_y[i], hand_rect_two_x[i]),
(0, 255, 0), 1)
return frame
def draw_rect(frame):
rows, cols, _ = frame.shape
global total_rectangle, hand_rect_one_x, hand_rect_one_y, hand_rect_two_x, hand_rect_two_y
hand_rect_one_x = np.array(
[6 * rows / 20, 6 * rows / 20, 6 * rows / 20, 9 * rows / 20, 9 * rows / 20, 9 * rows / 20, 12 * rows / 20,
12 * rows / 20, 12 * rows / 20], dtype=np.uint32)
hand_rect_one_y = np.array(
[9 * cols / 20, 10 * cols / 20, 11 * cols / 20, 9 * cols / 20, 10 * cols / 20, 11 * cols / 20, 9 * cols / 20,
10 * cols / 20, 11 * cols / 20], dtype=np.uint32)
hand_rect_two_x = hand_rect_one_x + 10
hand_rect_two_y = hand_rect_one_y + 10
for i in range(total_rectangle):
cv2.rectangle(frame, (hand_rect_one_y[i], hand_rect_one_x[i]),
(hand_rect_two_y[i], hand_rect_two_x[i]),
(0, 255, 0), 1)
return frame

这里没什么复杂的。我创建了四个数组hand_rect_one_xhand_rect_one_y来保存每个矩形的坐标。然后代码会遍历这些数组,并使用 将它们绘制到框架上。这里只是数组的长度,hand_rect_two_xhand_rect_two_ycv2.rectangletotal_rectangle9

现在用户知道将手掌放在哪里了,下一步是从这些矩形中提取像素并使用它们生成 HSV 直方图。

def hand_histogram(frame):
global hand_rect_one_x, hand_rect_one_y
hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
roi = np.zeros([90, 10, 3], dtype=hsv_frame.dtype)
for i in range(total_rectangle):
roi[i * 10: i * 10 + 10, 0: 10] = hsv_frame[hand_rect_one_x[i]:hand_rect_one_x[i] + 10,
hand_rect_one_y[i]:hand_rect_one_y[i] + 10]
hand_hist = cv2.calcHist([roi], [0, 1], None, [180, 256], [0, 180, 0, 256])
return cv2.normalize(hand_hist, hand_hist, 0, 255, cv2.NORM_MINMAX)
def hand_histogram(frame):
global hand_rect_one_x, hand_rect_one_y
hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
roi = np.zeros([90, 10, 3], dtype=hsv_frame.dtype)
for i in range(total_rectangle):
roi[i * 10: i * 10 + 10, 0: 10] = hsv_frame[hand_rect_one_x[i]:hand_rect_one_x[i] + 10,
hand_rect_one_y[i]:hand_rect_one_y[i] + 10]
hand_hist = cv2.calcHist([roi], [0, 1], None, [180, 256], [0, 180, 0, 256])
return cv2.normalize(hand_hist, hand_hist, 0, 255, cv2.NORM_MINMAX)

这里函数将输入帧转换为 HSV。我们使用 Numpy 创建一张大小为 1000 像素、[90 * 10]包含3颜色通道的图像,并将其命名为ROI (感兴趣区域)。然后,它从绿色矩形中取出 900 像素的值,并将它们放入 ROI 矩阵中。

使用 ROI 矩阵创建cv2.calcHist肤色直方图,并cv2.normalize使用范数 Type 对该矩阵进行归一化cv2.NORM_MINMAX。现在,我们有一个直方图来检测帧中的皮肤区域。

现在用户知道将手掌放在哪里了,下一步是从这些矩形中提取像素并使用它们生成 HSV 直方图。

现在我们得到了肤色直方图,可以用它来查找帧中包含肤色的成分。OpenCV 为我们提供了一种便捷的方法,cv2.calcBackProject它使用直方图来分离图像中的特征。我使用这个函数将肤色直方图应用到帧中。如果您想了解更多关于反投影的知识,可以阅读这里这里

def hist_masking(frame, hist):
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
dst = cv2.calcBackProject([hsv], [0, 1], hist, [0, 180, 0, 256], 1)
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (31, 31))
cv2.filter2D(dst, -1, disc, dst)
ret, thresh = cv2.threshold(dst, 150, 255, cv2.THRESH_BINARY)
thresh = cv2.merge((thresh, thresh, thresh))
return cv2.bitwise_and(frame, thresh)
view raw Back Project.py hosted with ❤ by GitHub
def hist_masking(frame, hist):
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
dst = cv2.calcBackProject([hsv], [0, 1], hist, [0, 180, 0, 256], 1)
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (31, 31))
cv2.filter2D(dst, -1, disc, dst)
ret, thresh = cv2.threshold(dst, 150, 255, cv2.THRESH_BINARY)
thresh = cv2.merge((thresh, thresh, thresh))
return cv2.bitwise_and(frame, thresh)
view raw Back Project.py hosted with ❤ by GitHub

在前两行中,我将输入帧转换为 HSV 格式,然后应用了cv2.calcBackProject肤色直方图hist。接下来,我使用了滤波和阈值函数来平滑图像。最后,我使用该cv2.bitwise_and函数对输入帧进行了蒙版处理。最终的帧应该只包含帧中的肤色区域。

手与背景分离(1)

手与背景分离(2)

现在我们有一个仅包含肤色区域的框架,但我们真正想要的是找到指尖的位置。使用 OpenCV,你可以在框架中查找轮廓。如果你不知道什么是轮廓,可以阅读这里。利用轮廓,你可以找到凸起缺陷,这可能是指尖的位置。

在我的应用中,我需要找到用户瞄准的指尖。为此,我确定了凸度缺陷,即距离轮廓质心最远的位置。具体代码如下:

def manage_image_opr(frame, hand_hist):
hist_mask_image = hist_masking(frame, hand_hist)
contour_list = contours(hist_mask_image)
max_cont = max_contour(contour_list)
cnt_centroid = centroid(max_cont)
cv2.circle(frame, cnt_centroid, 5, [255, 0, 255], -1)
if max_cont is not None:
hull = cv2.convexHull(max_cont, returnPoints=False)
defects = cv2.convexityDefects(max_cont, hull)
far_point = farthest_point(defects, max_cont, cnt_centroid)
print("Centroid : " + str(cnt_centroid) + ", farthest Point : " + str(far_point))
cv2.circle(frame, far_point, 5, [0, 0, 255], -1)
if len(traverse_point) < 20:
traverse_point.append(far_point)
else:
traverse_point.pop(0)
traverse_point.append(far_point)
draw_circles(frame, traverse_point)
def manage_image_opr(frame, hand_hist):
hist_mask_image = hist_masking(frame, hand_hist)
contour_list = contours(hist_mask_image)
max_cont = max_contour(contour_list)
cnt_centroid = centroid(max_cont)
cv2.circle(frame, cnt_centroid, 5, [255, 0, 255], -1)
if max_cont is not None:
hull = cv2.convexHull(max_cont, returnPoints=False)
defects = cv2.convexityDefects(max_cont, hull)
far_point = farthest_point(defects, max_cont, cnt_centroid)
print("Centroid : " + str(cnt_centroid) + ", farthest Point : " + str(far_point))
cv2.circle(frame, far_point, 5, [0, 0, 255], -1)
if len(traverse_point) < 20:
traverse_point.append(far_point)
else:
traverse_point.pop(0)
traverse_point.append(far_point)
draw_circles(frame, traverse_point)

框架轮廓 (1)

框架中的轮廓 (2)<br>

然后确定最大的轮廓。对于最大的轮廓,它会找到外壳、质心和缺陷。

红色圆圈表示缺陷,紫色圆圈表示质心

现在你已经找到了所有这些缺陷,你只需要找到距离轮廓中心最远的那个点。这个点被假设为指向手指的点。中心点是紫色,最远的点是红色。就这样,你找到了指尖。

质心为紫色,最远点为红色

到目前为止,所有困难的部分都已完成,现在我们要做的就是创建一个list来存储 帧中变化的位置farthest_point。您可以自行决定要存储多少个变化点。我这里只存储20点。

使用 OpenCV 和 Python 进行手指检测和跟踪<br>

最后,感谢您阅读这篇文章。想了解更多精彩文章,您也可以在 Twitter 上关注我 iamarpandey,或在 Github 上 关注我amarlearning

祝你编程愉快!🤓

鏂囩珷鏉ユ簮锛�https://dev.to/amarlearning/finger-detection-and-tracking-using-opencv-and-python-586m
PREV
如何使用 JavaScript Promises 摆脱回调地狱
NEXT
为什么我们需要在类组件的构造函数中绑定方法?