前言
当走上街头时能隐约察觉到多了些许“红白绿”的色彩,那是:圣诞老人🎅、雪花❄️、圣诞树🎄。弥红灯闪烁,各类促销活动海报令郎满目。没有错,圣诞节快来了~
从写博客开始,一路上得到了不少小伙伴的支持和鼓励,而这周的总排名不偏不倚,恰好100名整。我想,这大概是某种巧合,更确切来说应该是一种激励。近些日,严酷寒冬似乎柔情了许多,有了那么几日的柔柔暖阳,而我希望可以把这份温暖也带给大家。
我将用粉丝的头像拼接出一幅圣诞树,正所谓,“千图成像,集万千之美”。
原文地址:千图成像!祝可爱的小伙伴们圣诞快乐_kimol君的博客-CSDN博客
一、🎄圣诞树🎄
图片上传失败
重试
该图的像素为6200x10250,将其局部放大后的效果为:
今天所用到的代码是比较通用的,只需要替换背景图和图片库即可打造属于你自己的专属图片。详情分析,客观您往下瞅瞅~
帅小伙!圣诞来袭,还不赶快用来表白一波,还在犹豫啥呢?
二、代码分析
1.头像爬取
通过爬虫将粉丝的头像下载到本地,具体分析大家可以参考我之前的博客【前方高能!看小伙是怎么表白粉丝的】,完整代码如下:
# -*- coding: utf-8 -*- """ Created on Sat Oct 17 12:27:33 2020 @author: kimol_love """ import requests def get_fansInfo(): ''' 获取粉丝相关信息 ''' url = 'https://me.csdn.net/api/relation/index?pageno=%d&pagesize=%d&relation_type=fans' # 接口地址 cookies = {} # CSDN登陆后的cookies headers = { # 请求头 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'Referer': 'https://i.csdn.net/', 'Origin': 'https://i.csdn.net', 'Connection': 'keep-alive', 'TE': 'Trailers', } # 获取粉丝总数 res = requests.get(url%(1,10),headers=headers,cookies=cookies) res_json = res.json() N_fans = res_json['data']['data_all'] print('一共有%d个粉丝'%N_fans) # 获取全部粉丝数据 res = requests.get(url%(1,N_fans),headers=headers,cookies=cookies) res_json = res.json() return res_json def download_avatar(username,url): ''' 下载用户头像 ''' savePath = './avatars' # 头像存储目录 res = requests.get(url) with open('%s/%s.jpg'%(savePath,username),'wb') as f: f.write(res.content) if __name__ == '__main__': fans = get_fansInfo() for f in fans['data']['list']: username = f['fans'] # 用户名 url = f['avatar'] # 头像地址 download_avatar(username,url) print('用户"%s"头像下载完成!'%username)
将头像下载完毕并去重,结果如下:
一共有2796张非重复图片,有了它们,我们便可以开始——搞事情!
2.千图成像
所谓千图成像就是用很多张图片拼接成一张完整的图片,它需要两个部分:一张背景图,一个图片库。根据背景图的结构用图片库中的图片来进行拼接,最终形成新的图片。
一种最简单直观的思路便是:遍历背景图中的每个像素点,并用图库中与之颜色最相近的图片粘贴在这个位置。
因此,我们首先需要计算每个图片的“平均颜色”,即图片像素点(R,G,B)的平均值,代码如下:
def compute_mean(imgPath): ''' 计算平均(R,G,B) ''' img = Image.open(imgPath) img = img.convert('RGB') imgArray = np.array(img) R = np.mean(imgArray[:,:,0]) G = np.mean(imgArray[:,:,1]) B = np.mean(imgArray[:,:,2]) return (R,G,B)
遍历图片库中的每张图片,并得到它们的平均距离,生成一个图片列表,以便后续的使用:
def get_imgList(imgDir): ''' 获取图片列表(图片目录及平均颜色) ''' imgList = [] for imgName in os.listdir(imgDir): path = imgDir+imgName color = compute_mean(path) imgList.append({'path':path,'RGB':color}) return imgList
该列表的每个元素是一个字典,包括了path和RGB,分别表示图片的路径以及图片的平均颜色,如下:
在得到了图片的“平均颜色”后,便是比较背景图的像素点与它的相似性,这里采用了欧式距离:
def compute_distance(color1, color2): ''' 计算两张图的颜色差 ''' dis = 0 for i in range(len(color1)): dis += (color1[i]-color2[i])**2 dis = dis**0.5 return dis
剩下的便是遍历背景图,并进行比较填充,代码如下:
def create_image(bgImg,imgDir,N=10,M=50): ''' 根据背景图,用头像填充出新图 bgImg:背景图地址 imgDir:头像目录 N:背景图缩放的倍率 M:头像的大小(MxM) ''' # 获取图片列表 imgList = get_imgList(imgDir) # 读取背景图 bg = Image.open(bgImg) bg = bg.resize((bg.size[0]//N,bg.size[1]//N)) # 缩放 bgArray = np.array(bg) width = bg.size[0]*M # 新生成图片的宽度 height = bg.size[1]*M # 新生成图片的高度 # 创建空白的新图 newImg = Image.new('RGB',(width,height)) # 循环填充图 for x in range(bgArray.shape[0]): for y in range(bgArray.shape[1]): ## 找到距离最小的图片 minDis = 10000 index = 0 for img in imgList: dis = compute_distance(img['RGB'],bgArray[x][y]) if dis < minDis: index = img['path'] minDis = dis ## 填充 tempImg = Image.open(index) tempImg = tempImg.resize((M,M)) newImg.paste(tempImg,(y*M,x*M)) print('(%d, %d)'%(x,y)) # 保存图片 newImg.save('千图成像.jpg')
调用它:
create_image('bg.jpg','./avatars(dr)/',5,50)
函数的输入有bgImg、imgDir、N、M,它们分别表示:
参数 | 含义 |
---|
注:imgDir最后需要有目录连接符/,否则会报错;N是为了将背景图的尺寸进行调整压缩,否则容易因为像素点太多而执行缓慢。
最后,代码一Run,小图在手,简单的快乐,美滋滋~
写在最后
由于kimol君太想赶在圣诞前将这份礼物送出,所以代码以及文字略显仓促,还望大家见谅。
客观来说,该代码的执行效率并不高,特别是当N设置得较小(即背景图片像素点很多)或者图片库中的图片很多时。针对这个问题,也有很多优化方法,例如采用图片向量的方式对图片进行编码,进而比较等等。感兴趣的小伙伴可以参考这篇文章。
最后,提前祝最可爱的小伙伴们圣诞快乐🎅,愿我们聚于一图,焕尽万丈芒!另祝即将奔赴考研战场的小伙伴考的都会,会的都考!
我是kimol君,咋们下次再会~
后记
近期有很多朋友通过私信咨询有关Python学习问题。为便于交流,点击蓝色自己加入讨论解答资源基地