使用 Python 組合 NBA 球星卡

Xat_MassacrE發表於2017-03-18

相信對 NBA 感興趣的大兄弟一定不會對球星卡陌生吧,雖然不知道我們們這個圈子對 NBA 感興趣的大兄弟多不多。但是,不感興趣也問題不大,本文闡述的方法其實是通用的圖片合成方法。

讓我們來看一張球星卡:

使用 Python 組合 NBA 球星卡

這種球星卡可以劃分為5個部分

  1. 球員動作圖
  2. 球員姓名
  3. 球隊logo
  4. 底板背景圖
  5. 裝飾邊框圖

今天我們要乾的事情就是找到這5個素材然後用 Python 把他們組合起來,那麼這個時候肯定有大兄弟會有疑問了,直接用 PS 套起來不就好了嗎,講道理這樣做確實方便快捷,但是前提是你只做這一張卡,當你要為聯盟大概450名球員製作球星卡的時候,你就需要一個指令碼來幫助你完成了(對 PS 不太熟,如果 PS 也可以,可以告訴我哈)。

這篇文章需要一點 Python 基礎,完全不瞭解 Python 的大兄弟最好去學習一點基礎知識再看。

OK 讓我們開始吧!

準備素材

就像開頭提到的,我們需要5種素材。這5種素材我都會提供若干個給大家練手。

使用 Python 組合 NBA 球星卡

上面的圖片實際上只有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,讓我們來看一看結果怎麼樣吧

使用 Python 組合 NBA 球星卡

恩,似乎還不錯,但是大家會發現波什的手沒了,所以說一馬平川什麼的都是騙人的。

經過我個人的觀察,會發現大部分的球星動作圖都是和詹姆斯類似的(即球員的動作在圖片中的位置是靠下的),如果下移貼上座標會導致球星卡的主要局域出現大面積的空白。一計不成,再生一計,我們可以對類似波什的動作圖做特殊處理,下移他們的貼上座標就可以了。

ok,問題來了,人眼一看就會知道哪個動作圖高哪個動作圖低,那麼 Python 怎麼才能知道呢?

使用 Python 組合 NBA 球星卡

可以看到,每張動作圖的大小是一樣的,但是具體的動作在圖片中的分佈是不一樣的。

這個時候我們就需要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複製程式碼

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

使用 Python 組合 NBA 球星卡

不錯,這樣就可以了,尤其是一瞬間跑出來 450 張看起來效果還不錯的球星卡還是非常的爽的。

OK了,到這裡應該就可以結束了,原始碼可以在 這裡 得到,裡面包含本文所有涉及的圖片,素材和程式碼。

如果各位大兄弟,有更好的設計和佈局也歡迎和我交流。

相關文章