首页 > 编程语言 >高分辨率拼接案例分析【基础算法】

高分辨率拼接案例分析【基础算法】

时间:2023-12-15 10:01:51浏览次数:41  
标签:rows Mat cols src2 算法 src1 拼接 cv 高分辨率

一、案例来源

本例项目来源于群里面网友提问“在流水线上采集到的图片,相互之间位移基本确定,需要进行进一步精细拼接”

935c88e74b7ec78a3e93d9badc72330b

希望得到的结果。a6fb7e7c-d6f8-4e51-9949-1c7d0bd16274

具体而言,这是一块大型服务器板子,会走点拍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);

 

 

在这种情况下,基本上能够得到这样的结果:

77026629-50b2-421f-bfaa-6b11131b5cdaf9a786b6-7ad6-4664-8e7b-8f8857dff4b5

 

    

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));

  

e18055d7-d30e-4e41-ab51-8542cd741c9e

基于lineblender算法进行融合,算法原理是非常简单的,但是如果想正确的实现,需要清晰的思路。

2449c418-de3d-4710-b9c6-47410ffda285

 

//图像融合
    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、在融合结果的中心区域,能够获得较好的结果:

871c8c67-da18-4246-8cce-6bf771c2fd7f

这里的融合,几乎看不错错误。但是在边缘的部分,则错误比较明显:

464f8b6d-b341-494a-a6cd-8625179edf08

出现这个错误的原因,并不是原始图片发生了偏转,而是图片在采集的时候发生了“镜像失真”。在图像处理之前,首先要进行更为细致的标定,这应该也是工业化必须的步骤。

2、多图片批量拼接。

比如之前遇到过的显微镜问题:

f1124a47-6b72-469d-b2a7-f5a0760e49e8

虽然其原理可以简介本例算法,但是在涉及到“横向”“竖向”拼接的时候,算法的复杂度将指数级上升,写出更有弹性的代码、处理大像素的图片,都是非常考验能力的。

四、项目小结

总的来说,这里提出了一种关于行业模式下拼接的新方法,是有效的,值得在实际的项目中继续研究。而如果能够指导获得高质量的原始图片,也是非常有价值的。

相关参考:

1、相位变换原理:https://blog.csdn.net/zhaocj/article/details/50157801

2、LogPolarFFTTemplateMatcher https://github.com/Smorodov/LogPolarFFTTemplateMatcher

标签:rows,Mat,cols,src2,算法,src1,拼接,cv,高分辨率
From: https://www.cnblogs.com/jsxyhelu/p/17902722.html

相关文章

  • 算法战斗第三天C++2
    A.DominopilingYouaregivenarectangularboardofM × Nsquares.Alsoyouaregivenanunlimitednumberofstandarddominopiecesof2 × 1squares.Youareallowedtorotatethepieces.Youareaskedtoplaceasmanydominoesaspossibleonthe......
  • 算法学习笔记二一冒泡排序
    目录什么是冒泡排序算法原理代码示例什么是冒泡排序​对给定数组进行遍历,每次比较相邻两个元素大小,若大的数值在前面则交换两数位置(升序),每完成一趟遍历数组中最大的元素都会上升到数组的末尾,这也是冒泡一词的由来。算法原理(升序)列表每相邻的数,如果前面比后面大,则交换这两个数......
  • 基于小波神经网络的网络流量预测算法matlab仿真
    1.算法运行效果图预览   2.算法运行软件版本matlab2022A 3.算法理论概述       网络流量能直接反映网络性能的好坏,网络流量的建模与预测对于大规模网络的规划设计、网络资源管理以及用户行为的调节等方面都具有积极意义。本课题首先介绍了网络流量的特征......
  • 基于FPGA的图像形态学腐蚀算法实现,包括tb测试文件和MATLAB辅助验证
    1.算法运行效果图预览 将FPGA的仿真结果导入到MATLAB,结果如下所示:   2.算法运行软件版本vivado2019.2 matlab2022a 3.算法理论概述      基于FPGA的图像形态学腐蚀算法实现主要依赖于图像处理的基本原理和数学形态学的基础知识。在图像处理中,形态学操......
  • 常见排序算法
    排序常见的简单排序算法I.选择排序选择排序思路:选择出数组中的最小元素,将它与数组的第一个元素交换位置。再从剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。publicvoidsort(int[]arr){intN=arr.length;......
  • 代码随想录算法训练营第二天| LeetCode977.有序数组的平方、209.长度最小的子数组、59
    LeetCode977.有序数组的平方●今日学习的文章链接和视频链接代码随想录(programmercarl.com) 题目链接977.有序数组的平方-力扣(LeetCode) ●自己看到题目的第一想法昨天正好做了这道题目,总体来说就是用双指针法,要么从绝对值最小的数开始排序,要么从绝对值最大的数开......
  • 算法Day2双指针法排序,滑动窗口,螺旋矩阵
    Day2双指针法排序,滑动窗口,螺旋矩阵ByHQWQF2023/12/14笔记977.有序数组的平方https://leetcode.cn/problems/squares-of-a-sorted-array/返回一个非递减顺序排序的整数数组每个元素的平方后组成的新数组,新数组也按非递减顺序排序。解法:双指针法由于给定数组本身是有序的,......
  • 算法战斗第三天C++1
    A.Bit++TheclassicprogramminglanguageofBitlandisBit++.Thislanguageissopeculiarandcomplicated.Thelanguageisthatpeculiarasithasexactlyonevariable,calledx.Also,therearetwooperations:Operation++increasesthevalueofv......
  • 机器学习中的算法——支持向量机(SVM)
    1.SVM的核心要素支持向量机是一种二分类模型,他基本模型是定义在特征空间上的间隔最大的线性分类器。通俗的说很类似于上次讲的那个回归的分类,其实从平面上看也是找一条直线来分割,分割的两边就是分类的结果,只不过这次的分类是找到一条线使得它能够对两旁的点距离最远。也就是......
  • 代码随想录算法训练营第二天|977.有序数组的平方、209.长度最小的子数组、59.螺旋矩阵
    LeetCode977有序数组的平方题目链接:977.有序数组的平方思路:双指针,由两侧向中间逼近 LeetCode 209.长度最小的子数组题目链接:209.长度最小的子数组思路:滑动窗口,关键点滑动窗口起始点和终止点位置关系的确定 LeetCode 59.螺旋矩阵题目链接:59.螺旋矩阵关键点:循环处理......