图像融合之拉普拉斯融合(laplacian blending)

内容预览:
  •     (3) 对downL继续进行(1) (2)操作,不断计算残差图lapL1, lap2, ...~
  •     (4)拉普拉斯 残差金字塔中一共有level幅图像~
  •   6. 重复步骤5,直至重建出第0层,也就是金字塔最低端的图像,即blen...~

一、拉普拉斯融合基本步骤

   1. 两幅图像L,R,以及二值掩模mask,给定金字塔层数level。

  2. 分别根据L,R构建其对应的拉普拉斯残差金字塔(层数为level),并保留高斯金字塔下采样最顶端的图像(尺寸最小的图像,第level+1层):

    拉普拉斯残差金字塔构建方法如下,以L图为例:

    (1) 对L进行高斯下采样得到downL,OpenCV中pyrDown()函数可以实现此功能。然后再对downL进行高斯上采样得到upL,OpenCV中pyrUp()函数可以实现此功能。

    (2) 计算原图L与upL之间的残差,得到一幅残差图lapL0。作为残差金字塔最低端的图像。

    (3) 对downL继续进行(1) (2)操作,不断计算残差图lapL1, lap2, lap3…..lapN。这样得到一系列残差图,即为拉普拉斯残差金字塔。

    (4)拉普拉斯 残差金字塔中一共有level幅图像。而我们需要保留第level+1层的高斯下采样图topL,以便后面使用。

  3. 二值掩模mask下采样构建高斯金字塔,同样利用pyrDown()实现,共有level+1层。

  4. 利用mask金字塔每一层的mask图,将L图和R图的拉普拉斯残差金字塔对应层的图像合并为一幅图像。这样得到合并后的拉普拉斯残差金字塔。同时利用最顶端的mask将步骤2中保留的topL和topR合并为topLR。

  5. 以topLR为金字塔最顶端的图像,利用pyrUp()函数对topLR进行高斯上采样,得到upTopLR,并将upTopLR与步骤4中合并后的残差金字塔对应层的图像相加,重建出该层的图像。

  6. 重复步骤5,直至重建出第0层,也就是金字塔最低端的图像,即blendImg。输出。

 

  拉普拉斯金字塔的OpenCV实现代码如下:

  1 #include <opencv2/opencv.hpp>
2 #include <iostream>
3 #include <string>
4
5 using namespace std;
6 using namespace cv;
7
8 /************************************************************************/
9 /* 说明:
10 *金字塔从下到上依次为 [0,1,...,level-1] 层
11 *blendMask 为图像的掩模
12 *maskGaussianPyramid为金字塔每一层的掩模
13 *resultLapPyr 存放每层金字塔中直接用左右两图Laplacian变换拼成的图像
14 */
15 /************************************************************************/
16
17
18 class LaplacianBlending {
19 private:
20 Mat left;
21 Mat right;
22 Mat blendMask;
23
24 //Laplacian Pyramids
25 vector<Mat> leftLapPyr, rightLapPyr, resultLapPyr;
26 Mat leftHighestLevel, rightHighestLevel, resultHighestLevel;
27 //mask为三通道方便矩阵相乘
28 vector<Mat> maskGaussianPyramid;
29
30 int levels;
31
32 void buildPyramids()
33 {
34 buildLaplacianPyramid(left, leftLapPyr, leftHighestLevel);
35 buildLaplacianPyramid(right, rightLapPyr, rightHighestLevel);
36 buildGaussianPyramid();
37 }
38
39 void buildGaussianPyramid()
40 {
41 //金字塔内容为每一层的掩模
42 assert(leftLapPyr.size()>0);
43
44 maskGaussianPyramid.clear();
45 Mat currentImg;
46 cvtColor(blendMask, currentImg, CV_GRAY2BGR);
47 //保存mask金字塔的每一层图像
48 maskGaussianPyramid.push_back(currentImg); //0-level
49
50 currentImg = blendMask;
51 for (int l = 1; l<levels + 1; l++) {
52 Mat _down;
53 if (leftLapPyr.size() > l)
54 pyrDown(currentImg, _down, leftLapPyr[l].size());
55 else
56 pyrDown(currentImg, _down, leftHighestLevel.size()); //lowest level
57
58 Mat down;
59 cvtColor(_down, down, CV_GRAY2BGR);
60 //add color blend mask into mask Pyramid
61 maskGaussianPyramid.push_back(down);
62 currentImg = _down;
63 }
64 }
65
66 void buildLaplacianPyramid(const Mat& img, vector<Mat>& lapPyr, Mat& HighestLevel)
67 {
68 lapPyr.clear();
69 Mat currentImg = img;
70 for (int l = 0; l<levels; l++) {
71 Mat down, up;
72 pyrDown(currentImg, down);
73 pyrUp(down, up, currentImg.size());
74 Mat lap = currentImg - up;
75 lapPyr.push_back(lap);
76 currentImg = down;
77 }
78 currentImg.copyTo(HighestLevel);
79 }
80
81 Mat reconstructImgFromLapPyramid()
82 {
83 //将左右laplacian图像拼成的resultLapPyr金字塔中每一层
84 //从上到下插值放大并与残差相加,即得blend图像结果
85 Mat currentImg = resultHighestLevel;
86 for (int l = levels - 1; l >= 0; l--)
87 {
88 Mat up;
89 pyrUp(currentImg, up, resultLapPyr[l].size());
90 currentImg = up + resultLapPyr[l];
91 }
92 return currentImg;
93 }
94
95 void blendLapPyrs()
96 {
97 //获得每层金字塔中直接用左右两图Laplacian变换拼成的图像resultLapPyr
98 resultHighestLevel = leftHighestLevel.mul(maskGaussianPyramid.back()) +
99 rightHighestLevel.mul(Scalar(1.0, 1.0, 1.0) - maskGaussianPyramid.back());
100 for (int l = 0; l<levels; l++)
101 {
102 Mat A = leftLapPyr[l].mul(maskGaussianPyramid[l]);
103 Mat antiMask = Scalar(1.0, 1.0, 1.0) - maskGaussianPyramid[l];
104 Mat B = rightLapPyr[l].mul(antiMask);
105 Mat blendedLevel = A + B;
106
107 resultLapPyr.push_back(blendedLevel);
108 }
109 }
110
111 public:
112 LaplacianBlending(const Mat& _left, const Mat& _right, const Mat& _blendMask, int _levels) ://construct function, used in LaplacianBlending lb(l,r,m,4);
113 left(_left), right(_right), blendMask(_blendMask), levels(_levels)
114 {
115 assert(_left.size() == _right.size());
116 assert(_left.size() == _blendMask.size());
117 //创建拉普拉斯金字塔和高斯金字塔
118 buildPyramids();
119 //每层金字塔图像合并为一个
120 blendLapPyrs();
121 };
122
123 Mat blend()
124 {
125 //重建拉普拉斯金字塔
126 return reconstructImgFromLapPyramid();
127 }
128 };
129
130 Mat LaplacianBlend(const Mat &left, const Mat &right, const Mat &mask)
131 {
132 LaplacianBlending laplaceBlend(left, right, mask, 10);
133 return laplaceBlend.blend();
134 }
135
136 int main() {
137 Mat img8UL = imread("data/apple.jpg");
138 Mat img8UR = imread("data/orange.jpg");
139
140 int imgH = img8UL.rows;
141 int imgW = img8UL.cols;
142
143 imshow("left", img8UL);
144 imshow("right", img8UR);
145
146 Mat img32fL, img32fR;
147 img8UL.convertTo(img32fL, CV_32F);
148 img8UR.convertTo(img32fR, CV_32F);
149
150 //创建mask
151 Mat mask = Mat::zeros(imgH, imgW, CV_32FC1);
152 mask(Range::all(), Range(0, mask.cols * 0.5)) = 1.0;
153
154 Mat blendImg = LaplacianBlend(img32fL, img32fR, mask);
155
156 blendImg.convertTo(blendImg, CV_8UC3);
157 imshow("blended", blendImg);
158
159 waitKey(0);
160 return 0;
161 }

View Code

 融合结果如下图:

  

 

金字塔层数level=5                                                金字塔层数level=10

  

 

  附上自己实现pyrDown和pyrUp写的拉普拉斯融合,仅供参考:

  1 #include <opencv2opencv.hpp>
2 #include <iostream>
3 #include <string>
4
5 using namespace std;
6
7 //#define DEBUG
8
9
10 void borderInterp(cv::Mat &_src, int radius)
11 {
12 int imgH = _src.rows;
13 int imgW = _src.cols;
14 float *pSrc = (float*)_src.data;
15 for (int i = radius; i < imgH-radius*2; i++)
16 {
17 for (int j = 0; j < 2; j++)
18 {
19 int srcIdx = (i*imgW + j + 3) * 3;
20 int dstIdx = (i*imgW + j) * 3;
21 pSrc[dstIdx] = pSrc[srcIdx];
22 pSrc[dstIdx + 1] = pSrc[srcIdx + 1];
23 pSrc[dstIdx + 2] = pSrc[srcIdx + 2];
24 }
25 for (int j = imgW - radius; j < imgW; j++)
26 {
27 int srcIdx = (i*imgW + j - 3) * 3;
28 int dstIdx = (i*imgW + j) * 3;
29 pSrc[dstIdx] = pSrc[srcIdx];
30 pSrc[dstIdx + 1] = pSrc[srcIdx + 1];
31 pSrc[dstIdx + 2] = pSrc[srcIdx + 2];
32
33 }
34 }
35 for (int j = 0; j < imgW; j++)
36 {
37 for (int i = 0; i < 2; i++)
38 {
39 int srcIdx = ((i + 3)*imgW + j) * 3;
40 int dstIdx = (i*imgW + j) * 3;
41 pSrc[dstIdx] = pSrc[srcIdx];
42 pSrc[dstIdx + 1] = pSrc[srcIdx + 1];
43 pSrc[dstIdx + 2] = pSrc[srcIdx + 2];
44 }
45 for (int i = imgH - radius; i < imgH; i++)
46 {
47 int srcIdx = ((i - 3)*imgW + j) * 3;
48 int dstIdx = (i*imgW + j) * 3;
49 pSrc[dstIdx] = pSrc[srcIdx];
50 pSrc[dstIdx + 1] = pSrc[srcIdx + 1];
51 pSrc[dstIdx + 2] = pSrc[srcIdx + 2];
52 }
53 }
54 }
55
56 void myPyrDown(cv::Mat src, cv::Mat &dst, cv::Size dSize)
57 {
58 dSize = dSize.area() == 0 ? cv::Size((src.cols + 1) / 2, (src.rows + 1) / 2) : dSize;
59
60 float scale = 1. / 16;
61
62 int imgH = src.rows;
63 int imgW = src.cols;
64 cv::Mat _src = cv::Mat::zeros(imgH + 4, imgW + 4, CV_32FC3);
65 int _imgH = _src.rows;
66 int _imgW = _src.cols;
67 src.copyTo(_src(cv::Rect(2, 2, imgW, imgH)));
68 borderInterp(_src, 2);
69
70 //高斯卷积
71 cv::Mat gaussImg = cv::Mat::zeros(imgH, imgW, CV_32FC3);
72 cv::Mat tmpRowGaussImg = _src.clone();
73 float *pSrc = (float*)_src.data;
74 float *pRowGaussImg = (float*)tmpRowGaussImg.data;
75 //行卷积
76 for (int i = 2; i < imgH+2; i++)
77 {
78 for (int j = 2; j < imgW+2; j++)
79 {
80 float val[3] = { 0 };
81 int idx = i*_imgW + j;
82 for (int chan = 0; chan < 3; chan++)
83 {
84 val[chan] += pSrc[(idx - 2) * 3 + chan] + pSrc[(idx + 2) * 3 + chan]
85 + 4 * (pSrc[(idx - 1) * 3 + chan] + pSrc[(idx + 1) * 3 + chan])
86 + 6 * pSrc[idx * 3 + chan];
87 }
88 pRowGaussImg[idx * 3] = val[0] * scale;
89 pRowGaussImg[idx * 3 + 1] = val[1] * scale;
90 pRowGaussImg[idx * 3 + 2] = val[2] * scale;
91 }
92 }
93
94 float *pGaussImg = (float*)gaussImg.data;
95 //列卷积
96 for (int j = 0; j < imgW; j++)
97 {
98 for (int i = 0; i < imgH; i++)
99 {
100 int gi = i + 2;
101 int gj = j + 2;
102 float val[3] = { 0 };
103 int idx = gi*_imgW + gj;
104 for (int chan = 0; chan < 3; chan++)
105 {
106 val[chan] += pRowGaussImg[(idx-2*_imgW) * 3 + chan] + pRowGaussImg[(idx + 2*_imgW) * 3 + chan]
107 + 4 * (pRowGaussImg[(idx - _imgW) * 3 + chan] + pRowGaussImg[(idx + _imgW) * 3 + chan])
108 + 6 * pRowGaussImg[idx * 3 + chan];
109 }
110
111 int id = (i*imgW + j) * 3;
112 pGaussImg[id] = val[0] * scale;
113 pGaussImg[id + 1] = val[1] * scale;
114 pGaussImg[id + 2] = val[2] * scale;
115 }
116 }
117
118 int downH = dSize.height;
119 int downW = dSize.width;
120
121 if (abs(downH * 2 - imgH) > 2) downH = imgH*0.5;
122 if (abs(downW * 2 - imgW) > 2) downW = imgW*0.5;
123 downH = (downH < 1) ? 1 : downH;
124 downW = (downW < 1) ? 1 : downW;
125
126 dst = cv::Mat::zeros(downH, downW, CV_32FC3);
127 float *pDst = (float*)dst.data;
128 for (int i = 0; i < imgH; i++)
129 {
130 for (int j = 0; j < imgW; j++)
131 {
132 if (i % 2 != 0 || j % 2 != 0) continue;
133 int srcIdx = (i*imgW + j) * 3;
134 int y = int((i+1) * 0.5);
135 int x = int((j+1) * 0.5);
136 y = (y >= downH) ? (downH - 1) : y;
137 x = (x >= downW) ? (downW - 1) : x;
138 int dstIdx = (y*downW + x) * 3;
139 pDst[dstIdx] = pGaussImg[srcIdx];
140 pDst[dstIdx + 1] = pGaussImg[srcIdx + 1];
141 pDst[dstIdx + 2] = pGaussImg[srcIdx + 2];
142 }
143 }
144 }
145
146 void myPyrUp(cv::Mat src, cv::Mat &dst, cv::Size dSize)
147 {
148 dSize = dSize.area() == 0 ? cv::Size(src.cols * 2, src.rows * 2) : dSize;
149 cv::Mat _src;
150 src.convertTo(_src, CV_32FC3);
151
152 float scale = 1. / 8;
153
154 int imgH = src.rows;
155 int imgW = src.cols;
156 int upImgH = dSize.height;
157 int upImgW = dSize.width;
158
159 if (abs(upImgH - imgH * 2) > upImgH % 2) upImgH = imgH*2;
160 if (abs(upImgW - imgW * 2) > upImgW % 2) upImgW = imgW*2;
161
162 cv::Mat upImg = cv::Mat::zeros(upImgH, upImgW, CV_32FC3);
163 float *pSrc = (float*)_src.data;
164 float *pUpImg = (float*)upImg.data;
165 for (int i = 0; i < upImgH; i++)
166 {
167 for (int j = 0; j < upImgW; j++)
168 {
169 if (i % 2 != 0 || j % 2 != 0) continue;
170 int dstIdx = (i*upImgW + j)*3;
171 int y = int((i+1)*0.5);
172 int x = int((j+1)*0.5);
173 y = (y >= imgH) ? (imgH - 1) : y;
174 x = (x >= imgW) ? (imgW - 1) : x;
175 int srcIdx = (y*imgW + x) * 3;
176
177 pUpImg[dstIdx] = pSrc[srcIdx];
178 pUpImg[dstIdx + 1] = pSrc[srcIdx + 1];
179 pUpImg[dstIdx + 2] = pSrc[srcIdx + 2];
180 }
181 }
182
183 dst = cv::Mat::zeros(dSize, CV_32FC3);
184 cv::Mat _upImg = cv::Mat::zeros(upImgH + 4, upImgW + 4, CV_32FC3);
185 int _imgH = _upImg.rows;
186 int _imgW = _upImg.cols;
187 upImg.copyTo(_upImg(cv::Rect(2, 2, upImgW, upImgH)));
188 borderInterp(_upImg, 2);
189
190 //高斯卷积
191 cv::Mat tempRowGaussImg = _upImg.clone();
192 float *pUpData = (float*)_upImg.data;
193 float *pRowGaussImg = (float*)tempRowGaussImg.data;
194 //行卷积
195 for (int i = 2; i < upImgH + 2; i++)
196 {
197 for (int j = 2; j < upImgW + 2; j++)
198 {
199 float val[3] = { 0 };
200 int idx = i*_imgW + j;
201 for (int chan = 0; chan < 3; chan++)
202 {
203 val[chan] += pUpData[(idx - 2) * 3 + chan] + pUpData[(idx + 2) * 3 + chan]
204 + 4 * (pUpData[(idx - 1) * 3 + chan] + pUpData[(idx + 1) * 3 + chan])
205 + 6 * pUpData[idx * 3 + chan];
206 }
207
208 pRowGaussImg[idx * 3] = val[0] * scale;
209 pRowGaussImg[idx * 3 + 1] = val[1] * scale;
210 pRowGaussImg[idx * 3 + 2] = val[2] * scale;
211 }
212 }
213
214
215 //列卷积
216 float *pDst = (float*)dst.data;
217 for (int j = 0; j < upImgW; j++)
218 {
219 for (int i = 0; i < upImgH; i++)
220 {
221 int gi = i + 2;
222 int gj = j + 2;
223 float val[3] = { 0 };
224 int idx = gi*_imgW + gj;
225 for (int chan = 0; chan < 3; chan++)
226 {
227 val[chan] += pRowGaussImg[(idx - 2 * _imgW) * 3 + chan] + pRowGaussImg[(idx + 2 * _imgW) * 3 + chan]
228 + 4 * (pRowGaussImg[(idx - _imgW) * 3 + chan] + pRowGaussImg[(idx + _imgW) * 3 + chan])
229 + 6 * pRowGaussImg[idx * 3 + chan];
230 }
231
232 int id = (i*upImgW + j) * 3;
233 pDst[id] = val[0] * scale;
234 pDst[id + 1] = val[1] * scale;
235 pDst[id + 2] = val[2] * scale;
236 }
237 }
238 }
239
240 void buildLaplacianPyramid(cv::Mat srcImg, vector<cv::Mat> &pyramidImgs, cv::Mat &topLevelImg, int levels)
241 {
242 cv::Mat currentImg = srcImg;
243 for (int k = 0; k < levels; k++)
244 {
245 cv::Mat downImg, upImg, lpImg;
246
247 #ifdef DEBUG
248 cv::pyrDown(currentImg, downImg);
249 cv::pyrUp(downImg, upImg, currentImg.size());
250 #else
251 myPyrDown(currentImg, downImg, cv::Size());
252 myPyrUp(downImg, upImg, currentImg.size());
253 #endif
254
255 lpImg = currentImg - upImg;
256 pyramidImgs.push_back(lpImg);
257 currentImg = downImg;
258 }
259 currentImg.copyTo(topLevelImg);
260 }
261
262 void buildGaussPyramid(cv::Mat mask, vector<cv::Mat> &maskGaussPyramidImgs, vector<cv::Mat> pyramidImgs,cv::Mat topLevelImg, int levels)
263 {
264 cv::Mat currentMask;
265 //mask转3通道
266 if (mask.channels() == 1)
267 {
268 cv::cvtColor(mask, currentMask, CV_GRAY2BGR);
269 }
270 else if(mask.channels()==3)
271 {
272 currentMask = mask;
273 }
274
275 maskGaussPyramidImgs.push_back(currentMask);
276 for (int k = 1; k < levels+1; k++)
277 {
278 cv::Mat downMask;
279 if (k < levels)
280 {
281 #ifdef DEBUG
282 cv::pyrDown(currentMask, downMask, pyramidImgs[k].size());
283 #else
284 myPyrDown(currentMask, downMask, pyramidImgs[k].size());
285 #endif
286 }
287 else
288 {
289 #ifdef DEBUG
290 cv::pyrDown(currentMask, downMask, topLevelImg.size());
291 #else
292 myPyrDown(currentMask, downMask, topLevelImg.size());
293 #endif
294 }
295
296 maskGaussPyramidImgs.push_back(downMask);
297 currentMask = downMask;
298 }
299 }
300
301 void buildResultPyramid(vector<cv::Mat> leftPyramidImgs, vector<cv::Mat> rightPyramidImgs, vector<cv::Mat> maskPyramids, vector<cv::Mat> &resultPyramidImgs, int levels)
302 {
303
304 for (int k = 0; k < levels; k++)
305 {
306 cv::Mat left = leftPyramidImgs[k].mul(maskPyramids[k]);
307 cv::Mat right = rightPyramidImgs[k].mul(cv::Scalar(1.0,1.0,1.0) - maskPyramids[k]);
308 cv::Mat result = left + right;
309 resultPyramidImgs.push_back(result);
310 }
311
312 }
313
314 void reconstruct(vector<cv::Mat> lpPyramidImgs, cv::Mat blendTopLevelImg, cv::Mat &blendImg, int levels)
315 {
316 cv::Mat currentImg = blendTopLevelImg;
317 for (int k = levels - 1; k >= 0; k--)
318 {
319 cv::Mat upImg;
320 #ifdef DEBUG
321 cv::pyrUp(currentImg, upImg, lpPyramidImgs[k].size());
322 #else
323 myPyrUp(currentImg, upImg, lpPyramidImgs[k].size());
324 #endif
325 currentImg = upImg + lpPyramidImgs[k];
326 }
327 currentImg.copyTo(blendImg);
328 }
329
330 cv::Mat laplacianBlending(cv::Mat leftImg, cv::Mat rightImg, cv::Mat mask)
331 {
332 cv::Mat leftImg32f, rightImg32f, mask32f;
333 leftImg.convertTo(leftImg32f, CV_32FC1);
334 rightImg.convertTo(rightImg32f, CV_32FC1);
335 mask.convertTo(mask32f, CV_32FC1);
336
337 vector<cv::Mat> leftLpPyramidImgs, rightLpPyramidImgs, resultLpPyramidImgs, gaussPyramidMaskImgs;
338 cv::Mat leftTopLevelImg, rightTopLevelImg;
339 int levels =4;
340 //拉普拉斯金字塔
341 buildLaplacianPyramid(leftImg32f, leftLpPyramidImgs, leftTopLevelImg, levels);
342 buildLaplacianPyramid(rightImg32f, rightLpPyramidImgs, rightTopLevelImg, levels);
343 //mask创建gauss金字塔
344 buildGaussPyramid(mask32f, gaussPyramidMaskImgs, leftLpPyramidImgs, leftTopLevelImg, levels);
345 //结合左右两图的laplacian残差图
346 buildResultPyramid(leftLpPyramidImgs, rightLpPyramidImgs, gaussPyramidMaskImgs, resultLpPyramidImgs, levels);
347 //
348 cv::Mat blendImg = cv::Mat::zeros(leftImg.size(), CV_32FC3);
349
350 cv::Mat blendTopLevelImg = leftTopLevelImg.mul(gaussPyramidMaskImgs[levels]) + rightTopLevelImg.mul(cv::Scalar(1.0, 1.0, 1.0) - gaussPyramidMaskImgs[levels]);
351 reconstruct(resultLpPyramidImgs, blendTopLevelImg, blendImg, levels);
352
353 blendImg.convertTo(blendImg, CV_8UC3);
354 return blendImg;
355 }
356
357 void main()
358 {
359 cv::Mat appleImg = cv::imread("data/apple.jpg");
360 cv::Mat pearImg = cv::imread("data/orange.jpg");
361
362 int imgH = appleImg.rows;
363 int imgW = appleImg.cols;
364 cv::Mat mask = cv::Mat::zeros(imgH, imgW, CV_32FC1);
365 mask(cv::Range::all(), cv::Range(0, imgW*0.5)) = 1.0;
366 cv::Mat blendImg = laplacianBlending(appleImg, pearImg, mask);
367 cv::namedWindow("blendImg", 0);
368 cv::imshow("blendImg", blendImg);
369 cv::imwrite("data/blendImg.png", blendImg);
370 cv::waitKey(0);
371 }

View Code

 

以上就是:图像融合之拉普拉斯融合(laplacian blending) 的全部内容。

本站部分内容来源于互联网和用户投稿,如有侵权请联系我们删除,谢谢。
Email:[email protected]


0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论