上篇介绍了如何训练自己的特征库
使用过vuforia或者亮风台的朋友应该知道,这两个平台对图片的跟踪的准备工作是很简单的,只需要几张样本图片就可以做了。
但是按照上篇的介绍,如果用CascadeClassifier进行物体跟踪就需要非常非常多的样本,那么,要对图片进行识别跟踪就没有像上面说的两个平台那样简便的办法吗?
答案是可以选择feature2d。
1.feature2d是什么?
字面上就很容易理解,feature2d是做2d图像的特征处理的。比如我们可以用它做指纹取样、识别,图像的角点提取,图像的跟踪等等。
这个模块就在opencv的modules/feature2d里面。
2.用feature2d做图片跟踪需要准备什么?
说这个之前,先要说一下nonfree模块,我在第一篇提到过这个。
图像的特征提取有很多算法,有些算法并非由opencv提供,opencv提出了特征提取,比对的准则,而大多算法是由opencv的使用者们完成的。
在opencv2.x,这些算法都放在nonfree模块里,但是到了opencv3.x,nonfree模块就被去掉了。取而代之的是opencv_contrib库,它同样一个使用BSD协议的开源库。
遗憾的是,目前笔者找到的opencv_contrib只能在x86系统上成功编译,也就是说目前还不支持像android,iOS这样的使用arm架构的系统。
然而,我们要做的功能只是用了它其中的xfeature2d模块,这只是它诸多模块中的之一,而且跟其他模块没有任何耦合。
没错,我们完全可以把xfeature2d的源码单独做一个库,然后用在我们的功能里。笔者已经成功编译出了一个xfeature2d的静态库,然而,限于版权问题,这里就不上代码了。
大家自己动动灵活的小脑袋吧。
3.一切准备就绪,然后怎么做?
既然要做图片跟踪,当然先要做图片对比,那么怎么做图片的对比呢?我们使用特征点对比,这样我们先要做的就是用feature2d提取图片的特征点。
特征点提取的算法有很多,笔者用过的大体有这三个:SURF,SIFT,ORB。在这里我就介绍SIFT算法吧,我更愿意称之为算子(因为听起来很专业嘛)。
这三种算子有何区别呢?我可以很大胆的告诉你:不清楚。
我不研究它们的工作原理,哪个好用用哪个,这是我一贯的作风。
跟第二篇一样,做一个摄像头预览的ViewController。
我们需要一张样本图片,就是我们需要识别跟踪的图片,用这张图片建立一个灰度cv::Mat。
NSString *imp = [[NSBundle mainBundle] pathForResource:ifn ofType:@"jpg"];
Mat gray = imread([imp cStringUsingEncoding:NSUTF8StringEncoding], IMREAD_GRAYSCALE);
vector keypoints;
Mat descriptor;
Ptr detector = SIFT::create();
detector->detect(gray, keypoints);
Ptr extractor = SIFT::create();
extractor->compute(gray, keypoints, descriptor);
这时候样本的特征就取好了。
在取到摄像头帧之后,做第二篇一样的处理,得到一个Mat,然后灰化
Mat image, gray;
image = [得到的mat];
cvtColor(image,gray,CV_BGR2GRAY);
vector kp;
Mat d;
Ptr detector = SIFT::create();
detector->detect(gray, kp);
if (kp.size() <= 0) {
return;
}
Ptr extractor = SIFT::create();
extractor->compute(gray, kp, d);
接下来就可以做对比,得到一个DMatch数组
vector matches;
BFMatcher matcher = BFMatcher();
matcher.match(descriptor, d, matches);
double maxDist = 0.0;
double minDist = DBL_MAX;
for (int i=0;i maxDist) {
maxDist = dist;
}
}
double maxGoodMatchDist = THRESHOLD * minDist;
vector goodMatches;
for( int i = 0; i
vector modePoints;
vector scenePoints;
for (int i=0;i= keypoints.size()) {
continue;
}
if (goodMatche.trainIdx < 0 || goodMatche.trainIdx >= kp.size()) {
continue;
}
modePoints.push_back(keypoints[goodMatche.queryIdx].pt);
scenePoints.push_back(kp[goodMatche.trainIdx].pt);
}
Mat homography = findHomography(modePoints, scenePoints, CV_RANSAC);
if (homography.data == NULL) {
return;
}
std::vector objCorners(4);
objCorners[0] = cvPoint(0,0);
objCorners[1] = cvPoint( mode.mat.cols, 0 );
objCorners[2] = cvPoint( mode.mat.cols, mode.mat.rows );
objCorners[3] = cvPoint( 0, mode.mat.rows );
std::vector sceneCorners(4);
perspectiveTransform( objCorners, sceneCorners, homography);
if (!(sceneCorners[1].x > sceneCorners[0].x
&& sceneCorners[2].y > sceneCorners[1].y
&& sceneCorners[3].x < sceneCorners[2].x
&& sceneCorners[0].y < sceneCorners[3].y)) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
UIGraphicsBeginImageContext(CGSizeMake(img.size.width, img.size.height));
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(contextRef, 4);
CGContextSetRGBStrokeColor(contextRef, 1.0, 0.0, 0.0, 1);
CGContextMoveToPoint(contextRef, sceneCorners[0].x, sceneCorners[0].y);
CGContextAddLineToPoint(contextRef, sceneCorners[1].x, sceneCorners[1].y);
CGContextAddLineToPoint(contextRef, sceneCorners[2].x, sceneCorners[2].y));
CGContextAddLineToPoint(contextRef, sceneCorners[3].x, sceneCorners[3].y));
CGContextAddLineToPoint(contextRef, sceneCorners[0].x, sceneCorners[0].y);
CGContextStrokePath(contextRef);
UIImage *rectImage = UIGraphicsGetImageFromCurrentImageContext();
_overlayImageView.image = rectImage;
UIGraphicsEndImageContext();
});