一、案例來源
本例項目來源於羣裏面網友提問“在流水線上採集到的圖片,相互之間位移基本確定,需要進行進一步精細拼接”
具體而言,這是一塊大型服務器板子,會走點拍100張圖【特定設備】,每張圖有部分重合,算下來應該七百多寬度重合,圖像大小爲5000多。難點是重合的全是cpu socket pin,全部長一個樣子。
二、算法研究
如果是有定位點的圖片,我們當然會優先考慮採用“特徵定位”的方式,但是難點在於這裏重合的部分基本長的都一樣。這種情況下,我們經過討論,得出了採用“相位變換”的方法來解決“配準”問題。
算法參考:
Mat src1 = cv::imread("e:/template/pcb/1.bmp"); Mat src2 = cv::imread("e:/template/pcb/2.bmp"); Mat gray1; Mat gray2; Mat dst1; Mat dst2; cvtColor(src1, gray1, COLOR_BGR2GRAY); //轉換爲灰度圖像 gray1.convertTo(dst1, CV_32FC1); //轉換爲32位浮點型 cvtColor(src2, gray2, COLOR_BGR2GRAY); gray2.convertTo(dst2, CV_32FC1); Mat roi1; Mat roi2; roi1 = dst1(Rect(dst1.cols - 700, 0, 700, dst1.rows)); roi2 = dst2(Rect(0, 0, 700, dst2.rows)); //TODO 主動選取ROI 區域 //獲得相位差距 Point2d phase_shift; phase_shift = phaseCorrelate(roi1, roi2); cout << endl << "warp :" << endl << "\tX shift : " << phase_shift.x << "\tY shift : " << phase_shift.y << endl; //圖片對準 Mat result(cv::Size(src1.cols + src2.cols, src1.rows + 200), CV_8UC3, Scalar::all(0)); Mat leftHalf(cv::Size(src1.cols, src1.rows + 200), CV_8UC3, Scalar::all(0)); Mat rightHalf(cv::Size(src2.cols, src2.rows + 200), CV_8UC3, Scalar::all(0)); Mat copy1 = result(Rect(0, 100, src1.cols, src1.rows)); src1.copyTo(copy1); copy1 = leftHalf(Rect(0, 100, src1.cols, src1.rows)); src1.copyTo(copy1); Mat copy2 = result(Rect(src1.cols - 700- phase_shift.x, 100-phase_shift.y, src2.cols, src2.rows)); src2.copyTo(copy2); copy2 = rightHalf(Rect(0, 100 - phase_shift.y, src2.cols, src2.rows)); src2.copyTo(copy2); //繪製融合線 //cv::line(result, cv::Point(src1.cols - 700 - phase_shift.x, 0), cv::Point(src1.cols - 700 - phase_shift.x, result.cols), cv::Scalar(0, 0, 255)); //cv::line(result, cv::Point(src1.cols - 700 - phase_shift.x-50, 0), cv::Point(src1.cols - 700 - phase_shift.x-50, result.cols), cv::Scalar(0, 0, 255)); //cv::line(result, cv::Point(src1.cols - 700 - phase_shift.x+50, 0), cv::Point(src1.cols - 700 - phase_shift.x+50, result.cols), cv::Scalar(0, 0, 255)); //圖像融合 leftHalf.convertTo(leftHalf, CV_32FC3, 1.0 / 255); rightHalf.convertTo(rightHalf, CV_32FC3, 1.0 / 255); result.convertTo(result, CV_32FC3, 1.0 / 255); double dblend = 0.0; int istart = src1.cols - 700 - phase_shift.x ;//col的初始定位 for (int i = 0; i < 100; i++) { //使用col,必須保證row是一樣的 result.col(istart + i) = leftHalf.col(istart + i)*(1-dblend)+ rightHalf.col(i)*dblend; dblend = dblend + 0.01; } result.convertTo(result, CV_8UC3, 255); imwrite("e:/template/pcb/result.jpg", result); cv::waitKey(0);
在這種情況下,基本上能夠得到這樣的結果:
Mat result(cv::Size(src1.cols + src2.cols, src1.rows + 200), CV_8UC3, Scalar::all(0)); Mat leftHalf(cv::Size(src1.cols, src1.rows + 200), CV_8UC3, Scalar::all(0)); Mat rightHalf(cv::Size(src2.cols, src2.rows + 200), CV_8UC3, Scalar::all(0)); Mat copy1 = result(Rect(0, 100, src1.cols, src1.rows)); src1.copyTo(copy1); copy1 = leftHalf(Rect(0, 100, src1.cols, src1.rows)); src1.copyTo(copy1); Mat copy2 = result(Rect(src1.cols - 700- phase_shift.x, 100-phase_shift.y, src2.cols, src2.rows)); src2.copyTo(copy2); copy2 = rightHalf(Rect(0, 100 - phase_shift.y, src2.cols, src2.rows)); src2.copyTo(copy2);
從細節來看,已經對準,只是融合的問題。準確地找到融合線是成功融合的前提條件 。
//繪製融合線 cv::line(result, cv::Point(src1.cols - 700 - phase_shift.x, 0), cv::Point(src1.cols - 700 - phase_shift.x, result.cols), cv::Scalar(0, 0, 255));
基於lineblender算法進行融合,算法原理是非常簡單的,但是如果想正確的實現,需要清晰的思路。
//圖像融合 leftHalf.convertTo(leftHalf, CV_32FC3, 1.0 / 255); rightHalf.convertTo(rightHalf, CV_32FC3, 1.0 / 255); result.convertTo(result, CV_32FC3, 1.0 / 255); double dblend = 0.0; int istart = src1.cols - 700 - phase_shift.x ;//col的初始定位 for (int i = 0; i < 100; i++) { //使用col,必須保證row是一樣的 result.col(istart + i) = leftHalf.col(istart + i)*(1-dblend)+ rightHalf.col(i)*dblend; dblend = dblend + 0.01; }
能夠得到較好的效果。
三、擴展分析
1、在融合結果的中心區域,能夠獲得較好的結果:
這裏的融合,幾乎看不錯錯誤。但是在邊緣的部分,則錯誤比較明顯:
出現這個錯誤的原因,並不是原始圖片發生了偏轉,而是圖片在採集的時候發生了“鏡像失真”。在圖像處理之前,首先要進行更爲細緻的標定,這應該也是工業化必須的步驟。
2、多圖片批量拼接。
比如之前遇到過的顯微鏡問題:
雖然其原理可以簡介本例算法,但是在涉及到“橫向”“豎向”拼接的時候,算法的複雜度將指數級上升,寫出更有彈性的代碼、處理大像素的圖片,都是非常考驗能力的。
四、項目小結
總的來說,這裏提出了一種關於行業模式下拼接的新方法,是有效的,值得在實際的項目中繼續研究。而如果能夠指導獲得高質量的原始圖片,也是非常有價值的。
相關參考:
1、相位變換原理:https://blog.csdn.net/zhaocj/article/details/50157801
2、LogPolarFFTTemplateMatcher https://github.com/Smorodov/LogPolarFFTTemplateMatcher