多张图片拼接与层叠

Posted by jjx on September 19, 2016

本节实验将学习和实践下列知识点:

  • 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,可以进行比较,来体会两种层叠方式的不同

参考链接:多张图片拼接与层叠