4、关键点检测器和描述符
OpenCV和CUDA提供一种实现FAST算法的有效方法,使用FAST算法检测关键点的程序如下所示:
#include <iostream> #include "opencv2/opencv.hpp" using namespace cv; using namespace std; int main() { Mat h_image = imread( "images/drawing.JPG", 0 ); //Detect the keypoints using FAST Detector cv::Ptr<cv::cuda::FastFeatureDetector> detector = cv::cuda::FastFeatureDetector::create(100,true,2); std::vector<cv::KeyPoint> keypoints; cv::cuda::GpuMat d_image; d_image.upload(h_image); detector->detect(d_image, keypoints); cv::drawKeypoints(h_image,keypoints,h_image); //Show detected keypoints imshow("Final Result", h_image ); waitKey(0); return 0; }
OpenCV和CUDA提供了一个FastFeatureDetector类来实现FAST算法,使用类的create方法创建此类的对象,需要三个参数:第一个参数是用于FAST算法的强度阈值;第二个参数指定是否使用非最大抑制,它是一个布尔值,可以指定为true或false;第三个参数表示计算邻域所使用的FAST方法,有以下三种可用的方法:
cv2.FAST_FEATURE_DETECTOR_TYPE_5_8、cv2.FAST_FEATURE_DETECTOR_TYPE_7_12以及cv2.FAST_FEATURE_DETECTOR_TYPE_9_16,可以指定为标志0,1或2。
创建检测对象关键点的函数需要一个输入图像和存储关键点的矢量作为参数,然后用drawKeypoints函数来绘制计算出的关键点原始图像,这需要源图像、关键点矢量和目标图像作为参数。
可以改变强度阈值以检测不同数量的关键点,如果阈值很低,那么更多的关键点将通过分段测试,并将被归类为关键点。随着阈值增加,检测到的关键点的数量将逐渐减少。同样的方式,如果非最大抑制为false,就会在单个角点上检测出一个以上的关键点。代码输出如图所示:
OpenCV和CUDA提供了一个简单的API来实现ORB算法。用于实现ORB算法的代码如下:
#include <iostream> #include "opencv2/opencv.hpp" using namespace cv; using namespace std; int main() { Mat h_image = imread( "images/drawing.JPG", 0 ); cv::Ptr<cv::cuda::ORB> detector = cv::cuda::ORB::create(); std::vector<cv::KeyPoint> keypoints; cv::cuda::GpuMat d_image; d_image.upload(h_image); detector->detect(d_image, keypoints); cv::drawKeypoints(h_image,keypoints,h_image); imshow("Final Result", h_image ); waitKey(0); return 0; }
使用create函数创建ORB类对象,所有参数都是可选的,这里使用默认值。detect函数对生成的对象进行检测以找出图像中的关键点,需要输入图像和关键点的矢量,在那里输出将作为参数存储。检测到的关键点用drawKeypoints函数绘制点图像。前面代码的输出如图所示:
OpenCV和CUDA提供了一个API来计算SURF关键点和描述符,我们还可以看到这些如何用于检测查询图像中的对象。SURF特征检测和匹配的代码如下:
#include <stdio.h> #include <iostream> #include "opencv2/opencv.hpp" #include "opencv2/core.hpp" #include "opencv2/imgproc.hpp" #include "opencv2/highgui.hpp" #include "opencv2/calib3d.hpp" #include "opencv2/calib3d/calib3d.hpp" #include "opencv2/features2d.hpp" #include "opencv2/xfeatures2d.hpp" #include "opencv2/xfeatures2d/nonfree.hpp" #include "opencv2/core/cuda.hpp" #include "opencv2/cudaarithm.hpp" #include "opencv2/cudafeatures2d.hpp" #include "opencv2/xfeatures2d/cuda.hpp" using namespace cv; using namespace cv::xfeatures2d; using namespace std; int main( int argc, char** argv ) { Mat h_object_image = imread( "images/object1.jpg", 0 ); Mat h_scene_image = imread( "images/scene1.jpg", 0 ); cuda::GpuMat d_object_image; cuda::GpuMat d_scene_image; cuda::GpuMat d_keypoints_scene, d_keypoints_object; vector< KeyPoint > h_keypoints_scene, h_keypoints_object; cuda::GpuMat d_descriptors_scene, d_descriptors_object; d_object_image.upload(h_object_image); d_scene_image.upload(h_scene_image); cuda::SURF_CUDA surf(100); surf( d_object_image, cuda::GpuMat(), d_keypoints_object, d_descriptors_object ); surf( d_scene_image, cuda::GpuMat(), d_keypoints_scene, d_descriptors_scene ); Ptr< cuda::DescriptorMatcher > matcher = cuda::DescriptorMatcher::createBFMatcher(); vector< vector< DMatch> > d_matches; matcher->knnMatch(d_descriptors_object, d_descriptors_scene, d_matches, 2); surf.downloadKeypoints(d_keypoints_scene, h_keypoints_scene); surf.downloadKeypoints(d_keypoints_object, h_keypoints_object); std::vector< DMatch > good_matches; for (int k = 0; k < std::min(h_keypoints_object.size()-1, d_matches.size()); k++) { if ( (d_matches[k][0].distance < 0.6*(d_matches[k][1].distance)) && ((int)d_matches[k].size() <= 2 && (int)d_matches[k].size()>0) ) { good_matches.push_back(d_matches[k][0]); } } std::cout << "size:" <<good_matches.size(); Mat h_image_result; drawMatches( h_object_image, h_keypoints_object, h_scene_image, h_keypoints_scene, good_matches, h_image_result, Scalar::all(-1), Scalar::all(-1), vector<char>(), DrawMatchesFlags::DEFAULT ); std::vector<Point2f> object; std::vector<Point2f> scene; for (int i = 0; i < good_matches.size(); i++) { object.push_back(h_keypoints_object[good_matches[i].queryIdx].pt); scene.push_back(h_keypoints_scene[good_matches[i].trainIdx].pt); } Mat Homo = findHomography(object, scene, RANSAC); std::vector<Point2f> corners(4); std::vector<Point2f> scene_corners(4); corners[0] = Point(0, 0); corners[1] = Point(h_object_image.cols, 0); corners[2] = Point(h_object_image.cols, h_object_image.rows); corners[3] = Point(0, h_object_image.rows); perspectiveTransform(corners, scene_corners, Homo); line(h_image_result, scene_corners[0] + Point2f(h_object_image.cols, 0),scene_corners[1] + Point2f(h_object_image.cols, 0),Scalar(255, 0, 0), 4); line(h_image_result, scene_corners[1] + Point2f(h_object_image.cols, 0),scene_corners[2] + Point2f(h_object_image.cols, 0),Scalar(255, 0, 0), 4); line(h_image_result, scene_corners[2] + Point2f(h_object_image.cols, 0),scene_corners[3] + Point2f(h_object_image.cols, 0),Scalar(255, 0, 0), 4); line(h_image_result, scene_corners[3] + Point2f(h_object_image.cols, 0),scene_corners[0] + Point2f(h_object_image.cols, 0),Scalar(255, 0, 0), 4); imshow("Good Matches & Object detection", h_image_result); waitKey(0); return 0; }
5、使用背景减法进行对象跟踪
背景减法是在一系列视频帧中将前景对象从背景中分离出来的过程。它广泛应用于对象检测和跟踪应用中去除背景部分。背景减法分四步进行:
·图像预处理
·背景建模
·检测前景
·数据验证
图像预处理通常用于去除图像中存在的各种噪声。第二步是对背景进行建模,以便将其与前景分离。在某些应用中,视频的第一帧作为背景不更新,后面每帧和第一帧之间的绝对差被用来分离前景和背景。
在其他技术中,通过对算法所看到的所有帧的平均值或中间值对背景进行建模,并将该背景与前景分离。与第一种方法相比,它对光照变化的鲁棒性更高,并且会产生更多动态背景。其他更具统计密集度的模型,如高斯模型和使用帧的历史的支持向量模型,也可以用于背景建模。
第三步是利用当前帧和背景之间的绝对差,将前景与模型背景分离。将这个绝对差与设置的阈值进行比较:如果大于阈值,则对象被认为是移动的;如果小于阈值,那么对象被认为是静止的。
5.1、高斯混合法
高斯混合法(MoG)是一种广泛使用的基于高斯混合的背景减法,用于分离前景和背景。背景从帧序列中不断更新,混合K高斯分布用于将像素分类为前景或背景,同时对帧的时间序列进行加权,以改善背景建模。连续变化的强度被归类为前景强度,静态强度被归类为背景强度。
OpenCV和CUDA提供了一个简单的API来实现背景减法的MoG。代码如下:
#include <iostream> #include <string> #include "opencv2/opencv.hpp" using namespace std; using namespace cv; using namespace cv::cuda; int main() { VideoCapture cap("D:/images/abc.avi"); int type = static_cast<int>(cap.get(CAP_PROP_FOURCC)); Size S = Size((int)cap.get(CAP_PROP_FRAME_WIDTH), (int)cap.get(CAP_PROP_FRAME_HEIGHT)); int fps = cap.get(CAP_PROP_FPS); VideoWriter write; write.open("D:/images/h_fgmask_mog.mp4", write.fourcc('M', 'P', '4', '2'), 20, S, true); VideoWriter write2; write.open("D:/images/h_img_mog.mp4", write.fourcc('M', 'P', '4', '2'), 20, S, true); VideoWriter write3; write.open("D:/images/h_background_mog.mp4", write.fourcc('M', 'P', '4', '2'), 20, S, true); if (!cap.isOpened()) { cerr << "can not open camera or video file" << endl; return -1; } Mat frame; cap.read(frame); GpuMat d_frame; d_frame.upload(frame); Ptr<BackgroundSubtractor> mog = cuda::createBackgroundSubtractorMOG(); GpuMat d_fgmask, d_fgimage, d_bgimage; Mat h_fgmask, h_fgimage, h_bgimage; mog->apply(d_frame, d_fgmask, 0.01); namedWindow("cap"); while (1) { cap.read(frame); if (frame.empty()) break; d_frame.upload(frame); int64 start = cv::getTickCount(); mog->apply(d_frame, d_fgmask, 0.01); mog->getBackgroundImage(d_bgimage); double fps = cv::getTickFrequency() / (cv::getTickCount() - start); std::cout << "FPS : " << fps << std::endl; d_fgimage.create(d_frame.size(), d_frame.type()); d_fgimage.setTo(Scalar::all(0)); d_frame.copyTo(d_fgimage, d_fgmask); d_fgmask.download(h_fgmask); d_fgimage.download(h_fgimage); d_bgimage.download(h_bgimage); imshow("image", frame); putText(h_fgmask, format("FPS: %.2f", fps), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255), 2, 8); imshow("foreground mask", h_fgmask); write.write(h_fgmask); putText(h_fgimage, format("FPS: %.2f", fps), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255), 2, 8); imshow("foreground image", h_fgimage); write2.write(h_fgimage); putText(h_bgimage, format("FPS: %.2f", fps), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255), 2, 8); imshow("mean background image", h_bgimage); write3.write(h_bgimage); if (waitKey(1) == 'q') break; } return 0; }
createBackgroundSubtractorMOG类用于创建实现MoG的对象,它可以在创建对象时提供一些可选参数。这些参数包括history、nmixtures、backgroundRatio和noiseSigma。history参数表示用于为背景建模的前一帧的数量,默认值是200;nmixture参数指定用于分离像素的高斯混合数,默认值是5。你可以根据应用程序使用这些值。
所创建对象的apply方法用于从第一帧创建前景掩码,需要一个输入图像和一个图像数组来存储作为输入的前景掩码和学习速率。在while循环中的每一帧之后,这个前景掩码和背景图像都会不断更新。getBackgroundImage函数用于获取当前背景模型。
前景掩码用于创建前景图像,该图像指示当前正在移动的对象。它的基本逻辑是在原始帧和前景掩码之间操作。在每一帧之后,前景掩码、前景图像和建模的背景将被下载到主机内存中,以便在屏幕上显示。
MoG模型应用于PETS 2009数据集的视频,该数据集广泛用于行人检测,它有一个静态的背景,人们在视频中四处走动。结果如下所示:
5.2、GMG背景减法
GMG算法的名称源自该算法发明人的姓名首字母,这个算法结合了背景估计与贝叶斯图像分割,使用贝叶斯推断将背景与前景分离,还使用帧的历史来建模背景。它再次基于帧的时间序列进行加权。新的观测比旧的观测的权重更高。
OpenCV和CUDA提供了类似于MoG的API用于实现GMG算法。实现用于背景减法的GMG算法的代码如下:
#include <iostream> #include <string> #include "opencv2/opencv.hpp" #include "opencv2/core.hpp" #include "opencv2/core/utility.hpp" #include "opencv2/cudabgsegm.hpp" #include "opencv2/cudalegacy.hpp" #include "opencv2/video.hpp" #include "opencv2/highgui.hpp" using namespace std; using namespace cv; using namespace cv::cuda; int main( ) { VideoCapture cap("D:/images/abc.avi"); int type = static_cast<int>(cap.get(CAP_PROP_FOURCC)); Size S = Size((int)cap.get(CAP_PROP_FRAME_WIDTH), (int)cap.get(CAP_PROP_FRAME_HEIGHT)); int fps = cap.get(CAP_PROP_FPS); VideoWriter write; write.open("D:/images/h_fgmask.mp4", write.fourcc('M', 'P', '4', '2'), 20, S, true); VideoWriter write2; write2.open("D:/images/h_fgimage.mp4", write.fourcc('M', 'P', '4', '2'), 20, S, true); if (!cap.isOpened()) { cerr << "can not open video file" << endl; return -1; } Mat frame; cap.read(frame); GpuMat d_frame; d_frame.upload(frame); Ptr<BackgroundSubtractor> gmg = cuda::createBackgroundSubtractorGMG(40); GpuMat d_fgmask, d_fgimage, d_bgimage; Mat h_fgmask, h_fgimage, h_bgimage; gmg->apply(d_frame, d_fgmask); namedWindow("cap"); while (cap.read(frame)) { cap.read(frame); if (frame.empty()) break; d_frame.upload(frame); int64 start = cv::getTickCount(); gmg->apply(d_frame, d_fgmask, 0.01); double fps = cv::getTickFrequency() / (cv::getTickCount() - start); std::cout << "FPS : " << fps << std::endl; d_fgimage.create(d_frame.size(), d_frame.type()); d_fgimage.setTo(Scalar::all(0)); d_frame.copyTo(d_fgimage, d_fgmask); d_fgmask.download(h_fgmask); d_fgimage.download(h_fgimage); imshow("image", frame); putText(h_fgmask, format("FPS: %.2f", fps), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255), 2, 8); imshow("foreground mask", h_fgmask); write.write(h_fgmask); putText(h_fgimage, format("FPS: %.2f", fps), Point(50, 50), FONT_HERSHEY_SIMPLEX, 1.0, Scalar(0, 0, 255), 2, 8); imshow("foreground image", h_fgimage); write2.write(h_fgimage); if (waitKey(30) == 'q') break; } return 0; }
createBackgroundSubtractorGMG类用于实现GMG创建的对象。它可以在创建对象时提供两个参数:第一个参数用于对背景建模的前一帧的数量,上面代码中取40;第二个参数是决策阈值,用于将像素分类为前景,其默认值为0.8。
所创建对象的apply方法用于第一帧以创建前景掩码。通过使用帧的历史在while循环中不断更新前景掩码和前景图像,前景掩码用于与MoG类似的方式创建前景图像。