使用 Python 组合 NBA 球星卡 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
ywt474000158
V2EX    Python

使用 Python 组合 NBA 球星卡

  •  
  •   ywt474000158 2017 年 3 月 24 日 2286 次点击
    这是一个创建于 3238 天前的主题,其中的信息可能已经有所发展或是发生改变。

    相信对 NBA 感兴趣的大兄弟一定不会对球星卡陌生吧,虽然不知道咱们这个圈子对 NBA 感兴趣的大兄弟多不多。但是,不感兴趣也问题不大,本文阐述的方法其实是通用的图片合成方法。

    让我们来看一张球星卡:

    这种球星卡可以划分为 5 个部分

    1. 球员动作图
    2. 球员姓名
    3. 球队 logo
    4. 底板背景图
    5. 装饰边框图

    今天我们要干的事情就是找到这 5 个素材然后用 Python 把他们组合起来,那么这个时候肯定有大兄弟会有疑问了,直接用 PS 套起来不就好了吗,讲道理这样做确实方便快捷,但是前提是你只做这一张卡,当你要为联盟大概 450 名球员制作球星卡的时候,你就需要一个脚本来帮助你完成了(对 PS 不太熟,如果 PS 也可以,可以告诉我哈)。

    这篇文章需要一点 Python 基础,完全不了解 Python 的大兄弟最好去学习一点基础知识再看。

    OK 让我们开始吧!

    准备素材

    就像开头提到的,我们需要 5 种素材。这 5 种素材我都会提供若干个给大家练手。

    上面的图片实际上只有 4 个素材,还有一个就是球员的名字了,球员的名字我们可以在组合过程中使用 ImageDraw 和 ImageFont 加载球员姓名。

    为了避免字体路径和中文乱码的问题,我还提供了一个微软雅黑的字体。

    素材可以在 这里 clone 或者下载,声明:本文所有涉及的素材和图片仅供交流学习使用。

    开始写代码

    我们的场景是为联盟中的所有球员制作球星卡,那么所有的球员自然是从数据库里面查出来的了,这里为了练习,我们可以 mock 一些数据(虽然,讲道理,波什并不能放在 SUPER 里面,但是这里只有一张装饰边框图,所以就勉为其难的和吾皇放在一个等级了)。

    mock_data = [ { 'id': 1966, 'cn_name': '勒布朗-詹姆斯', 'team_id': 5, 'category': 'SUPER' }, { 'id': 1977, 'cn_name': '克里斯-波什', 'team_id': 14, 'category': 'SUPER' } ] 

    有数据之后,我们就来遍历这些球员,找到我们需要的属性,再传入到一个组合函数中。

    def compose_all(all): for player in all: id = player['id'] # if id == 1966: if True: category = player['category'] player_img = str(id) + '.png' team_id = player['team_id'] team_img = str(team_id) + '.png' name = player['cn_name'] category = player_category.index(category) + 1 category_img = 'card_bg_' + str(category) + '.png' output_name = str(id) + '.png' print('start compose ' + str(id)) compose(player_img, name, team_img, category_img, output_name) 

    这里有个个人习惯,因为经常在服务器上写一些脚本,所有if True:那个地方就是调试用的,当一个球员调试没问题之后,注释掉,跑代码,这样可以不用再调整缩进了,不知道其他的大兄弟这个地方喜欢怎么写。

    在这里我默认会对球员分档(根据一些数据信息)

    player_category = ["SUPER", "CORE", "BLUE", "SIX", "BENCH"] 

    对应档位的装饰边框分别为card_bg_1.pngcard_bg_2.png等。 检查 5 个素材是否都拿到了:

    1. 球员动作图 -> player_img
    2. 球员姓名 -> name
    3. 球队 logo -> team_img
    4. 底板背景图 > ** None **
    5. 装饰边框图 -> card_bg_n.png (n 对应档位)

    还差一个底板背景图,因为每个球员底板背景图都一样,所以在组合函数中直接使用就好了。

    在我们去组合球星卡之前,还有一个问题需要解决,那就是我们不能保证所有素材都在同一个目录下,那么我们就需要给每个素材指定一个目录,这样我们在组合球星卡的时候就可以一马平川了。

    team_path = './logo/' player_path = './player_img/' output_path = './trading_cards/' font_file = './assets/msyh.ttf' card_decorate_path = './assets/' 

    设置好路径之后写上我们的组合函数,为了保证这个函数的正常运行,我们需要导入三个模块。

    import os import numpy as np from PIL import Image, ImageFont, ImageDraw 

    如果提示没有找到模块,请使用下面的命令进行安装

    pip install Pillow pip install numpy 

    Pillow 关于图片处理的详细文档请参考 Pillow

    下面是我们的组合函数

    def compose(player_img, name, team_logo, category_img, output_name): card_bg = card_decorate_path + 'bg.png' player_img_offset_height = 15 if not os.path.isfile(player_path + player_img): need_manual_compose.append(player_img) print(player_path + player_img + ' is not exist') return player_img = Image.open(player_path + player_img).convert('RGBA') bg_img = Image.open(card_decorate_path + category_img).convert('RGBA') card_bg_img = Image.open(card_bg).convert('RGBA') logo = Image.open(team_path + team_logo).convert('RGBA') logo = logo.resize((100,100), Image.ANTIALIAS) card_bg_img.paste(player_img, (35,player_img_offset_height), player_img) card_bg_img.paste(bg_img, (0,0), bg_img) card_bg_img.paste(logo, (95,315), logo) fOnt= ImageFont.truetype(font_file, 20) d = ImageDraw.Draw(card_bg_img) try: name = unicode(name, 'utf-8') except NameError: name = name d.text((12, 12), name, fOnt=font, fill=(255,255,255)) card_bg_img.save(output_path + output_name, quality=100) 

    有几个问题需要说明一下:

    1. 有些球星动作的素材可能找不到,那么就将找不到的球员记录下来,最后手工处理。
    2. 因为我们要使用到 alpha 通道,所以需要 convert('RGBA)
    3. 在图片 paste 之前必须保证团片和粘贴范围像素一样,不一样的话就使用 resize 函数变成一样的, Image.ANTIALIAS 参数的作用是抗锯齿,这样 resize 出来的图片边缘会更圆润。
    4. b 图片贴在 a 图片上,使用 a.paste(b, (x,y), b),(x,y) 为左上角的坐标,第三个参数 b 是作为 mask ,如果不使用这个参数会导致 b 图片透明的部分也覆盖在 a 上面。
    5. 这个程序在 python2 和 python3 上都可以运行。

    OK ,让我们来看一看结果怎么样吧

    恩,似乎还不错,但是大家会发现波什的手没了,所以说一马平川什么的都是骗人的。

    经过我个人的观察,会发现大部分的球星动作图都是和詹姆斯类似的(即球员的动作在图片中的位置是靠下的),如果下移粘贴坐标会导致球星卡的主要局域出现大面积的空白。一计不成,再生一计,我们可以对类似波什的动作图做特殊处理,下移他们的粘贴坐标就可以了。

    ok ,问题来了,人眼一看就会知道哪个动作图高哪个动作图低,那么 Python 怎么才能知道呢?

    可以看到,每张动作图的大小是一样的,但是具体的动作在图片中的分布是不一样的。

    这个时候我们就需要numpy这个库来帮助我们把图片转换成像素矩阵,然后我们对矩阵进行逐行扫描并记录有效像素出现的位置,这样就可以判断哪些动作图是偏高的。

    def calculateUsefulHeight(img): img = Image.open(player_path + img).convert('RGBA') w, h = img.size mat = np.array(img) for i in range(mat.shape[0]): if not allEqual(mat[i]): return h - i def allEqual(line): w = len(line) if not w: return True init_value = line[0][3] step = 10 for i in range(int(round(w/step))): if line[i * step][3] == init_value: continue else: return False return True 

    然后再在组合函数中加入对动作图高的特殊处理的代码就可以了。

    def compose(player_img, name, team_logo, category_img, output_name): ... # deal with high player image h = calculateUsefulHeight(player_img) # 这个地方的 310 是球星卡展示球员动作的最大高度 if h > 310: offset = h - 310 player_img_offset_height += offset 

    再来跑一遍,看看效果如何。

    不错,这样就可以了,尤其是一瞬间跑出来 450 张看起来效果还不错的球星卡还是非常的爽的。

    OK 了,到这里应该就可以结束了,源码可以在 这里 得到,里面包含本文所有涉及的图片,素材和代码。

    如果各位大兄弟,有更好的设计和布局也欢迎和我交流。

    2 条回复    2017-03-24 18:54:47 +08:00
    KIDJourney
        1
    KIDJourney  
       2017 年 3 月 24 日
    主要的工作量就在扣球星。。
    楼主要不要把这个解决了呀。
    ywt474000158
        2
    ywt474000158  
    OP
       2017 年 3 月 24 日
    @KIDJourney 这些球星的动作是有素材包的啊,并不需要去扣。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1031 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 36ms UTC 19:44 PVG 03:44 LAX 11:44 JFK 14:44
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86