5 |
6 | QT_BEGIN_NAMESPACE
7 | namespace Ui { class MainWindow; }
8 | QT_END_NAMESPACE
9 |
10 | class MainWindow : public QMainWindow
11 | {
12 | Q_OBJECT
13 |
14 | public:
15 | MainWindow(QWidget *parent = nullptr);
16 | ~MainWindow();
17 |
18 | private slots:
19 | void on_action_triggered();
20 |
21 | void on_actionBlend_triggered();
22 |
23 | void on_actiondraw_triggered(bool checked);
24 |
25 | private:
26 | Ui::MainWindow *ui;
27 | };
28 | #endif // MAINWINDOW_H
29 |
--------------------------------------------------------------------------------
/ImgViewer/mainwindow.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1000
10 | 800
11 |
12 |
13 |
14 | MainWindow
15 |
16 |
17 |
18 |
19 | 0
20 |
21 |
22 | 0
23 |
24 |
25 | 0
26 |
27 |
28 | 0
29 |
30 | -
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | toolBar
39 |
40 |
41 | TopToolBarArea
42 |
43 |
44 | false
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | 打开
53 |
54 |
55 |
56 |
57 | 融合
58 |
59 |
60 |
61 |
62 | true
63 |
64 |
65 | 绘制
66 |
67 |
68 |
69 |
70 |
71 | QImageShowWidget
72 | QWidget
73 |
74 | 1
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/ImgViewer/qimageshowwidget.cpp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/ImgViewer/qimageshowwidget.cpp
--------------------------------------------------------------------------------
/ImgViewer/qimageshowwidget.h:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/ImgViewer/qimageshowwidget.h
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [1. 概述](#1-概述)
2 |
3 | [2. 实现](#2-实现)
4 | - [2.1. 准备](#21-准备)
5 | - [2.2. 核心](#22-核心)
6 | - [2.2.1. 均值坐标(Mean-Value Coordinates)](#221-均值坐标mean-value-coordinates)
7 | - [2.2.2. ROI边界栅格化](#222-roi边界栅格化)
8 | - [2.2.3. 核心实现](#223-核心实现)
9 | - [2.2.4. 实现中的问题](#224-实现中的问题)
10 |
11 | [3. 效果](#3-效果)
12 | - [3.1. 使用过程](#31-使用过程)
13 | - [3.2. 效率](#32-效率)
14 |
15 | [4. 参考](#4-参考)
16 |
17 | # 1. 概述
18 | 泊松融合是图像融合处理效果最好的算法,其来自于2004年Siggraph的经典paper:《Poisson Image Editing》。以这篇文章为发端,很多大神提出了一系列的优化算法。2009年, Zeev Farbman 在的SIGGRAPH上面提出的基于Mean-Value Coordinates方法的泊松融合加速算法《Coordinates for Instant Image Cloning》(文献二)。在这篇文章中,泊松方程被转换成拉普拉斯方程,并且提出了用均值坐标Mean-Value Coordinates来近似求解这个方程,从而达到实时运算的效果。
19 |
20 | 初步了解了一下原生的泊松融合算法和均值坐标融合算法,其原理包含的内涵十分丰富,包含一些诸如列散度、拉普拉斯算子、梯度场、泊松方程等等数学知识,要完全弄明白确实需要一定的基础。这里就重点关注一下根据《Coordinates for Instant Image Cloning》(文献二)实现图像融合的过程,有机会的话再详细推导一下其原理。
21 |
22 | # 2. 实现
23 | ## 2.1. 准备
24 | 在OpenCV中,已经收录了泊松融合算法,也就是函数seamlessClone():![seamlessClone][imglink1]
25 |
26 | 这个算法要求输入一个源图像,一个目标图像,源图像希望融合到目标图像的位置,以及一个mask图像。这个mask图像也就是一张二值化图像,用来标识图像的ROI(region of interest感兴趣区域)。均值坐标融合算法的输入参数也是一样的,不过mask图像很难以处理,OpenCV自带的GUI难以满足需求。所以我这里通过QT来做GUI,通过OpenCV将图像显示到QT窗体上,然后再QT窗体的图像区域内绘制多边形,多边形内部即为ROI。可以参考我的这两篇文章:
27 | [《使用QT显示OpenCV读取的图片》][netlink1]
28 | [《使用QT绘制一个多边形》][netlink2]
29 |
30 | ## 2.2. 核心
31 | ### 2.2.1. 均值坐标(Mean-Value Coordinates)
32 | 在论文中提出了一个很重要的概念也就是均值坐标(Mean-Value Coordinates)。对于如下多边形内部的点:![Mean-Value Coordinates][imglink2]
33 |
34 | 都有一系列与多边形边界相关的坐标值:![Mean-Value Coordinates][imglink3]
35 |
36 | 也就是说,只要确定了ROI,也就确定了ROI区域内每个点的均值坐标(Mean-Value Coordinates),每个点会有m个值(m为ROI边界多边形的顶点)。
37 |
38 | ### 2.2.2. ROI边界栅格化
39 | 论文中是以ROI边界多边形为例的,实际用到图像处理中是不会只用几个多边形的节点来计算的,而应该是ROI边界上连续的点。实际上不用想也知道,图像融合最关键的部分就是ROI边界部分的像素值。必须要用到ROI边界上所有的像素值来计算。
40 |
41 | 也就是说这里还需要一个工作,就是将ROI边界多边形栅格化,取得其上连续的像素位置,得到准确的栅格化多边形边界。这里可以参看我的这篇文章[《矢量线的一种栅格化算法》][netlink3]。按照顺序逐条将多边形的边栅格化,即可以得到ROI的栅格化多边形边界。
42 |
43 | ### 2.2.3. 核心实现
44 | 论文给出的算法伪代码如下:![MCV融合][imglink4]
45 |
46 | 这段算法描述并不复杂,转换成自然语言如下:
47 | 1. 假设ROI区域内有n个点,其边界由m个点组成。
48 | 2. 那么可以求每个点的MVC(均值坐标),每个点有m个坐标值,一共有n个点,MVC就是就是一个n*m的矩阵。
49 | 3. 求ROI区域边界的像素差diff,显然其是一个m*1的矩阵。
50 | 4. 那么新图像ROI区域的插值为:r = MVC * diff,矩阵乘法后r为n*1矩阵。
51 | 5. 将插值r与原图像g矩阵相加:f = g + r,替换目标图像相应位置的值。
52 |
53 | 核心部分具体的实现代码如下:
54 | ```cpp
55 | QTime startTime = QTime::currentTime();
56 |
57 | //Step1:找到边界上所有的像素点
58 | vector ROIBoundPointList;
59 | CalBoundPoint(ROIBoundPointList);
60 |
61 | //Step2:计算范围内每个点的 mean-value coordinates
62 | size_t srcImgBufNum = static_cast(srcImg.cols) * static_cast(srcImg.rows);
63 | vector> MVC(srcImgBufNum);
64 | for(size_t i = 0; i < srcImgBufNum; i++)
65 | {
66 | MVC[i].resize(ROIBoundPointList.size()-1, 0);
67 | }
68 | vector clipMap(srcImgBufNum, true); //标识范围内的点
69 |
70 | cout<<"开始计算 mean-value coordinates..." << endl;
71 | #pragma omp parallel for //开启OpenMP并行加速
72 | for (int ri = 0; ri < srcImg.rows; ++ri)
73 | {
74 | for (int ci = 0; ci < srcImg.cols; ++ci)
75 | {
76 | //点是否在多边形内
77 | size_t m = static_cast(srcImg.cols) * ri + ci;
78 | if(!Point_In_Polygon_2D(ci, ri, ROIBoundPointList))
79 | {
80 | clipMap[m] = false;
81 | continue;
82 | }
83 |
84 | //逐点计算MVC
85 | Vector2d P(ci, ri);
86 | vector alphaAngle(ROIBoundPointList.size());
87 | for(size_t pi = 1; pi < ROIBoundPointList.size(); pi++)
88 | {
89 | alphaAngle[pi] = threePointCalAngle(ROIBoundPointList[pi-1], P, ROIBoundPointList[pi]);
90 | }
91 | alphaAngle[0] = alphaAngle[ROIBoundPointList.size()-1];
92 |
93 |
94 | for(size_t pi = 1; pi < ROIBoundPointList.size(); pi++)
95 | {
96 | double w_a = tan(alphaAngle[pi-1]/2) + tan(alphaAngle[pi]/2);
97 | double w_b = (ROIBoundPointList[pi-1] - P).Mod();
98 | MVC[m][pi-1] = w_a / w_b;
99 | if(_isnan(MVC[m][pi-1])==1)
100 | {
101 | MVC[m][pi-1] = 0;
102 | }
103 | }
104 |
105 | double sum = 0;
106 | for(size_t pi = 0; pi < MVC[m].size(); pi++)
107 | {
108 | sum = sum + MVC[m][pi];
109 | }
110 |
111 | for(size_t pi = 0; pi < MVC[m].size(); pi++)
112 | {
113 | MVC[m][pi] = MVC[m][pi] / sum;
114 | }
115 | }
116 | }
117 | cout<<"计算完成!" << endl;
118 |
119 | //Step3:计算边界的像素插值
120 | vector diff;
121 | for(size_t i = 0; i < ROIBoundPointList.size()-1; i++)
122 | {
123 | size_t l = (size_t) srcImg.cols * ROIBoundPointList[i].y + ROIBoundPointList[i].x;
124 | for(int bi = 0; bi < winBandNum; bi++)
125 | {
126 | size_t m = (size_t) dstImg.cols * winBandNum * (ROIBoundPointList[i].y + posY)+ winBandNum * (ROIBoundPointList[i].x + posX) + bi;
127 | size_t n = (size_t) srcImg.cols * winBandNum * ROIBoundPointList[i].y + winBandNum * ROIBoundPointList[i].x + bi;
128 | int d = (int)(dstImg.data[m]) - (int)(srcImg.data[n]);
129 | diff.push_back(d);
130 | }
131 | clipMap[l] = false; //在多边形边上的点没法计算MVC
132 | }
133 |
134 | //Step4:插值计算
135 | cout<<"开始插值计算..." << endl;
136 | //Mat rMat(srcImg.rows, srcImg.cols, CV_64FC3);
137 | #pragma omp parallel for
138 | for (int ri = 0; ri < srcImg.rows; ++ri)
139 | {
140 | for (int ci = 0; ci < srcImg.cols; ++ci)
141 | {
142 | size_t l = (size_t) srcImg.cols * ri + ci;
143 | if(!clipMap[l])
144 | {
145 | continue;
146 | }
147 |
148 | vector r(winBandNum, 0);
149 |
150 | for(size_t pi = 0; pi < MVC[l].size(); pi++)
151 | {
152 | for(int bi = 0; bi < winBandNum; bi++)
153 | {
154 | r[bi] = r[bi] + MVC[l][pi] * diff[pi * winBandNum + bi];
155 | }
156 | }
157 |
158 | for(int bi = 0; bi < winBandNum; bi++)
159 | {
160 | size_t n = (size_t) srcImg.cols * winBandNum * ri + winBandNum * ci + bi;
161 | size_t m = (size_t) dstImg.cols * winBandNum * (ri + posY)+ winBandNum * (ci + posX) + bi;
162 |
163 | dstImg.data[m] = min(max(srcImg.data[n] + r[bi], 0.0), 255.0);
164 | }
165 | }
166 | }
167 | cout<<"插值完成!" << endl;
168 |
169 | QTime stopTime = QTime::currentTime();
170 | int elapsed = startTime.msecsTo(stopTime);
171 | cout<<"总结完成用时"<![MCV融合][imglink5]
181 |
182 | 点击"绘制"按钮,在源图像区域内绘制一个多边形,确定一个ROI:![MCV融合][imglink6]
183 |
184 | 准备一张想要融合的目标图像:![MCV融合][imglink7]
185 |
186 | 点击"融合"按钮,会加载目标图像,并会根据设置的位置,将源图像的ROI融合到目标图像中:![MCV融合][imglink8]
187 |
188 | ## 3.2. 效率
189 | 在Debug模式,不使用OpenMP加速的情况下,这个算法的效率大约需要50秒左右的时间。
190 |
191 | 在Debug模式,使用OpenMP加速,算法的效率可以优化到10秒,也就是不使用OpenMP加速时的5倍左右。而我使用的机器CPU是i7-8750H标压6核CPU,考虑到一些IO操作造成的性能损耗,这个优化效率是正常的。
192 |
193 | 最后在使用Release模式,使用OpenMP加速之后,算法的效率可以优化到1秒左右,这说明编译器优化对程序性能也是有很大影响的,尤其是对并行程序而言。
194 |
195 | 这个实现只是这个算法的初始实现,效率就已经达到了1秒左右,看来论文说的可以达到实时融合确实不是虚言。有机会再尝试一下论文中提到的一些性能优化实现。
196 |
197 | # 4. 参考
198 | [1] [泊松融合及其优化算法](https://zhuanlan.zhihu.com/p/31680396)
199 |
200 | [2] [Coordinates for Instant Image Cloning](https://www.cse.huji.ac.il/~danix/mvclone/)
201 |
202 | [3] [图像处理(十二)图像融合(1)Seamless cloning泊松克隆-Siggraph 2004](https://blog.csdn.net/hjimce/article/details/45716603)
203 |
204 | [4] [多尺度并行坐标插值实时图像克隆算法](http://sjcj.nuaa.edu.cn/sjcjycl/article/html/201901014)
205 |
206 | [实现代码](https://github.com/fafa1899/MVCImageBlend)
207 |
208 | [基于均值坐标(Mean-Value Coordinates)的图像融合算法的优化实现](https://blog.csdn.net/charlee44/article/details/104979382)
209 |
210 | [netlink1]:https://blog.csdn.net/charlee44/article/details/104464262
211 | [netlink2]:https://blog.csdn.net/charlee44/article/details/104696765
212 | [netlink3]:https://blog.csdn.net/charlee44/article/details/104662373
213 |
214 | [imglink1]:https://raw.githubusercontent.com/fafa1899/MVCImageBlend/master/README_IMG/1.PNG
215 | [imglink2]:https://raw.githubusercontent.com/fafa1899/MVCImageBlend/master/README_IMG/2.PNG
216 | [imglink3]:https://raw.githubusercontent.com/fafa1899/MVCImageBlend/master/README_IMG/3.PNG
217 | [imglink4]:https://raw.githubusercontent.com/fafa1899/MVCImageBlend/master/README_IMG/4.PNG
218 | [imglink5]:https://raw.githubusercontent.com/fafa1899/MVCImageBlend/master/README_IMG/5.PNG
219 | [imglink6]:https://raw.githubusercontent.com/fafa1899/MVCImageBlend/master/README_IMG/6.PNG
220 | [imglink7]:https://raw.githubusercontent.com/fafa1899/MVCImageBlend/master/README_IMG/7.jpg
221 | [imglink8]:https://raw.githubusercontent.com/fafa1899/MVCImageBlend/master/README_IMG/8.PNG
--------------------------------------------------------------------------------
/README_IMG/1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/README_IMG/1.PNG
--------------------------------------------------------------------------------
/README_IMG/2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/README_IMG/2.PNG
--------------------------------------------------------------------------------
/README_IMG/3.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/README_IMG/3.PNG
--------------------------------------------------------------------------------
/README_IMG/4.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/README_IMG/4.PNG
--------------------------------------------------------------------------------
/README_IMG/5.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/README_IMG/5.PNG
--------------------------------------------------------------------------------
/README_IMG/6.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/README_IMG/6.PNG
--------------------------------------------------------------------------------
/README_IMG/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/README_IMG/7.jpg
--------------------------------------------------------------------------------
/README_IMG/8.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/README_IMG/8.PNG
--------------------------------------------------------------------------------
/data/source.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/data/source.jpg
--------------------------------------------------------------------------------
/data/target.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/data/target.jpg
--------------------------------------------------------------------------------
/mvc-final-opt.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/mvc-final-opt.pdf
--------------------------------------------------------------------------------
/论文翻译.doc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fafa1899/MVCImageBlend/fcea9e25c2805e6bd1f40bfc37ea56079479e3d4/论文翻译.doc
--------------------------------------------------------------------------------