本节实验将学习和实践下列知识点:
- Python基本语法
- PIL第三方库的使用
- Numexpr库的使用
- 图像处理相关知识
安装第三方库
$ sudo apt-get install python-imaging
$ sudo apt-get install python-numpy
$ sudo apt-get install python-numexpr
相关知识简介
照片模式
我们这里将用到RGB与RGBA;这两者的区别是RGBA有个透明通道,常见的照片格式中.png属于RGBA,而.jpg格式属于RGB模式。
RGB
RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一
numpy
Numpy是Pyhon很强大的开源数字扩展库,可以用来处理大型矩阵等等。
PIL
PIL是python与图片处理相关的第三方扩展库,可以完成图片的粘贴,拷贝,添加文字,层叠,合并,图片加强,操作像素点等等,非常强大。
两张照片层叠的两种方式
- 方式一:两张照片同一点的像素按照一定比例叠加。假设两张照片同一点的像素分别为A、B,则层叠之后该点像素点(alpha取值在0和1之间)为: Aalpha+B(1-alpha)
- 方式二:正片叠底。结果色 = 混合色 * 基色 / 255,PS中也采用这种方式; 正片叠底的特点: 明度变化:混合色不会大于255,故结果色一定小于1,混合模式之后必定比基色暗。0为黑色,若混合两色中有黑色,混合之后必定是黑色。若有白色,则混合色为另外一色的原色。故正片叠底可以改变非黑即白,处于灰度区间的明度,变黑。可以采用操作像素点,提高像素点的亮度。
详细代码
常量设置
现在开始写代码,首先便是导入相关文件,如下图所示:
from __future__ import division
import PIL
import Image
import numpy
import os
import random
import time
import ImageFont, ImageDraw
STAG = time.time()
# W_num: 一行放多少张照片
# H_num: 一列放多少张照片
# W_size: 照片宽为多少
# H_size: 照片高为多少
# root: 脚本的根目录
root=""
W_num =15
H_num = 15
W_size = 640
H_size = 360
- 这里,我们导入PIL,numexpr,Image模块等。
- 同时设置了几个变量,注释详细介绍了每个常量的意思。
获取所有照片信息
# name: getAllPhotos
# todo: 获得所有照片的路径
def getAllPhotos():
STA = time.time()
root = os.getcwd() + "/"
src = root+"/photos/"
for i in os.listdir(src):
if os.path.splitext(src+i)[-1] == ".jpg" or os.path.splitext(src+i)[-1] == ".png":
aval.append(src+i)
print "getAllPhotos Func Time %s"%(time.time()-STA)
伸缩图片
# name: transfer
# todo: 将照片转为一样的大小
def transfer(img_path, dst_width,dst_height):
STA = time.time()
im = Image.open(img_path)
if im.mode != "RGBA":
im = im.convert("RGBA")
s_w,s_h = im.size
if s_w < s_h:
im = im.rotate(90)
#if dst_width*0.1/s_w > dst_height*0.1/s_h:
# ratio = dst_width*0.1/s_w
#else:
# ratio = dst_height*0.1/s_h
resized_img = im.resize((dst_width, dst_height), Image.ANTIALIAS)
resized_img = resized_img.crop((0,0,dst_width,dst_height))
print "transfer Func Time %s"%(time.time()-STA)
return resized_img
- 由于每张照片的大小是不一样的,这里主要是将照片转为一样的大小。
- 由于每张照片的模式既有RGB,也有RGBA,我们直接利用PIL中的convert方法,将所有照片的模式都转为RGBA。然后在调用resize方法,将照片放缩到一样的大小。
- 这里要着重介绍一下numpy.array方法,numpy.array可以创建一个数组,numpy.array(resized_img)[:dst_height, :dst_width]是讲resized_img转换为了一个(dst_height, dst_width)的两维数组,每个数组元素是有四个常数值的列表。打印出来如下图所示:
数百张照片拼接,并且将拼接后的照片与另外一张照片进行层叠(past3.py)
# name: createNevImg
# todo: 创造一张新的图片,并保存
def createNevImg():
iW_size = W_num * W_size
iH_size = H_num * H_size
I = numpy.array(transfer(root+"lyf.jpg", iW_size, iH_size))
I = numexpr.evaluate("""I*(1-alpha)""")
for i in range(W_num):
for j in range(H_num):
SH = I[(j*H_size):((j+1)*H_size), (i*W_size):((i+1)*W_size)]
STA = time.time()
DA = transfer(random.choice(aval), W_size, H_size)
print "Cal Func Time %s"%(time.time()-STA)
res = numexpr.evaluate("""SH+DA*alpha""")
I[(j*H_size):((j+1)*H_size), (i*W_size):((i+1)*W_size)] = res
- 这部分是本项目的核心,着重介绍一下这里。
- root+’lyf.jpg’是要与拼接之后的照片进行层叠的照片;并利用numpy.array的方法将其转换成矩阵I; 注意:I的大小为(W_numW_size, H_numH_size),是第7步伸缩照片生成矩阵的W_num*H_num倍
- 第三行代码是调用numexpr.evaluate方法,将矩阵中每个元素都乘以(1-alpha)。
- 接下来的双重遍历便是具体拼接的实现。
- 由于矩阵I的规模是第六步伸缩照片生成矩阵的W_num*H_num倍,所以我们从左向右,从上向下依次取(W_size, H_size)大小的矩阵SH
- DA是调用第7步方法生成的矩阵
- 然后计算SH+DA*alpha,并将结果放回SH在I矩阵中位置。这里是讲两张照片中相同一点的像素分别乘以(1-alpha)、alpha,然后相加,如此两个照片便层叠在一块。alpha的取值可以自己设置,这里设置的是0.5。不难看出,这里选择的层叠的方法是方式一,将两张照片同一点的像素按照一定比例想加,这里选择的是alpha=0.5。
- 调用fromarray方法将矩阵I转为图片对象,并保存为createNevImage_0.5_past3.jpg,照片如下图所示。
数百张照片拼接,并且将拼接之后的照片与另外一张照片层叠(past.py)
# name: createNevImg
# todo: 创建一张新的照片并保存
def createNevImg():
STAA = time.time()
iW_size = W_num * W_size
iH_size = H_num * H_size
print root
I = numpy.array(transfer(root+"lyf.jpg", iW_size, iH_size)) * 1.0
for i in range(W_num):
for j in range(H_num):
s = random.choice(aval)
res = I[ j*H_size:(j+1)*H_size, i*W_size:(i+1)*W_size] * numpy.array(transfer(s, W_size, H_size))/255
I[ j*H_size:(j+1)*H_size, i*W_size:(i+1)*W_size] = res
img = Image.fromarray(I.astype(numpy.uint8))
img = img.point(lambda i : i * 1.5)
img.save("createNevImg_past.jpg")
print "createNevImg Func time %s"%(time.time()-STAA)
我们看到这里代码与上面的代码是很相似的,只是I矩阵的计算方法是不同的。这里要简单介绍一下I矩阵的计算方法。
- 这里采用是两照片层叠的方式二正片叠底,利用公式: 结果色 = 混合色 * 基色 / 255,至此代码就很明了了
- 利用PIL库以对每个像素点进行操作。 img.point(lambda i : i * 1.5) 将每个像素点的亮度(不知道有没有更专业的词)增大50%
- 保存照片为crateNevImg_past.jpg
旋转照片
# name: newRotateImage
# todo: 将createnevimg中得到的照片旋转,粘贴到另外一张照片中
def newRotateImage():
imName = "createNevImg_past.jpg"
print "正在将图片旋转中..."
STA = time.time()
im = Image.open(imName)
im2 = Image.new("RGBA", (W_size * int(W_num + 1), H_size * (H_num + 4)))
im2.paste(im, (int(0.5 * W_size), int(0.8 * H_size)))
im2 = im2.rotate(359)
im2.save("newRotateImage_past.jpg")
print "newRotateImage Func Time %s"%(time.time()-STA)
- mName是获取前一步中保存的照片名。
- 并调用Image.open打开照片,并赋给图片对象im。
- 第五行是调用Image.new来创建一张新的照片im2,第一个参数是照片模式,第二个参数是照片的宽高,用元组表示。
- 然后调用paste方法将im放到第二个图片对象im2中,第二个参数是放置的中心点。
- 调用rotate方法,旋转照片
- 调用save方法,保存照片为newRoatateImage_o.5_past3.jpg,具体照片效果如下图所示:
照片中添加文字
# name: writetoimage
# todo: 在图片中写祝福语
def writeToImage():
print "正在向图片中添加祝福语..."
STA = time.time()
img = Image.open("newRotateImage_past.jpg")
font = ImageFont.truetype('xindexingcao57.ttf', 600)
draw = ImageDraw.Draw(img)
draw.ink = 21 + 118*256 + 65*256*256
# draw.text((0,H_size * 6),unicode("happy every day",'utf-8'),(0,0,0),font=font)
tHeight = H_num + 1
draw.text((W_size * 0.5, H_size * tHeight), "happy life written by python", font = font)
img.save("final_past.jpg")
print "writeToImage Func Time %s"%(time.time()-STA)
- PIL功能很强大,可以向图片中添加文字
- font = ImageFont.truetype: 导入字体 ,第一个参数是字体文件,第二个参数是字体的大小
- draw = ImageDraw(img): 生成画笔,参数为要再图片中写字的图片对象。
- draw.ink = R + G256 + B256*256: 来设置画笔的颜色
- draw.text: 向图片对象中写字,第一个参数为字体的位置,用元组表示,第二参数是内容,第三个参数是字体。
- 最终利用save方法讲图片保存为final_0.5_past3.jpg。到此,工作基本完成了。
相关说明
- alpha:这个参数决定着层叠的效果,可以自由的改动这个参数,观察照片的变化,取值范围为(0–1)。
- 这里对PIL,numpy介绍并不详细,应该在阅读相关文档。相关文档
- 这里拼接的196张照片都是风景照,当把风景照换成人物照时,效果更好,最终的照片也更为漂亮。
- 由于采用了numpy,numexpr等库,加快了运算速度,past.py处理196张照片拼接、选择、层叠、添加文字,总共大概需要55秒左右,past3.py所需时间大概需要 23秒左右。
- 层叠的第一张照片很重要(即这里的lyf.jpg),可以使用PS修改一下尺寸等等。
- 分别运行past.py,past3.py,可以得到6张照片,后缀名分别为.past和.past3,可以进行比较,来体会两种层叠方式的不同
参考链接:多张图片拼接与层叠