使用OpenCV對圖片進行特征點檢測和匹配(C++)


背景

最近從不同網站下載了非常多的動漫壁紙,其中有一些內容相同,但是大小、背景顏色、色調、主人公的位置不同(例子如下)。正因為如此,基礎的均方誤差、直方圖檢測等方法很難識別出這些相似的圖片。

思路

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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM