经过前几次的努力,基本上确定了最终的眼球追踪模型的方向,所以这一周工作重心开始转向其他模块,包括情绪分析、手势识别等。以及工程的实现。我这周负责的任务主要是手势模块,重点核心的功能是通过手势来实现翻页(包括上下移动,左右翻页等)
因为我们的应用主要以实现交互为主,所以对于手势,只要能够实现简单的一些操作即可,目的是在一些不方便使用鼠标键盘操作的情况下实现简单的阅读交互。因为情绪分析识别和视线追踪都会使用到机器学习模型,所以在手势识别上,我优先考虑通过图像处理的方式来进行判断,实现快速高效的判断。
考虑日常使用的场景,在摄像头范围内,通过手势来翻页,那么在摄像头视频的帧序列中,具有明显运动的特征的应该就是手部(无论是在速度上还是在幅度上),所以通过获取一部分连续的摄像头视频帧序列,分析图像之间的区别来作出判断。
所以这个思路归纳下来就是帧间差分法。
帧间差分法是一种通过对视频图像序列中相邻两帧作差分运算来获得运动目标轮廓的方法,它可以很好地适用于存在多个运动目标和摄像机移动的情况。当监控场景中出现异常物体运动时,帧与帧之间会出现较为明显的差别,两帧相减,得到两帧图像亮度差的绝对值,判断它是否大于阈值来分析视频或图像序列的运动特性,确定图像序列中有无物体运动。图像序列逐帧的差分,相当于对图像序列进行了时域下的高通滤波。
在图像二值化以后,手一开始位置以及移动后的位置,分析最多个帧的序列可以明显看出来轮廓位置整体是向左移动的。
使用OpenCV来实现。
开始考虑通过
cv2.findContours()
得到轮廓,根据轮廓的中点的变化来实现,但是因为背景的复杂性,已经物体的复杂性,轮廓的数量不稳定,所以这个办法会让误差不可控。
下面引出
cv2.findContours()
函数。goodFeaturesToTrack函数可以获取图像中的最大特征值的角点,所以据此判断会更加简单。
实现步骤如下:
对两帧图做差分得到新图像
新图像灰度化、二值化处理
用goodFeaturesToTrack函数得到最大特征值的角点
计算角点的平均点,存入队列中
维护一个固定长度的队列,队列满时计算队列中数据(点位置)的移动情况,来确定运动方向
代码如下:
import cv2
import numpy as np
from queue import Queue
cap_region_x_begin=0.5 # start point/total width
cap_region_y_end=0.8 # start point/total width
bgSubThreshold = 50
learningRate = 0
# 去除背景
def removeBG(frame):
fgmask = bgModel.apply(frame,learningRate=learningRate)
# kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
# res = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
kernel = np.ones((3, 3), np.uint8)
fgmask = cv2.erode(fgmask, kernel, iterations=1)
res = cv2.bitwise_and(frame, frame, mask=fgmask)
return res
#获取摄像头
camera = cv2.VideoCapture(0)
width = int(camera.get(3))
height = int(camera.get(4))
firstFrame = None
lastDec = None
firstThresh = None
feature_params = dict(maxCorners=100, qualityLevel=0.3, minDistance=7, blockSize=7)
# Parameters for lucas kanade optical flow
lk_params = dict(winSize=(15, 15), maxLevel=2, criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03))
color = np.random.randint(0, 255, (100, 3))
num = 0
q_x = Queue(maxsize=10)
q_y = Queue(maxsize=10)
while camera.isOpened():
grabbed, frame = camera.read()
# print(grabbed)
frame = cv2.bilateralFilter(frame, 5, 50, 100) # smoothing filter
frame = cv2.flip(frame, 1) # 水平翻转图像
# cv2.rectangle(frame, (int(cap_region_x_begin * frame.shape[1]), 0),
# (frame.shape[1], int(cap_region_y_end * frame.shape[0])), (255, 0, 0), 2)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (21, 21), 0)
if firstFrame is None:
firstFrame = gray
continue
frameDelta = cv2.absdiff(firstFrame, gray)
thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1]
# 下面的是几种不同的二值化的方法,感觉对我来说效果都差不多
# thresh = cv2.adaptiveThreshold(frameDelta,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
# cv2.THRESH_BINARY,11,2)
# thresh = cv2.adaptiveThreshold(frameDelta,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
# cv2.THRESH_BINARY,11,2)
thresh = cv2.dilate(thresh, None, iterations=2)
p0 = cv2.goodFeaturesToTrack(thresh, mask=None, **feature_params)
if p0 is not None:
x_sum = 0
y_sum = 0
for i, old in enumerate(p0):
x, y = old.ravel()
x_sum += x
y_sum += y
x_avg = x_sum / len(p0)
y_avg = y_sum / len(p0)
if q_x.full():
# print(list(q_x.queue))
qx_list = list(q_x.queue)
key = 0
diffx_sum = 0
for item_x in qx_list:
key += 1
if key < 10:
diff_x = item_x - qx_list[key]
diffx_sum += diff_x
# print diff_x
if diffx_sum < 0:# and x_avg < 500:
# print "some coming form left"
print()
cv2.putText(frame, "To right", (100, 100), 0, 0.5, (0, 0, 255), 2)
else:
cv2.putText(frame, "To left", (100, 100), 0, 0.5, (0, 0, 255), 2)
pass
# print(x_avg)
q_x.get()
q_x.put(x_avg)
cv2.putText(frame, str(x_avg), (300, 100), 0, 0.5, (0, 0, 255), 2)
frame = cv2.circle(frame, (int(x_avg), int(y_avg)), 5, color[i].tolist(), -1)
cv2.imshow("Camera", frame)
cv2.waitKey(10)
firstFrame = gray.copy()
k = cv2.waitKey(10)
if k == 27: # press ESC to exit
break
elif k == ord('b'): # press 'b' to capture the background
bgModel = cv2.createBackgroundSubtractorMOG2(0, bgSubThreshold)
isBgCaptured = 1
print( 'Background Captured')
elif k == ord('r'): # press 'r' to reset the background
bgModel = None
triggerSwitch = False
isBgCaptured = 0
print ('Reset BackGround')
elif k == ord('n'):
triggerSwitch = True
print ('Trigger On')
camera.release()
cv2.destroyAllWindows()
在实际应用中,如果人也在动的话是有干扰的,无法精确的判断出手势...但是在人比较静止的情况下,手势的方向可以准确判断。
在下周的工作中,希望对背景做一些处理,尽量只保留手部的动作。还是尽量避免通过神经网络来做...直接通过openCV去除具有普适性的背景会好一些....如果最后效果不佳..那就只能上CNN提取特征了,但是仍然不好解决泛化的问题。