double contArea = fabs(contourArea(contours[i])); double contLength = arcLength(contours[i], false); double contCircularity = contLength*contLength / contArea;//周长面积比 Moments omement; omement = moments(contours[i]); Mat hu; HuMoments(omement, hu);//hu矩,主要用到前两个矩 <span style="font-family: Arial, Helvetica, sans-serif;">//然后将两者结合起来判断</span>(这个可以加上各种学习算法优化,但不是我的主要想谈论的点,因为学习之类的算法接触得比较少,不理解原理),也就是说,识别到手之后,给跟踪器一个初始化的框,这个框的选取非常重要,可以通过框选中的内容的特征如颜色直方图,梯度等,这些决定之后的跟踪是否能够很好地收敛于目标物体。
unsigned int colourModel::findBin(unsigned char R,unsigned char G,unsigned char B) { //根据RGB颜色返回直方图的位置 unsigned int r,g,b; r = (unsigned int)floor( (float)(R/BINSIZE) ); g = (unsigned int)floor( (float)(G/BINSIZE) ); b = (unsigned int)floor( (float)(B/BINSIZE) ); return (r + BPC*g + BPC*BPC*b); }之后选取Epanechnikov作为核函数计算目标模型直方图,用公式表示就是:
double CMsTracker::kernel(int x,int y,int half_x,int half_y) { double euclideanDistance = sqrt( pow( ( (double)(x)/(double)(half_x) ) ,2.0) +pow( ( (double) (y)/(double)(half_y) ) ,2.0) ); if (euclideanDistance > 1) return( 0.0); else return(1.0-pow(euclideanDistance,2)); } void CMsTracker::evalKernel (double*** kArray,int half_x,int half_y)//计算核函数直方图 { for (int x = -half_x; x < half_x; x++) { for (int y = -half_y; y < half_y; y++) { (*kArray)[x+half_x][y+half_y] = kernel(x,y,half_x,half_y); } } } void CMsTracker::evalKernelDeriv (int*** kArray,int half_x,int half_y) //计算微分核函数直方图,求局部最小值 { double euclideanDistance; for (int x = -half_x;x < half_x; x++) { for (int y = -half_y;y < half_y; y++) { euclideanDistance = sqrt( pow( ( (double)(x)/(double)(half_x) ) ,2.0) +pow( ( (double)(y)/(double)(half_y) ) ,2.0) ); if (euclideanDistance > 1) (*kArray)[x+half_x][y+half_y] = 0; else (*kArray)[x+half_x][y+half_y] = 1; } } }候选区域做类似目标区域的操作,同样能得到一个直方图, 那接下的是比较并且求出候选区域的位置( 在 MeanShift的已实现代码中,是用Bhattacharryya--巴氏系数, 来衡量目标模型跟候选目标的相似度),这里计算中心的位移得到:
void CMsTracker::computeDisplacement(double*** weights,int*** kArray,unsigned int centre_x,unsigned int centre_y,int half_x,int half_y,int *dx,int *dy) { double weight_sum = 0; double x_sum =0, y_sum=0; double curPixelWeight; for (int x = -half_x;x < half_x; x++) { for (int y = -half_y;y < half_y; y++) { curPixelWeight = (*weights)[x+half_x][y+half_y]*(*kArray)[x+half_x][y+half_y]; weight_sum += curPixelWeight; x_sum += x*curPixelWeight; y_sum += y*curPixelWeight; } } *dx = (int)floor(x_sum/weight_sum); *dy = (int)floor(y_sum/weight_sum); }
采用freeman链码对手势的运动方向进行编码,模版如图:
除了,上述简单的一个方向判断外,我们还用MHI(运动历史图像)来求手的运动的方向,它是一种人体运动的全局描述方法,其每个像素值是此像素点上运动时间的方程。记B(x,y,t)为运动人体的二值轮廓图像序列,则运动历史图像H(x,y,t)的计算方法为:
τ为时间窗口长度,即一个运动视频序列的帧数,表示运动的持续时间。因此,得到的MHI是一个灰度图,亮度大的区域表示最新运动的区域,随着时间的推移,旧的区域会慢慢变暗,直至变为0。
用第一个方法写出的代码如下:int calcDirection(Point current, int& preDirection) { motionPoint.push_back(current); int pointSize = motionPoint.size(); int currDirection; //计算当前的方向 if (pointSize < 5) //开始时运动的5个点不计算在内 return 0; Point pre = motionPoint[pointSize - 2];//前一个点 int absDist = abs(current.x - pre.x); if (absDist < 5){ //小范围扰动 currDirection = 0; } else{ currDirection = (current.x - pre.x) / absDist; } if (currDirection != preDirection){ if (currDirection == -preDirection) dCount++; else{ tCount++; if (tCount++ > 4){ preDirection = currDirection; tCount = 0;dCount = 0; } } if (dCount > 5 && pointSize >10){ motionPoint.clear(); preDirection = currDirection; dCount = 0; tCount = 0; return -currDirection; } } return 0; }用MHI写的判断方向的代码:
void update_mhi(const Mat& img, Rect region, int diff_threshold) { double timestamp = (double)clock() / CLOCKS_PER_SEC; Size size = img.size(); int idx1 = last; double angle; if (mhi.size() != size) { mhi = Mat::zeros(size, CV_32F); zplane = Mat::zeros(size, CV_8U); buf[0] = Mat::zeros(size, CV_8U); buf[1] = Mat::zeros(size, CV_8U); } cvtColor(img, buf[last], COLOR_BGR2GRAY); int idx2 = (last + 1) % 2; last = idx2; Mat silh = buf[idx2]; absdiff(buf[idx1], buf[idx2], silh); threshold(silh, silh, diff_threshold, 1, THRESH_BINARY); updateMotionHistory(silh, mhi, timestamp, MHI_DURATION); mhi.convertTo(mask, CV_8U, 255. / MHI_DURATION, (MHI_DURATION - timestamp)*255. / MHI_DURATION); calcMotionGradient(mhi, mask, orient, MAX_TIME_DELTA, MIN_TIME_DELTA, 3); region &= Rect(0, 0, mhi.cols, mhi.rows); Mat silh_roi = silh(region); Mat mhi_roi = mhi(region); Mat orient_roi = orient(region); Mat mask_roi = mask(region); angle = calcGlobalOrientation(orient_roi, mask_roi, mhi_roi, timestamp, MHI_DURATION); angle = 360.0 - angle; }