千图成像!祝可爱的小伙伴们圣诞快乐

千图成像!祝可爱的小伙伴们圣诞快乐

前言

当走上街头时能隐约察觉到多了些许“红白绿”的色彩,那是:圣诞老人、雪花️、圣诞树。弥红灯闪烁,各类促销活动海报令郎满目。没有错,圣诞节快来了~

从写博客开始,一路上得到了不少小伙伴的支持和鼓励,而这周的总排名不偏不倚,恰好100名整。我想,这大概是某种巧合,更确切来说应该是一种激励。近些日,严酷寒冬似乎柔情了许多,有了那么几日的柔柔暖阳,而我希望可以把这份温暖也带给大家。

我将用粉丝的头像拼接出一幅圣诞树,正所谓,“千图成像,集万千之美”。

一、圣诞树

《千图成像!祝可爱的小伙伴们圣诞快乐》
该图的像素为6200×10250,将其局部放大后的效果为:
《千图成像!祝可爱的小伙伴们圣诞快乐》
今天所用到的代码是比较通用的,只需要替换背景图和图片库即可打造属于你自己的专属图片。详情分析,客观您往下瞅瞅~

帅小伙!圣诞来袭,还不赶快用来表白一波,还在犹豫啥呢?

二、代码分析

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

该列表的每个元素是一个字典,包括了pathRGB,分别表示图片的路径以及图片的平均颜色,如下:
《千图成像!祝可爱的小伙伴们圣诞快乐》
在得到了图片的“平均颜色”后,便是比较背景图的像素点与它的相似性,这里采用了欧式距离:

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)

函数的输入有bgImgimgDirNM,它们分别表示:

参数含义
bgImg背景图的地址
imgDir图片库的目录
N背景图的压缩比率
M填充的图片大小

注:imgDir最后需要有目录连接符/,否则会报错;N是为了将背景图的尺寸进行调整压缩,否则容易因为像素点太多而执行缓慢。

最后,代码一Run,小图在手,简单的快乐,美滋滋~

写在最后

由于kimol君太想赶在圣诞前将这份礼物送出,所以代码以及文字略显仓促,还望大家见谅。

客观来说,该代码的执行效率并不高,特别是当N设置得较小(即背景图片像素点很多)或者图片库中的图片很多时。针对这个问题,也有很多优化方法,例如采用图片向量的方式对图片进行编码,进而比较等等。感兴趣的小伙伴可以参考这篇文章

最后,提前祝最可爱的小伙伴们圣诞快乐,愿我们聚于一图,焕尽万丈芒!另祝即将奔赴考研战场的小伙伴考的都会,会的都考!

我是kimol君,咋们下次再会~
《千图成像!祝可爱的小伙伴们圣诞快乐》
创作不易,大侠请留步… 动起可爱的双手,来个赞再走呗 (๑◕ܫ←๑)

点赞