前言
最近碰到一个项目上的难题,是要从电动显微镜对焦的多张图像进行融合。因为,显微镜物镜的景深范围较小,可能在同一视野中有多个需要拍摄的物体位于不同的景深范围内,所以想通过图像的融合,将不同景深上的多张图像进行融合,从而把这些物体都在同一张图像中对用户进行展示。
opencv中的一个方法
在opencv中,提供了一个图像融合函数seamlessClone,可以直接进行图像融合。
dst = cv2.imread("/Users/zoulei/files/personal/images/bg.jpg")
obj = cv2.imread("/Users/zoulei/files/personal/images/fore.jpg")
# Create an all white mask
mask = 255 * np.ones(obj.shape, obj.dtype)
# The location of the center of the src in the dst
width, height, channels = dst.shape
center = (int(height / 2), int(width / 2))
# Seamlessly clone src into dst and put the results in output
normal_clone = cv2.seamlessClone(obj, dst, mask, center, cv2.NORMAL_CLONE)
mixed_clone = cv2.seamlessClone(obj, dst, mask, center, cv2.MIXED_CLONE)
cv2.imshow("origin image", dst)
cv2.imshow("fution image normal", normal_clone)
cv2.imshow("fution image mixed", mixed_clone)
cv2.waitKey()
cv2.destroyAllWindows()
opencv这块的原理应该是基于一个牛逼的图像融合算法:泊松融合,出自于论文[Poisson Image Editing-2003]。于是去了解了一下这个厉害的算法。
泊松融合
泊松融合大概的逻辑是:
- 将两幅图像都计算散度场,然后将两幅图像的散度场通过掩码进行相加
- 通过计算泊松方程重建图像。
我自己的理解是求散度之后更好的得到图像更深层的信息,在更深的层次上对图像进行信息融合,然后再重建,比直接了当的把图贴上去效果要好得多。
当然,论文是比较严谨和难懂的,我这里只是大概的记录下过程,以便于理解。
图像梯度
图像梯度的概念在我之前计算清晰度的文章中已经描述过了:
https://blog.csdn.net/pcgamer/article/details/127942102?spm=1001.2014.3001.5502
就不再赘述了。
图像散度
图像的散度的定义就是对图像进行二阶求导,实际上就是拉普拉斯算子:
∇
2
f
=
∂
2
f
∂
2
x
+
∂
2
y
∂
2
x
\nabla^2f=\frac{\partial^2 f}{\partial^2 x} + \frac{\partial^2 y}{\partial^2 x}
∇2f=∂2x∂2f+∂2x∂2y
因为图像是离散的数据,所以这个偏导(x方向上)的在离散数据上的定义就是:
∂
2
f
∂
2
x
=
f
(
x
+
1
,
y
)
+
f
(
x
−
1
,
y
)
−
2
f
(
x
,
y
)
\frac{\partial^2 f}{\partial^2 x} = f(x+1, y) + f(x-1, y) - 2f(x,y)
∂2x∂2f=f(x+1,y)+f(x−1,y)−2f(x,y)
(x,y)表示图像中的某一个像素值
同样的,在y方向的偏导就是:
∂
2
f
∂
2
y
=
f
(
x
,
y
+
1
)
+
f
(
x
,
y
−
1
)
−
2
f
(
x
,
y
)
\frac{\partial^2 f}{\partial^2 y} = f(x, y+1) + f(x, y-1) - 2f(x,y)
∂2y∂2f=f(x,y+1)+f(x,y−1)−2f(x,y)
根据第一个公式,两者相加就是
∇
2
f
=
∂
2
f
∂
2
x
=
f
(
x
+
1
,
y
)
+
f
(
x
−
1
,
y
)
+
f
(
x
,
y
+
1
)
+
f
(
x
,
y
−
1
)
−
4
f
(
x
,
y
)
\nabla^2f=\frac{\partial^2 f}{\partial^2 x} = f(x+1, y) + f(x-1, y) + f(x, y+1) + f(x, y-1) - 4f(x,y)
∇2f=∂2x∂2f=f(x+1,y)+f(x−1,y)+f(x,y+1)+f(x,y−1)−4f(x,y)
从图像意义上来看,对某一个图像数据进行二阶求导就是等于这个像素的上下左右像素值减去自己像素值的四倍。
在图像中就可以使用一个掩膜来进行计算:
[
0
1
0
1
−
4
1
0
1
0
]
\begin{bmatrix}0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0\end{bmatrix}
⎣⎡0101−41010⎦⎤
这个掩膜就是在图像领域的拉普拉斯算子。
计算图像的散度就可以直接将拉普拉斯算子作用于图像上进行计算。
我个人理解这个拉普拉斯可以提取图像中的边缘信息,因为边缘信息的梯度较小,如果一个像素点旁边的像素值都是相同的(也就是没有变化),这个算子计算得出的结果就是0。
融合图像散度
在那边论文描述的融合过程中,应该是把两幅图像分别求梯度场(一阶导数),然后把两幅图像的梯度场相加,然后再对融合梯度场进行一次求导,就是二次梯度场(散度),这就得到了融合图像的散度场。
通过散度场进行图像重建
对论文中的公式看的不是太明白,大概的意思是:
根据拉普拉斯算子的定义,图像进行拉普拉斯算子的卷积运算后就可以得到散度场。
那么,反过来,得到了散度场是否就可以得到原图像呢?答案是肯定的。
大致的思路如下:
-
假设有这么一副图像:
[ x 1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9 ] \begin{bmatrix} x_1 & x_2 & x_3 \\ x_4 & x_5 & x_6 \\ x_7 & x_8 & x_9\end{bmatrix} ⎣⎡x1x4x7x2x5x8x3x6x9⎦⎤ -
这幅图像对应的散度场: V ( 5 ) = x 2 + x 4 + x 6 + x 8 − 4 x 5 V(5) = x_2 + x_4 + x_6 + x_8 - 4x_5 V(5)=x2+x4+x6+x8−4x5
-
总共有9个未知数,现在有了一个方程,如果还有8个方程的话,那么每个未知数就会有一个固定的解了。
-
关键的点来了:泊松融合的目的是将源图像的一部分无缝融合到目标图像上。其本质是在保持目标图在融合边界的像素的同时以源图像的该部分的梯度场作为指导来生成融合区域内的像素。整体原则是保持融合区域内的生成像素的梯度场与源图像融合部分的像素的梯度场尽可能一致,反映到方程求解中则是梯度差异尽可能小。也就是让生成区域的拉普拉斯结果和源图像的拉普拉斯结果一致,且生成区域边界的的值和目标图像在融合区域的边界值一致。
-
上面这句话最后说的边界值需要一致,也就是说融合图像的边界值可以和目标图像一致,也就是说上面的9个未知量(不管这个是融合的还是原图像)中,8个是边界,边界像素可以和目标图像一致,也就是已知量,加上这8个方程,联合散度场的计算公式,就可以重建出原始的图像了。
泊松融合的一般逻辑
综上所述,我理解泊松融合就是下面的逻辑:
- 计算需要被融合的图像的梯度场
- 通过掩码得到目标图像上的ROI区域,计算的到这个ROI的梯度场。
- 把第一步和第二步的梯度场相加
- 对第三步的梯度场再次求导,得到融合图像的二阶导,也就是散度场
- 再对这个散度场进行图像重建,得到融合图像。
在opencv中的seamlessClone函数中,有三个flag方式:
- NORMAL_CLONE
- MIXED_CLONE
- MONOCHROME_TRANSFER
我还没去仔细看opencv的源码,根据我自己的实验,我个人感觉这三个参数主要在于上面的第三步的区别,可能是对两遍的梯度场做了一些权重的设置,看是以哪个梯度场为主来进行图像的生成。文章来源:https://www.toymoban.com/news/detail-492299.html
- NORMAL_CLONE,前需要融合的前景图像为主,边缘基本都保留了
- MIXED_CLONE,两者的特性都保留
- MONOCHROME_TRANSFER,保留前景的轮廓,把背景的风格给融合进去。上面没有这样的图像,这里补充一张:
文章来源地址https://www.toymoban.com/news/detail-492299.html
到了这里,关于python的opencv操作记录(十)——图像融合的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!