iOS开发之opencv学习笔记四:使用feature2d识别图片
2017-06-03 10:26
726 查看
上篇介绍了如何训练自己的特征库
使用过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"];
创建一个detector跟extractor,用这个detector来提取一个KeyPoint数组,extractor提取一个descriptor
这时候样本的特征就取好了。
在取到摄像头帧之后,做第二篇一样的处理,得到一个Mat,然后灰化
Mat image, gray;
image = [得到的mat];
cvtColor(image,gray,CV_BGR2GRAY);
同样,用SIFT算子提取这张灰度图的特征kp,d。
vector<KeyPoint> kp;
Mat d;
Ptr<SIFT> detector = SIFT::create();
detector->detect(gray, kp);
if (kp.size() <= 0) {
return;
}
Ptr<SIFT> extractor = SIFT::create();
extractor->compute(gray, kp, d);
为什么要判断kp.size()?因为你的摄像头可能会得到一张纯色毫无轮廓角点的图片,这样kp.size()就是0,这就没必要做对比了。
接下来就可以做对比,得到一个DMatch数组
vector<DMatch> matches;
BFMatcher matcher = BFMatcher();
matcher.match(descriptor, d, matches);
从DMatch数组里面照到最小和最大distance
double maxDist = 0.0;
double minDist = DBL_MAX;
for (int i=0;i<matches.size();i++) {
DMatch match = matches[i];
double dist = match.distance;
if (dist < minDist) {
minDist = dist;
}
if (dist > maxDist) {
maxDist = dist;
}
}
过滤distance过大的DMatch
double maxGoodMatchDist = THRESHOLD * minDist;
vector<DMatch> goodMatches;
for( int i = 0; i <descriptor.rows; i++ )
{
if( matches[i].distance < maxGoodMatchDist ){
goodMatches.push_back(matches[i]);
}
}
这里的THERSHOLD视算子而定,SIFT可以设成2。根据过滤好的DMatch数组再来过滤不吻合的keypoint,分别得到样本和当前取到的帧里面吻合的点
vector<cv::Point> modePoints;
vector<cv::Point> scenePoints;
for (int i=0;i<goodMatches.size();i++) {
DMatch goodMatche = goodMatches[i];
if (goodMatche.queryIdx < 0 || goodMatche.queryIdx >= 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);
}
得到一个homeography
Mat homography = findHomography(modePoints, scenePoints, CV_RANSAC);
找到四个边界点
if (homography.data == NULL) {
return;
}
std::vector<Point2f> 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<Point2f> 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();
});
这样就可以看到一个红框框跟着样本图后面跑了。
使用过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);
创建一个detector跟extractor,用这个detector来提取一个KeyPoint数组,extractor提取一个descriptor
vector<KeyPoint> keypoints; Mat descriptor;
Ptr<SIFT> detector = SIFT::create(); detector->detect(gray, keypoints); Ptr<SIFT> extractor = SIFT::create(); extractor->compute(gray, keypoints, descriptor);
这时候样本的特征就取好了。
在取到摄像头帧之后,做第二篇一样的处理,得到一个Mat,然后灰化
Mat image, gray;
image = [得到的mat];
cvtColor(image,gray,CV_BGR2GRAY);
同样,用SIFT算子提取这张灰度图的特征kp,d。
vector<KeyPoint> kp;
Mat d;
Ptr<SIFT> detector = SIFT::create();
detector->detect(gray, kp);
if (kp.size() <= 0) {
return;
}
Ptr<SIFT> extractor = SIFT::create();
extractor->compute(gray, kp, d);
为什么要判断kp.size()?因为你的摄像头可能会得到一张纯色毫无轮廓角点的图片,这样kp.size()就是0,这就没必要做对比了。
接下来就可以做对比,得到一个DMatch数组
vector<DMatch> matches;
BFMatcher matcher = BFMatcher();
matcher.match(descriptor, d, matches);
从DMatch数组里面照到最小和最大distance
double maxDist = 0.0;
double minDist = DBL_MAX;
for (int i=0;i<matches.size();i++) {
DMatch match = matches[i];
double dist = match.distance;
if (dist < minDist) {
minDist = dist;
}
if (dist > maxDist) {
maxDist = dist;
}
}
过滤distance过大的DMatch
double maxGoodMatchDist = THRESHOLD * minDist;
vector<DMatch> goodMatches;
for( int i = 0; i <descriptor.rows; i++ )
{
if( matches[i].distance < maxGoodMatchDist ){
goodMatches.push_back(matches[i]);
}
}
这里的THERSHOLD视算子而定,SIFT可以设成2。根据过滤好的DMatch数组再来过滤不吻合的keypoint,分别得到样本和当前取到的帧里面吻合的点
vector<cv::Point> modePoints;
vector<cv::Point> scenePoints;
for (int i=0;i<goodMatches.size();i++) {
DMatch goodMatche = goodMatches[i];
if (goodMatche.queryIdx < 0 || goodMatche.queryIdx >= 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);
}
得到一个homeography
Mat homography = findHomography(modePoints, scenePoints, CV_RANSAC);
找到四个边界点
if (homography.data == NULL) {
return;
}
std::vector<Point2f> 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<Point2f> 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();
});
这样就可以看到一个红框框跟着样本图后面跑了。
相关文章推荐
- opencv-ios开发笔记9 使用透视变换矫正扭曲的图片
- iOS开发之opencv学习笔记二:使用CascadeClassifier进行对特定物体的跟踪
- opencv-ios开发笔记9 使用透视变换矫正扭曲的图片
- OpenCV学习笔记——2维特征Feature2D,SURF
- 2011斯坦福大学iOS应用开发教程学习笔记(第五课)Protocols,手势识别
- IOS学习笔记(十)之UIImageView图片视图的基本概念和使用方法
- openCV学习笔记(5):使用sprintf函数实现在窗口连续显示同一文件夹下的图片
- IOS开发学习笔记(十七)——使用地理位置及地图(中篇)
- 2011斯坦福大学iOS应用开发教程学习笔记(第五课)Protocols,手势识别
- iOS开发UI篇—Quartz2D使用(图片剪切)
- 斯坦福大学iOS应用开发教程学习笔记(第五课)Protocols,手势识别
- ios学习笔记(一)Windows7上使用VMWare搭建iPhone开发环境
- 2011斯坦福大学iOS应用开发教程学习笔记(第五课)Protocols,手势识别
- 2011斯坦福大学iOS应用开发教程学习笔记(第五课)Protocols,手势识别
- IOS开发使用纯代码开发界面_学习笔记
- iOS开发的2D绘制--CoreGraphics的简单使用三(画文字和图片)
- 2011斯坦福大学iOS应用开发教程学习笔记(第五课)Protocols,手势识别
- IOS开发学习笔记(十六)——使用地理位置及地图(上篇)
- IOS开发学习笔记(十八)——使用地理位置及地图(下篇)
- iOS开发学习笔记:使用xcode里的单元测试,放在STAssert…里面的语句无法使用自动完成功能