情侣头像自动生成
又到了一年一度的情人节了,这次献上一个情侣头像自动生成的工具,效果见上图(示例是本人qq头像,可以改为情侣的头像or照片)
代码本身在去年七夕的时候就完成了,之后因为忙于学习和外包工作并没有把它放到博客上。恰好这次过年有段空闲的时间,于是便有了本文。
背景
起因是因为去年七夕时一个群里讲要做个自动生成的头像送给女朋友(话说这真的不会被打吗。。。)
正好当时特别想更换一个动态qq头像,于是花了一天做了上面的工具。然而最后因为qq的自定义动态头像存在问题,于是计划到最后一步便中断了,只剩下了图示的部分。
【yysy,qq动态图像是真的苟】
原理部分讲解
【代码使用python】
首先,先放上技术路线,并附上一些基础和重要的问题,以供思考。当然肯定也有其他方法解决这些问题,这部分更多的是在展示我当时是如何想的。
图像的预处理部分使用cv2库,RGB图转换为二值图方法很多,我简单设置了一个阈值,效果尚可。
然后就是一个重要的部分,如何让图像运动。实际上,方法是非常多的。我们知道,无论是视频或者是gif动图,本质上都是一群有规律有顺序的图片(类似于小时候看的小人书),反过来想,我们按照生成的顺序获得一些图片之后自然也就能合成出视频或者gif动图了。
之后事情也就很容易想了,将每一步的运动图片保存下来,然后使用imageio库将其转化为动图就可以了。
想明白图片如何运动之后,之后就是运动的算法和仿真的问题了。
运动细节
以下是几个运动细节方面的问题问题
首先简单核定一下运动流程,本代码使用的以下的流程
流程
对于每个点
- 首先确定每个点的运动目标(就近原则且不重合)
- 确定运动目标之后确定本次运动的方向
- 运动一步
- 判断是否到运动目标,如果到运动目标,则不再运动,若未到目标,则回到步骤3
1.整体运动方向
我设置了每个像素点只有上下左右四个方向(当然,你也可以设置8个方向,不过运动轨迹的仿真会更加复杂一点,当然,也会更加逼真)
2.运动目标的确认
这里,我们更加深入地思考一下这个问题,我们知道此时图片已经读取成了M x N x 3的矩阵了。
一张图片有上万,上十万的像素点,当然,因为图片上只有黑白像素点,因此我们要从整体上考虑黑像素点的部分就可以(剩下的就是白像素点)
简单思考之后我们知道,需要存储下每一步黑白点的位置,以及所有目标的运动位置,更进一步,要将黑白点的位置和所有目标的运动位置设置到一个矩阵,这涉及到像素点不同状态间的转换。
设置一个M x N矩阵,0代表白色像素点,而1代表黑色像素点,我们明显知道,当运动的初始状态应该是一种混乱的状态,统计出黑点和白点的数量,简单使用动态随机线段的方式抛出随机数。
(简单解释以下这种方法,假设黑点的数量为a,白点的数量为b,按照从上到下,从左到右的顺序按概率抛出顺序,并且不断更新概率)
再提出新的状态3和4,状态3就是目标的初始状态,状态4代表指定黑点的到达目标时候的状态
状态变化图如下
(这里会更新成所有状态3的点都是白点,点初始化后会先进行状态更新)
根据流程图,我们需要有三件事,
1.指定每个黑点的目标
2.将不同的状态和叠加态转换成黑白像素点
3.更新状态直到结束或者一定的阈值
第一件事,指定每个黑点的目标,比较简单的想法是直接去找临近点,但是这样的话存在一个问题,上万甚至上十万的像素点的之间的运算轻轻松松会破亿级别,所以算法需要优化。
*注:这里的距离简化计算使用横纵坐标的差的绝对值的和 *
我这里使用的以下的算法:
1.统计出每个状态1和状态3(之前状态3和4初始化后会更新一次)
2.以任何一个状态1的黑点,并假设为点A,取它的相同索引的目标白点假设为点B,距离点A更近的目标点其实在A点和B点所在的矩形区域内(其实是圆形,不过使用了矩形做了简化)
如图所示
不同的状态分成刻画不同的黑白像素和叠加态的考虑需要一些更加细节的讨论,这些不再详细展示,比较简单
3.该步下运动方向的确定
运动方向的确定在该点指定的目标点确定之后就已经确定了,最简单的方法是,按照一定的顺序指定点运动完成先运动完一个方向上的差量,比如先按照y轴运动再按照x轴运动,或者是先按照x轴运动再按照y轴运动。
但是这样会有一个问题,此时点的运动过于僵硬和死板
优化
于是开始了运动轨迹上的优化,考虑只有四个方向,于是考虑使用上面使用过的方法,随机线段方向的方法,具体如下:
假设,点A和点B在X轴差a个单位,Y轴差b个单位,A向B运动,此时我们我们抛出概率a/(a+b)可能为X轴方向运动,b/(a+b)的概率可能为Y轴方向运动,设置一个0到a+b的线段,抛出的数落在0-a之间,向X轴运动,数在a到(a+b)之间,向Y轴运动
附上部分代码
def run(all_first,all_third,simulate):
#所有的点都走一步
to_delete=[]
for i in range(len(all_first)):
x_first,y_first = all_first[i]
x_third,y_third = all_third[i]
if(abs(x_first-x_third)==0 and abs(y_first-y_third)==0):
simulate[x_third][y_third] = 4
to_delete.append(i)
# 如果是只有一个方向上已经完美了
elif(abs(x_first-x_third)==0 or abs(y_first-y_third)==0):
if(abs(x_first-x_third)==0):
#仅仅移动y方向
simulate[x_first][y_first] -= 1
if (y_third - y_first) > 0:
simulate[x_first][y_first + 1] += 1
all_first[i][0] = x_first
all_first[i][1] = y_first + 1
else:
simulate[x_first][y_first - 1] += 1
all_first[i][0] = x_first
all_first[i][1] = y_first - 1
elif(abs(y_first-y_third)==0):
#仅仅移动x方向
simulate[x_first][y_first] -= 1
if (x_third - x_first) > 0:
simulate[x_first + 1][y_first] += 1
all_first[i][0] = x_first + 1
all_first[i][1] = y_first
else:
simulate[x_first - 1][y_first] += 1
all_first[i][0] = x_first - 1
all_first[i][1] = y_first
else:
#x方向和y方向都会有运动距离
Efficient_motion = min(abs(y_first-y_third),abs(x_first-x_third))
motion = max(abs(y_first-y_third),abs(x_first-x_third))-Efficient_motion
#motion有可能为0
x_direction = int((x_third-x_first)/abs(x_third-x_first))
y_direction = int((y_third-y_first)/abs(y_third-y_first))
simulate[x_first][y_first] -= 1
import random
choice = random.randint(1,max(abs(y_first-y_third),abs(x_first-x_third)))
if(choice<=Efficient_motion):
simulate[x_first + x_direction][y_first+y_direction] += 1
all_first[i][0] = x_first + x_direction
all_first[i][1] = y_first + y_direction
else:
#直线运动
if(abs(x_third-x_first)>abs(y_third-y_first)):
#走x
simulate[x_first + x_direction][y_first] += 1
all_first[i][0] = x_first + x_direction
all_first[i][1] = y_first
else:
#走y
simulate[x_first][y_first + y_direction] += 1
all_first[i][0] = x_first
all_first[i][1] = y_first + y_direction
return len(to_delete)
视频生成
当以上的部分确定之后,剩下的部分就显比较简单了,生成一系列的图片,并按照顺序生成gif动图或视频(这里可以过滤一些图片,如果你认为一步一张图片的步幅过短的话,也可以多设计几个步幅)
最后
如果想设计情侣的生成头像or和我探讨深入的技术细节,欢迎关注微信公众号:积淀智慧