这几天在做一个手势识别的项目,其中最的关键一步是提取手掌中心。
获得手掌重心通常的做法是计算整个手部的重心,并以该重心位置近似手掌重心,这种方法只适用于没有手指伸出或只有一个手指伸出的情况,否则获得的手掌重心位置将严重偏离真实位置。
距离变换的基本含义是计算一个图像中非零像素点到最近的零像素点的距离,也就是到零像素点的最短距离。因此可以基于距离变换提取手掌重心。
算法基本思想:
(1)将手掌图像二值化,手掌内的区域设为白色,外部区域设为黑色。
(2)将二值化后的图像经过distanceTransform变换,得到dist_image,其中每个像素点的值是该像素点到其最近的零像素点的距离。
(3)找到dist_image的最大值(即圆的半径R),并记录下位置(即圆心坐标)。
代码如下:
#include "opencv2/opencv.hpp" #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <vector> using namespace cv; using namespace std; pair<Point,double> DetectInCircles(vector<Point> contour,Mat src) { Mat dist_image; distanceTransform(src,dist_image,CV_DIST_L2,3); int temp=0,R=0,cx=0,cy=0; int d; for (int i=0;i<src.rows;i++) for (int j=0;j<src.cols;j++) { /* checks if the point is inside the contour. Optionally computes the signed distance from the point to the contour boundary*/ d = pointPolygonTest(contour, Point2f(j, i), 0); if (d>0) { temp=(int)dist_image.ptr<float>(i)[j]; if (temp>R) { R=temp; cy=i; cx=j; } } } return make_pair(Point(cx,cy),R); } int main() { // Read input binary image Mat src= imread("D:\\mycode\\6.jpg",1); Mat image; cvtColor(src,image,CV_BGR2GRAY); vector<vector<Point>> contours; //findContours的输入是二值图像 findContours(image, contours, // a vector of contours CV_RETR_EXTERNAL, // retrieve the external contours CV_CHAIN_APPROX_NONE); // retrieve all pixels of each contours // Print contours' length轮廓的个数 cout << "Contours: " << contours.size() << endl; vector<vector<Point>>::const_iterator itContours= contours.begin(); for ( ; itContours!=contours.end(); ++itContours) { cout << "Size: " << itContours->size() << endl;//每个轮廓包含的点数 } //找到最大轮廓 int index=0,maxArea=0; for(unsigned int i=0;i<contours.size();i++) { int area=contourArea(contours[i]); if (area>maxArea) { index=i; maxArea=area; } } // draw black contours on white image Mat result(image.size(),CV_8U,Scalar(0)); drawContours(result,contours, //画出轮廓 -1, // draw all contours Scalar(255), // in black 2); // with a thickness of 2 pair<Point,double> m=DetectInCircles(contours[index],image); cout<<m.first.x<<" "<<m.first.y<<" "<<m.second<<endl; circle(src,m.first,3,Scalar(0,0,255),2); circle(src,m.first,m.second,Scalar(0,0,255),1); namedWindow("result"); imshow("result",src); waitKey(0); return 0; }
结果:
原图 dist_image 结果
其中有一点需要注意:
distanceTransform(InputArray src, OutputArray dst, int distanceType, int maskSize) Parameters: src – 8-bit, single-channel (binary) source image. dst – Output image with calculated distances. It is a 32-bit floating-point, single-channel image of the same size as src . distanceType – Type of distance. It can be CV_DIST_L1, CV_DIST_L2 , or CV_DIST_C . maskSize – Size of the distance transform mask. It can be 3, 5, or CV_DIST_MASK_PRECISE (the latter option is only supported by the first function). In case of the CV_DIST_L1 or CV_DIST_C distance type, the parameter is forced to 3 because a 3 3 mask gives the same result as 5 5 or any larger aperture. labels – Optional output 2D array of labels (the discrete Voronoi diagram). It has the type CV_32SC1 and the same size as src . See the details below.
dst为单通道的32-bit 浮点型矩阵,读取元素时需要用image.ptr<float>(i)[j],用imshow显示的时候需要用normalize(dist_image, magI, 0, 1, CV_MINMAX); 将float类型的矩阵转换到可显示图像范围 (float
[0, 1]).
另外还有一个博客值得参考:http://blog.csdn.net/wuhaibing_cver/article/details/8602461