图片扭曲变形

西游记中唐僧变老虎,在当时各项技术并不发达的时候是相当难实现的,而现在,利用 Python,我们自己就可以实现这一动画,先看效果。

 

 

下面就以此来讲解其思路:

首先,准备两张图片,分别是最开始的金刚狼图片和最终的狼图片(两张图片大小要是一样的),并将图片手动的进行网格划分(这部分可以通过深度学习来自动化实现,以后学会了再更新~),需要注意的是这两张图片的网格大小要是一样的,并且每个格点所处的相对位置要一样,比如鼻尖对鼻尖,眉心对眉心,眼珠子对眼珠子······

         

 

然后获取每个格点的像素坐标(我是使用MATLAB手动获取的,获取方法很多,只要能得到坐标就行)。下面是我获取的格点坐标,减一是由于 Python 计数是从零开始的。

Mt = np.array([[[1,1],[143,1],[200,1],[243,1],[270,1],[310,1],[372,1],[440,1],[546,1]],[[1,25],[142,24],[204,118],[249,160],[283,162],[328,171],[375,93],[430,46],[546,55]],[[1,129],[85,130],[192,217],[244,229],[280,229],[327,229],[376,253],[472,150],[546,148]],[[1,259],[90,262],[153,255],[226,261],[270,262],[325,267],[394,285],[454,295],[546,285]],[[1,317],[60,316],[110,318],[195,316],[261,319],[337,319],[407,330],[456,331],[546,333]],[[1,383],[47,382],[101,382],[182,349],[263,354],[346,355],[431,378],[480,381],[546,380]],[[1,453],[53,454],[82,454],[183,511],[259,550],[338,514],[424,437],[476,433],[546,435]],[[1,537],[57,540],[113,540],[167,550],[259,594],[333,570],[397,538],[470,534],[546,536]],[[1,608],[57,607],[120,607],[172,602],[245,631],[334,608],[394,606],[467,609],[546,605]],[[1,660],[58,660],[126,660],[172,660],[245,660],[334,660],[394,660],[467,660],[546,660]]]) - 1
Ms = np.array([[[1,1],[72,1],[125,1],[205,1],[280,1],[358,1],[432,1],[484,1],[546,1]],[[1,31],[72,30],[125,66],[205,36],[280,36],[358,47],[432,72],[484,50],[546,51]],[[1,107],[46,106],[137,158],[207,155],[280,171],[350,174],[417,184],[486,133],[546,134]],[[1,187],[51,186],[108,207],[204,228],[274,245],[342,247],[414,252],[475,229],[546,231]],[[1,290],[60,289],[111,291],[195,307],[269,320],[338,318],[417,317],[459,326],[546,323]],[[1,354],[58,352],[106,353],[188,344],[266,356],[338,347],[419,371],[455,374],[546,377]],[[1,406],[72,404],[112,406],[221,443],[262,460],[304,440],[403,416],[445,420],[546,424]],[[1,492],[67,490],[113,491],[196,497],[253,503],[313,502],[398,500],[455,499],[546,497]],[[1,591],[67,589],[122,588],[173,580],[241,598],[316,590],[380,579],[461,560],[546,560]],[[1,660],[67,660],[122,660],[173,660],[241,660],[316,660],[380,660],[461,660],[546,660]]]) - 1
View Code

我在中间合成了28张图片,所以动画一共是30张图片,每张图片都对应一个网格,所以需要先计算出中间的网格,再通过两个网格和其中一个网格对应的图片合成另一个网格对应的图片。中间网格的计算我就直接采用了线性插值。下面的代码便是对图片进行变形的函数,其中 S 是图片,Ms、Mt 是网格,其中调用的 inpolygon 函数可见这篇博客。

# 通过原始图片S及其网格矩阵Ms和目标图片的网格矩阵Mt生成目标图片T
def deformation(Ms, Mt, S):
    M, N, _ = Ms.shape
    width, height = S.size
    T = Image.new("RGB", (width, height), "red")
    for i in range(M-1):
        for j in range(N-1):
            xv = np.array([Ms[i][j][0], Ms[i][j+1][0], Ms[i+1][j+1][0], Ms[i+1][j][0]])
            yv = np.array([Ms[i][j][1], Ms[i][j+1][1], Ms[i+1][j+1][1], Ms[i+1][j][1]])
            new_xv = np.array([Mt[i][j][0], Mt[i][j+1][0], Mt[i+1][j+1][0], Mt[i+1][j][0]])
            new_yv = np.array([Mt[i][j][1], Mt[i][j+1][1], Mt[i+1][j+1][1], Mt[i+1][j][1]])
            max_x = np.max(xv)
            min_x = np.min(xv)
            max_y = np.max(yv)
            min_y = np.min(yv)
            a, b, c, d = getVar(xv, yv, new_xv)
            e, f, g, h = getVar(xv, yv, new_yv)
            for y in range(min_y, max_y+1):
                for x in range(min_x, max_x+1):
                    value = inpolygon(x, y, xv, yv)
                    if value:
                        new_x = int(a * x + b * y + c * x * y + d)
                        new_y = int(e * x + f * y + g * x * y + h)
                        T.putpixel((new_x, new_y), S.getpixel((x, y)))
    for y in range(height):
        for x in range(width):
            if(T.getpixel((x, y)) == (255, 0, 0)):
                T.putpixel((x, y), S.getpixel((x, y)))
    return T
View Code

分别将金刚狼图片和狼图片传入上述函数中,得到的结果再传入,循环29次,便可的到如下的变形图片。

   

 

从图中可以看出,经过变形后的图片,金刚狼的形状在不断向狼的形状变,而狼的形状也在不断向金刚狼的形状变。

最后一步,就是将二者融合了。而融合的过程也非常简单,将这两组30张图片以不同权值取加权数就行。比如第一帧金刚狼图片权值为1,狼图片权值为0,第二帧则金刚狼图片权值取29/30,而狼图片权值取1/30,依次类推。最后得到的图片如下:

 

将这30帧图片合成 gif 就得到了一开始展示的动图了。

对于这个程序,有一些优化和改进的地方:

  • 每个像素判断是否在四边形内非常耗时,这个程序运行时间的瓶颈也在这,以后有优化方法再继续改;
  • 将图片改成 PNG,去除背景,只留下人物主体,就会特别有意思;
  • 将格点的获取用深度学习替换,就可以自动处理,大量减轻人工预处理。

附上完整代码:

import numpy as np 
from PIL import Image
from scipy.linalg import solve

# 判断点是否在四边形内部,在四边形内部返回1,否则返回0
# x、y为点的坐标,xv为四边形的x坐标构成的向量,yv为四边形的y坐标构成的向量
def inpolygon(x, y, xv, yv):
    z1 = (xv[1] - xv[0]) * (y - yv[0]) - (x - xv[0]) * (yv[1] - yv[0])
    z2 = (xv[2] - xv[1]) * (y - yv[1]) - (x - xv[1]) * (yv[2] - yv[1])
    z3 = (xv[3] - xv[2]) * (y - yv[2]) - (x - xv[2]) * (yv[3] - yv[2])
    z4 = (xv[0] - xv[3]) * (y - yv[3]) - (x - xv[3]) * (yv[0] - yv[3])
    if(z1 * z2 > 0 and z3 * z4 > 0 and z1 * z3 > 0): # z1, z2, z3, z4同号
        return 1 
    return 0

# 解双线性方程组 a*x+b*y+c*x*y+d=new_x
def getVar(xv, yv, new_xv):
    a = np.array([[xv[0], yv[0], xv[0]*yv[0], 1], [xv[1], yv[1], xv[1]*yv[1], 1], [xv[2], yv[2], xv[2]*yv[2], 1], [xv[3], yv[3], xv[3]*yv[3], 1]])
    b = np.array([new_xv[0], new_xv[1], new_xv[2], new_xv[3]])
    x = solve(a, b)
    return x

# 通过原始图片S及其网格矩阵Ms和目标图片的网格矩阵Mt生成目标图片T
def deformation(Ms, Mt, S):
    M, N, _ = Ms.shape
    width, height = S.size
    T = Image.new("RGB", (width, height), "red")
    for i in range(M-1):
        for j in range(N-1):
            xv = np.array([Ms[i][j][0], Ms[i][j+1][0], Ms[i+1][j+1][0], Ms[i+1][j][0]])
            yv = np.array([Ms[i][j][1], Ms[i][j+1][1], Ms[i+1][j+1][1], Ms[i+1][j][1]])
            new_xv = np.array([Mt[i][j][0], Mt[i][j+1][0], Mt[i+1][j+1][0], Mt[i+1][j][0]])
            new_yv = np.array([Mt[i][j][1], Mt[i][j+1][1], Mt[i+1][j+1][1], Mt[i+1][j][1]])
            max_x = np.max(xv)
            min_x = np.min(xv)
            max_y = np.max(yv)
            min_y = np.min(yv)
            a, b, c, d = getVar(xv, yv, new_xv)
            e, f, g, h = getVar(xv, yv, new_yv)
            for y in range(min_y, max_y+1):
                for x in range(min_x, max_x+1):
                    value = inpolygon(x, y, xv, yv)
                    if value:
                        new_x = int(a * x + b * y + c * x * y + d)
                        new_y = int(e * x + f * y + g * x * y + h)
                        T.putpixel((new_x, new_y), S.getpixel((x, y)))
    for y in range(height):
        for x in range(width):
            if(T.getpixel((x, y)) == (255, 0, 0)):
                T.putpixel((x, y), S.getpixel((x, y)))
    return T

def getFrame(S, Ms, Mt):
    frames = []
    frames.append(S)
    frame_S = S
    frame_T = S
    frame_Ms = Ms 
    frame_Mt = Mt
    for i in range(29):
        frame_Mt = ((i + 1) * (Mt - Ms) / 29 + Ms).astype(int)
        print('正在处理第%d/30帧' %(i+2), end='\r')
        frame_T = deformation(frame_Ms, frame_Mt, frame_S)
        frames.append(frame_T)
        frame_S = frame_T
        frame_Ms = frame_Mt 
    return frames

# 将多张图片合成gif动画
def main():
    S = Image.open("source.jpg", "r")
    T = Image.open("target.jpg", "r")
    Mt = np.array([[[1,1],[143,1],[200,1],[243,1],[270,1],[310,1],[372,1],[440,1],[546,1]],[[1,25],[142,24],[204,118],[249,160],[283,162],[328,171],[375,93],[430,46],[546,55]],[[1,129],[85,130],[192,217],[244,229],[280,229],[327,229],[376,253],[472,150],[546,148]],[[1,259],[90,262],[153,255],[226,261],[270,262],[325,267],[394,285],[454,295],[546,285]],[[1,317],[60,316],[110,318],[195,316],[261,319],[337,319],[407,330],[456,331],[546,333]],[[1,383],[47,382],[101,382],[182,349],[263,354],[346,355],[431,378],[480,381],[546,380]],[[1,453],[53,454],[82,454],[183,511],[259,550],[338,514],[424,437],[476,433],[546,435]],[[1,537],[57,540],[113,540],[167,550],[259,594],[333,570],[397,538],[470,534],[546,536]],[[1,608],[57,607],[120,607],[172,602],[245,631],[334,608],[394,606],[467,609],[546,605]],[[1,660],[58,660],[126,660],[172,660],[245,660],[334,660],[394,660],[467,660],[546,660]]]) - 1
    Ms = np.array([[[1,1],[72,1],[125,1],[205,1],[280,1],[358,1],[432,1],[484,1],[546,1]],[[1,31],[72,30],[125,66],[205,36],[280,36],[358,47],[432,72],[484,50],[546,51]],[[1,107],[46,106],[137,158],[207,155],[280,171],[350,174],[417,184],[486,133],[546,134]],[[1,187],[51,186],[108,207],[204,228],[274,245],[342,247],[414,252],[475,229],[546,231]],[[1,290],[60,289],[111,291],[195,307],[269,320],[338,318],[417,317],[459,326],[546,323]],[[1,354],[58,352],[106,353],[188,344],[266,356],[338,347],[419,371],[455,374],[546,377]],[[1,406],[72,404],[112,406],[221,443],[262,460],[304,440],[403,416],[445,420],[546,424]],[[1,492],[67,490],[113,491],[196,497],[253,503],[313,502],[398,500],[455,499],[546,497]],[[1,591],[67,589],[122,588],[173,580],[241,598],[316,590],[380,579],[461,560],[546,560]],[[1,660],[67,660],[122,660],[173,660],[241,660],[316,660],[380,660],[461,660],[546,660]]]) - 1
    print('开始对图像进行变形')
    frame_source = getFrame(S, Ms, Mt)
    print('\n开始对最终图片进行变形')
    frame_target = getFrame(T, Mt, Ms)
    gif = []
    width, height = S.size
    print('\n开始合成过渡图片')
    for i in range(30):
        print('正在处理第%d/30帧' %(i+1), end='\r')
        frame = Image.new("RGB", (width, height), "red")
        # frame_source[i].save('.\\result\\mid_image\\source_%d.jpg' %(i+1))
        # frame_target[29-i].save('.\\result\\mid_image\\target_%d.jpg' %(i+1))
        for j in range(height):
            for k in range(width):
                Tr, Tg, Tb = frame_target[29 - i].getpixel((k, j))
                Sr, Sg, Sb = frame_source[i].getpixel((k, j))
                r = int((i * Tr + (29 - i) * Sr) / 29)
                g = int((i * Tg + (29 - i) * Sg) / 29)
                b = int((i * Tb + (29 - i) * Sb) / 29)
                frame.putpixel((k, j), (r, g, b))
        # frame.save('.\\result\\mid_image\\mid_%d.jpg' %(i+1))
        gif.append(frame)
    print('\n正在保存动画')
    gif[0].save(".\\result\\result.gif", save_all = True, append_images = gif, duration = 0.1)
    print('动画已保存到result文件夹中')

if __name__ == '__main__':
    print('该程序大约需要6分钟完成,请耐心等候 :)')
    main()
View Code
posted @ 2019-11-07 14:09  强扭的瓜才甜  阅读(838)  评论(0编辑  收藏  举报