目录
一,问题背景
在扫描一个区域图像时,由于各种原因,需要把一个区域划分成若干个小区域,各自单独扫描成图像。
由于扫描设备对小区域的定位存在误差,不能直接把小图拼成大图,需要配准拼接算法进行处理。
上游的把区域划分成小区域的算法,叫扫描规划算法。
配准拼接算法和扫描规划算法是息息相关,互相配合。
扫描规划算法会给出所有小图的理论位置(即所有小区域的位置),配准拼接算法会基于图像算出所有图片的实际位置,理论位置和实际位置的差值就是扫描设备的定位误差。
配准拼接算法的原理就是计算所有相邻两张小图的相对位置关系(即配准算法),再基于这个数据计算所有小图的位置。
因为配准算法的需要,扫描规划算法会让所有相邻两张小图之间都有重合区域。
二,简单场景——绝对配准
如果配准算法是绝对准确可靠的,那么根据配准算法的结果,用搜索算法即可得到完整大图。
1,基本代码
输入所有图片信息,计算所有配准结果,再拼接大图。
struct ImageInfo
{
//图片内容和坐标等数据
};
struct Shift
{
int x, y;//两张图片的配准结果
};
struct Pos
{
int x, y;//小图坐标
};
class RegistrationStitch
{
public:
RegistrationStitch(int row, int col, vector<ImageInfo>images) :row{ row }, col{ col }, images{ images }{}
ImageInfo GetStitchImage()
{
Registration();
vector<Pos> allPos = GetPos(shiftLR, shiftUD);
return GetStitchImage(images, allPos);
}
private:
int GetId(int r, int c) //所有小图编号0到row*col-1
{
return r * col + c;
}
void Registration()//配准算法
{
for (int r = 0; r < row; r++) {
for (int c = 0; c < col; c++) {
if (r)shiftUD[GetId(r, c)] = Registration(images[GetId(r - 1, c)], images[GetId(r, c)]);
if (c)shiftLR[GetId(r, c)] = Registration(images[GetId(r, c - 1)], images[GetId(r, c)]);
}
}
}
Shift Registration(ImageInfo image1, ImageInfo image2)//两张小图配准
{
return Shift{};
}
static vector<Pos> GetPos(map<int, Shift>& shiftLR, map<int, Shift>& shiftUD)
{
return vector<Pos>{};
}
static ImageInfo GetStitchImage(vector<ImageInfo>& images, vector<Pos>& allPos)
{
ImageInfo ans;
for (int i = 0; i < images.size(); i++) {
// ans = CopyImage(ans,images[i],allPos[i]);
}
return ans;
}
private:
int row, col;
vector<ImageInfo>images;
map<int, Shift> shiftLR;
map<int, Shift> shiftUD;
}
这里的GetPos函数是static的,即只需要入参不需要图片等信息。其他私有静态成员函数同理。
GetPos里面省略的是一个BFS或者DFS算法,也可以用带权并查集。
2,减少配准
上面的伪代码进行了所有配准,其实只需要一部分。
以3*3为例,不需要做12次配准,只需要做8次配准。
不是任意8个配准就行,其实就是从一个9个点12条边的图中选出任意一颗生成树。
既然配准算法是绝对准确可靠的,那么任选一颗生成树,结果都是一样的。
其实可以选择一个固定方式的生成树,如上图(所有的左右配准加第一列的上下配准)
3,实时配准
如果想加快拼接出图的速度,可以一边扫描小图一边完成配准。
struct ImageInfo
{
//图片内容和坐标等数据
};
struct Shift
{
int x, y;//两张图片的配准结果
};
struct Pos
{
int x, y;//小图坐标
};
class RegistrationStitch
{
public:
RegistrationStitch(int row, int col) :row{ row }, col{ col }{}
void Push(ImageInfo image)
{
images.push_back(image);
Registration(); //每次传入一张小图,调用一次配准
}
ImageInfo GetStitchImage()
{
vector<Pos> allPos = GetPos(shiftLR, shiftUD);
return GetStitchImage(images, allPos);
}
private:
int GetId(int r, int c) //所有小图编号0到row*col-1
{
return r * col + c;
}
void Registration()//配准算法
{
vector<pair<int, int>> pairs;
// pairs = ... 计算当前传入小图和哪些小图需要配准
for (auto p : pairs) {
auto shift = Registration(images[p.first], images[p.second]);
// shiftLR or shiftUD 存入结果shift
}
}
Shift Registration(ImageInfo image1, ImageInfo image2)//两张小图配准
{
return Shift{};
}
static vector<Pos> GetPos(map<int, Shift>& shiftLR, map<int, Shift>& shiftUD)
{
return vector<Pos>{};
}
static ImageInfo GetStitchImage(vector<ImageInfo>& images, vector<Pos>& allPos)
{
ImageInfo ans;
for (int i = 0; i < images.size(); i++) {
// ans = CopyImage(ans,images[i],allPos[i]);
}
return ans;
}
private:
int row, col;
vector<ImageInfo>images;
map<int, Shift> shiftLR;
map<int, Shift> shiftUD;
}
三,实际场景——非绝对配准
正常情况下配准都不是绝对准确可靠的。
一般来说2张图片的配准结果会有2类问题,多解、精度误差。
限制配准算法的搜索范围,能一定程度上减少多解问题。而精度误差是不可能完全消除了,这就带来一个问题,不同的生成树对应的拼接结果是不一样的。
所以在完成所有配准(而不是只有生成树)后,如何计算所有小图坐标?
有两类方法,第一类是最小生成树,第二类是最优化算法。
1,最小生成树
每个配准结果都有一个置信度,以此作为权值,选择最高置信度的生成树。
2,最优化
优化目标是一个row*col*2元函数,即每张图的x坐标和y坐标,
其中S是所有的相邻图像对,cx和cy是前序流程算出的偏移量,z是对应的置信度。
求f的最小值。
求f的最小值,可以用无需初始解的加权最小二乘法,也可以用需要初始解的梯度类算法。
初始解可以用固定默认值,也可以用最小生成树的结果。