1. 存在的问题
之前最佳缝合线查找存在一定的问题:查找不到最小能量和的缝合线路径。举一个简单的例子如图1.1所示:
图1.1 |
如果从上至下求和选择最小能量函数,则选择的路径为蓝色,但是蓝色路径不是最小能量和路径。因为从上至下只是考虑了下一步对于上一步的能量和最小,但是没有考虑到全局的能量和最小。上图中的最小能量和路径应该为红色。因此需要有一个新的思路求取最佳缝合线。
2. 新的思路
(也是OpenCV中缝合线查找思想):我们反过来对最小能量做选择,不在从多个像素中选择一个作为缝合线的延伸方向,而是从多个缝合线中选择一条最优的来连接当前点。思路如图2.1所示。
图2.1 |
如果存在3x3的能量矩阵,把第一行能量值作为三条最小能量和路径,那么能量化去向可以表示为如图2.2所示。
图2.2 |
算法步骤:
(1) 初始化。第一行作为第一步的最小能量和路径;
(2) 最小能量和迭代。依次遍历每一行,得到最小能量和路径来源的位置:左上、顶上、右上。在遍历的时候需要记录抵达每一行每一列的最小能量和向量,以及抵达每行每列路径来源的位置。假设左上(1)、顶上(2)、右上(3),供回溯时使用。
(3) 缝合线回溯。最后通过记录来源的位置矩阵从最后一行回溯缝合线。在X方向上,如果是1则减1,如果是3则加1,如果是2则不变,Y方向上减1。
3. 实验结果
图3.1 重叠区域 |
图3.2 最佳缝合线与不同终点缝合线 |
图3.3 缝合线诱导融合 |
4. C++代码
void OptimalSeam::DP_find_seam(Mat I1, Mat I2, vector<int>& seam_paths)
{
Mat_<float> E;
// 能量函数的计算,参考image stitching 2
energy_function(I1, I2, E);
E.convertTo(E, CV_32F);
Mat_<uchar> control = Mat::zeros(E.size(), CV_8U);
Mat_<float> sum_cost = E.rowRange(0, 1);
for (int i = 1; i < E.rows; i++) {
float* E_ptr = E.ptr<float>(i);
Mat_<float> cost_tmp = sum_cost.clone();
for (int j = 0; j < E.cols; j++) {
float min_e = sum_cost(0, j);
int c_x = 2;
if (j > 0 && min_e > sum_cost(0, j - 1)) {
min_e = sum_cost(0, j - 1);
c_x = 1;
}
if (j < E.cols - 1 && min_e > sum_cost(0, j + 1)) {
min_e = sum_cost(0, j + 1);
c_x = 3;
}
cost_tmp(0, j) = min_e + E_ptr[j];
control(i, j) = c_x;
}
sum_cost = cost_tmp;
}
float minE = sum_cost(0, 0);
int end_x = 0;
for (int i = 0; i < sum_cost.cols; i++) {
if (sum_cost(0, i) < minE) {
end_x = i;
}
}
Point current_p(end_x, control.rows - 1), top_p(end_x, control.rows - 1);
seam_paths.push_back(end_x);
for (; top_p.y != 0; seam_paths.push_back(top_p.x)) {
if (control(current_p) == 1) top_p.x--;
else if (control(current_p) == 3) top_p.x++;
top_p.y--;
current_p = top_p;
}
reverse(seam_paths.begin(), seam_paths.end());
}
5. 参考
https://github.com/xitu/gold-miner/blob/master/TODO1/real-world-dynamic-programming-seam-carving.md
https://medium.com/swlh/real-world-dynamic-programming-seam-carving-9d11c5b0bfca 原文