背景
最近从不一样网站下载了很是多的动漫壁纸,其中有一些内容相同,可是大小、背景颜色、色调、主人公的位置不一样(例子以下)。正由于如此,基础的均方偏差、直方图检测等方法很难识别出这些类似的图片。python
思路
OpenCV中有不少用来对特征点进行检测和计算的函数,这些函数可以利用像素点及其周围的灰度检测其是不是图像中的特征点,并计算出它的信息,好比ORB、SIFT、SURF、AKANA。同时OpenCV还有一些利用特征点的信息对特征点进行匹配的算法,好比BF、FLANN。咱们能够先把参与匹配的每一个图片的特征点和信息计算出来,而后对图片两两进行特征点匹配,若是两幅图片匹配上的特征点数量超过一个定值,即认为这两个图片类似。这种方法由于是直接对图像的特征进行考虑,所以对于大小、色调、主人公的位置不一样的类似图片也能很好的匹配。算法
对于特征点检测,这些算法分为两类,一类输出的特征点信息是二进制串,包括ORB、AKANA等,一类输出的特征点信息是浮点数,包括SIFT、SURF,可是SIFT和SURF这两个算法是有专利的,商用要付钱,因此OpenCV把它们放进了Contrib扩展包里面,若是你用的是python版的OpenCV,必须下载3.4.2.16版本的opencv-contrib-python才能用OpenCV里的SIFT和SURF函数。我用的是C++版本的OpenCV,你须要下载OpenCV的源码和OpenCV-contrib扩展包而后本身编译,很麻烦,因此我选择的是ORB。对于特征点匹配,FLANN不论效率仍是效果都比BF好不少(固然也有多是我BF没用对),可是网上不少教程(包括OpenCV本身的文档)都是ORB配BF,SIFT配FLANN,StackOverflow也有人问ORB怎么搭配FLANN使用,有的回答直接说特征点信息是整数的算法不能搭配FLANN,但幸亏这个问题下的另外一我的给出了FLANN搭配ORB时的参数,(https://stackoverflow.com/questions/43830849/opencv-use-flann-with-orb-descriptors-to-match-features)这也说明了这个问题仍是被不少人忽视的,毕竟当今世界是深度学习的天下,不多人去关注这些传统算法了。函数
这个程序效率比较低,须要进行一些优化。首先咱们用于求特征点和匹配的图片应该是原图的灰度图通过缩小后的版本,同时注意这个操做不要用cv::resize完成,否则会慢不少,直接在imread的时候指定第二个参数为cv::IMREAD_REDUCE_COLOR_4能够在读入图片的同时缩小。固然,瓶颈仍是在那个两两匹配的二重循环里,为了减小FLANN的操做,我先预处理出图像各个通道的平均值,用这个值来大体表示这个图像的色调,在二重循环中,若是两个图片的平均值相差太大(我设置的是60),就认为它们不类似,不进行特征点匹配,固然这样会致使多出很多漏网之鱼,不过实践证实这样作大部分类似的图片仍是不会被筛掉的,并且速度也提升了不少。学习
代码
1 #include <io.h> 2 #include <ctime> 3 #include <vector> 4 #include <opencv2/opencv.hpp> 5 6 bool judge(cv::Scalar &a, cv::Scalar &b, int num) { 7 return std::abs(a[0] - b[0]) < num && std::abs(a[1] - b[1]) < num && std::abs(a[2] - b[2]) < num; 8 } 9 10 int main() { 11 12 clock_t start = clock(); //计时开始 13 std::vector <cv::String> filelist; 14 typedef std::tuple <cv::String, cv::String, int> data; 15 std::vector <std::vector <cv::KeyPoint>> kplist; 16 std::vector <cv::Mat> deslist; 17 std::vector <cv::Scalar> averagebgr; 18 std::vector <data> same; 19 20 _finddata_t fd; 21 intptr_t pf = _findfirst("D:/image/*.??g", &fd); filelist.push_back(cv::String("D:/宋奕欣/") + fd.name); 22 while (!_findnext(pf, &fd)) filelist.push_back(cv::String("D:/image/") + fd.name); 23 _findclose(pf); //列举出图片,这里用的是io.h里的_findfirst和_findnext,通配符.??g筛选出.jpg和.png的文件 24 25 cv::Ptr <cv::ORB> orb = cv::ORB::create(); 26 for (auto i : filelist) { 27 cv::Mat imgo = cv::imread(i, cv::IMREAD_REDUCED_COLOR_4); 28 averagebgr.push_back(cv::mean(imgo)); //求各通道平均值 29 cv::Mat img; cv::cvtColor(imgo, img, cv::COLOR_BGR2GRAY); 30 std::vector<cv::KeyPoint> kp; cv::Mat des; //kp是特征点,des是特征点的信息 31 orb->detectAndCompute(img, cv::Mat(), kp, des); 32 kplist.push_back(kp); 33 deslist.push_back(des); 34 } 35 36 std::cout << "Successfully found keypoints." << std::endl; 37 38 39 cv::FlannBasedMatcher flann(cv::makePtr<cv::flann::LshIndexParams>(12, 20, 2)); //这个cv::makePtr<cv::flann::LshIndexParams>(12, 20, 2)就是使FLANN能搭配ORB的参数,默认构造函数指定的是随机KD树算法,只能用于SIFT和SURF 40 for (int i = 0; i < filelist.size(); i++) 41 for (int j = i + 1; j < filelist.size(); j++) { 42 43 if (!judge(averagebgr[i], averagebgr[j], 60)) continue; 44 45 std::vector<cv::KeyPoint> kpl, kps; cv::Mat desl, dess; 46 kpl = kplist[i]; desl = deslist[i]; 47 kps = kplist[j]; dess = deslist[j]; 48 49 std::vector <std::vector <cv::DMatch> > matches; flann.knnMatch(dess, desl, matches, 2); 50 std::vector <cv::DMatch> good; 51 for (auto k : matches) { 52 if (k.size() > 1 && k[0].distance < 0.5 * k[1].distance) good.push_back(k[0]); //knnMatch的k=2时,每一个Dmatch会返回distance最小的两组匹配,当最小的这两组的distance相差足够大时,较小的那一组才多是合法匹配 53 } 54 55 if (good.size() > 10) same.push_back(std::make_tuple(filelist[i], filelist[j], good.size())); 56 57 // cv::Mat img; cv::drawMatches(imgs, kps, imgl, kpl, good, img, cv::Scalar(0, 255, 0)); 58 // cv::imshow("img", img); cv::waitKey(); 59 } 60 61 std::sort(same.begin(), same.end(), [](data x, data y) { 62 return std::get<2>(x) > std::get<2>(y); 63 }); //把匹配的图片按匹配的特征点数排序 64 for (data i : same) { 65 std::cout << std::get<0>(i) << ' ' << std::get<1>(i) << ' ' << std::get<2>(i) << std::endl; 66 } 67 68 std::cout << (double)(clock() - start) / CLOCKS_PER_SEC << std::endl; 69 system("pause"); 70 return 0; 71 }