0%

OpenCV之图像合成

实验1-2,用alpha混合,为图片替换一张新的背景

OpenCV学习记录(二)

实验说明

实验1-2:图像合成

实验要求:

1)现有一张4通道透明图像a.png:

2)从其中提取出alpha通道并显示;

3)用alpha混合,为a.png替换一张新的背景(bg.png)。

图像通道

首先,先了解什么是图像通道。

一幅完整的图像,红色绿色蓝色三个通道缺一不可。即使图像中看起来没有蓝色,只能说蓝色光的亮度均为0或者各像素值的红色和绿色通道不全为0,但不能说没有蓝色通道存在。

一张RGB图像含有三个通道:红(Red)、绿(Green)、蓝(Blue)。一张RGBA图像含有四个通道:红(Red)、绿(Green)、蓝(Blue)和Alpha(A)。一张灰度图则只有一个通道。

至于实验中提到的Alpha通道(α Channel),是计算机图形学中的术语,指的是特别的通道,意思是“非彩色”通道,主要是用来保存选区和编辑选区,代表透明度。至于为什么用α来表示,可能是因为它是不表示某种颜色的图片的第一个属性,毕竟α自身没有所谓”透明度“的含义。

提取通道

那么,如何确定一张图片拥有的通道数,又怎样将不同通道的图像分别提取出来呢?

下面是提取通道的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#OpenCV的方式
#读入完整图片,包括alpha通道
im=cv2.imread(file,cv2.IMREAD_UNCHANGED)
#输出图片信息(宽,长,通道数),RGBA通道数为4
print(im.shape)
#分别接受图片的四个通道,a代表alpha
#下面三种方式效果都是一样的
b,g,r,a=cv2.split(im)
#3表示第4个通道,也可以用-1,表示倒数第一个
a=cv2.split(im)[3]
a=im[:,:,3]

#PIL的方式
im=Image.open(file)
r,g,b,a=im.split()
#用-1表示倒数第一个通道
a=im.split()[-1]

四通道图片

原图

r通道

g通道

b通道

α通道

图片合成

用alpha混合,为a.png替换一张新的背景(bg.png)

起初,筛选用关键字”图像合成“搜索到的教程,我选择用python的PIL库的Image.blend(im1,im2,0.5)方法。

im1im2是两张模式(mode,如rgb)、大小(size)和通道数都相同的图片,如果不同则需要预处理。第三个参数则是im1的透明度,取值范围为[0,1]。

根据公式

blend_img = img1 * (1 – alpha) + img2 * alpha

调整alpha的值,产生的图片又不同的效果。

运行下面的代码,alpha是提取的alpha通道图,im2是背景图。

out=Image.blend(alpha,im2,0.5)

可是得到的结果却不是想象中的那样……

可以发现上图只是两张图片整体的简单重叠,中间的玩偶甚至只有轮廓。而把alpha换成im1,得到的效果是:

有一点雏形,调节参数也无济于事。但可以发现,blend函数应该并不是解决此问题的钥匙,alpha本应该是提取出的图像、矩阵,而非一个介于0到1的数字。

在opencv中的等效方法是cv.addWeighted(),也就是改变图像权重。

上图来自课件,介绍的是图像的加法和乘法,看上去不难理解,但是具体应该怎么操作呢?

搜索关键字,alpha blending,算是找到了救星。

在这篇文章Alpha Blending using OpenCV (C++ / Python)中,作者较为详细的说明了alpha混合的相关知识(其实和我之前了解到的不差),并给出了实例代码,这才让我徒劳的搜索工作到了尽头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import cv2

# Read the images
foreground = cv2.imread("puppets.png")
background = cv2.imread("ocean.png")
alpha = cv2.imread("puppets_alpha.png")

# Convert uint8 to float
foreground = foreground.astype(float)
background = background.astype(float)

# Normalize the alpha mask to keep intensity between 0 and 1
alpha = alpha.astype(float)/255

# Multiply the foreground with the alpha matte
foreground = cv2.multiply(alpha, foreground)

# Multiply the background with ( 1 - alpha )
background = cv2.multiply(1.0 - alpha, background)

# Add the masked foreground and background.
outImage = cv2.add(foreground, background)

# Display image
cv2.imshow("outImg", outImage/255)
cv2.waitKey(0)

对上面代码做出“本土化修改”,调节图片格式和通道(都转为3通道图像),最终得出了下面结果。

OHHHHHHHH!

代码解析

按照给定的代码运行的结果符合预期,下面分析一下这些代码具体做了什么(虽然本来就有注释)。

读入的三张图片预先做了处理使之格式一致,然后代码

foreground = foreground.astype(float)

将图像数据类型从unit8 转成了float

为什么要做这样的处理?

先看一段找到的Matlab的相关说明

为了节省存储空间,matlab为图像提供了特殊的数据类型uint8(8位无符号整数),以此方式存储的图像称作8位图像。matlab读入图像的数据是uint8,而matlab中数值一般采用double型(64位)运算。
运算的时候将原图像的灰度值转换成double的作用主要是考虑计算过程中的精度的问题,double的数据是有小数点的,而uint8是0-255的整数,如果直接用uint8计算,会在计算过程中产生舍入误差,这种误差在图像的数据中是比较大的误差。

python中同理

python中展示图像时,图片数据类型应该是unit8,运算时,采用float或者np.double(将代码中的float替换为np.double效果一致)。

再看看两次乘法和一次加法操作的结果吧。

主体*Alpha

foreground = cv.multiply(alpha, foreground)

背景*(1-Alpha)

background = cv.multiply(1 - alpha, background)

主体+背景

outImage = cv.add(foreground, background)

就是前面的合成图片啦。

代码的逻辑,正应用了前面提过的公式

lmage Composite (合成):C=αF+(1-α)B

到此,实验一才算做完了。

参考资料

  1. 图像通道_百度百科
  2. 图像的通道数(channels)解释_
  3. 什么是Alpha通道?
  4. cv.imread()函数
  5. PIL.Image.blend()的使用
  6. Alpha Blending using OpenCV (C++ / Python)
  7. [Python+OpenCV(四)——像素运算]
  8. OpenCV-Python图像乘法运算cv2.multiply函数详解及像素值溢出归一化处理
  9. python中图片的float类型和uint8类型
  10. matlab图像数据类型uint8,double关系