Python使用圖片作爲像素生成像素畫

具體見代碼。

#!python3
# -*- coding: utf-8 -*-

import os
import math
from PIL import Image


class ProcessBar(object):
    """一個打印進度條的類"""
    def __init__(self, total):  # 初始化傳入總數
        self.shape = ['▏', '▎', '▍', '▋', '▊', '▉']
        self.shape_num = len(self.shape)
        self.row_num = 30
        self.now = 0
        self.total = total

    def print_next(self, now=-1):   # 默認+1
        if now == -1:
            self.now += 1
        else:
            self.now = now

        rate = math.ceil((self.now / self.total) * (self.row_num * self.shape_num))
        head = rate // self.shape_num
        tail = rate % self.shape_num
        info = self.shape[-1] * head
        if tail != 0:
            info += self.shape[tail-1]
        full_info = '[%s%s] [%.2f%%]' % (info, (self.row_num-len(info)) * '  ', 100 * self.now / self.total)

        print("\r", end='', flush=True)
        print(full_info, end='', flush=True)

        if self.now == self.total:
            print('')


class MosaicImg(object):
    '''
    用於將源圖像當成一個像素點合成目標圖像
    '''
    def __init__(self,
                 target,            # 目標圖像的路徑,圖像的透明區域會被忽略
                 img_path,          # 源圖片的目錄,裏面的圖片將被拿來作爲像素點
                 pixel_size=50,     # 每個像素圖片的寬高
                 size=(0, 0),       # 最後生成的圖片的尺寸,格式是 (width, height)
                 scale_factor=-1,   # 縮放因子,0~1之間的浮點數,如果設定了這個,則 size 無效
                 ):
        path = os.path.split(target)
        name, ext = os.path.splitext(path[1])
        self.save_img = os.path.join(path[0], "mosaic_" + name + '.png')      # 生成的圖像名字
        self.pixel_size = pixel_size
        # 處理目標圖像
        self.target = Image.open(target).convert('RGBA')
        if scale_factor == -1:      # 使用 size 參數
            self.target.thumbnail(size)
        else:                       # 使用 scale_factor 參數
            new_size = (scale_factor * self.target.size[0], scale_factor * self.target.size[1])
            self.target.thumbnail(new_size)

        # 處理源圖像
        self.images = {}            # key是一張圖片的主顏色,value是該圖片的 PIL.Image 對象
        self.images_count = {}
        file_list = os.listdir(img_path)
        print("正在讀取源圖像:")
        pb = ProcessBar(len(file_list))
        for file in file_list:   # 遍歷目錄
            img = Image.open(os.path.join(img_path, file)).convert('RGB')
            img.thumbnail((pixel_size, pixel_size))  # 重設圖片大小
            color = self.main_color(img)    # 計算主顏色
            self.images[color] = img
            self.images_count[color] = 0
            pb.print_next()
        # 每個圖像最多使用的次數
        self.max_img_use = 3 * self.target.size[0] * self.target.size[1] / len(self.images)

    def gen_mosaic_image(self):
        '''
        使用初始化的設置生成像素圖。
        其具體做法是遍歷目標圖像,對每個像素點的顏色,從源圖像
        的主顏色中找到一個最接近的,然後將該圖像當成像素點
        '''
        # 最後生成的圖像的大小
        size = (self.pixel_size * self.target.size[0], self.pixel_size * self.target.size[1])
        self.mosaic_img = Image.new('RGBA', size)
        # 開始生成圖像
        print("正在生成圖像: ")
        # 遍歷每一個像素
        pb = ProcessBar(self.target.size[0])
        for x in range(self.target.size[0]):
            for y in range(self.target.size[1]):
                r, g, b, a = self.target.getpixel((x, y))   # 得到該像素的顏色
                if a == 0:  # 跳過透明區域
                    continue
                min_score = 1000
                min_color = None
                for color in self.images:                   # 找到最接近的顏色
                    score = self.color_distance((r, g, b), color)
                    if score < min_score:
                        min_score = score
                        min_color = color

                # 將圖片貼上去
                self.mosaic_img.paste(self.images[min_color], (x*self.pixel_size, y*self.pixel_size))
                self.images_count[min_color] += 1           # 使用次數+1
                # 超過了最大使用次數就刪除
                if self.images_count[min_color] > self.max_img_use:
                    self.images.pop(min_color)
            pb.print_next()
        print('正在保存圖像,請稍候。')
        self.mosaic_img.save(self.save_img, format='PNG')

        return self.mosaic_img

    def main_color(self, image):
        '''
        得到一張圖片的主顏色,這裏使用了偷懶的做法,
        將圖片resize到一個像素點大小,取該點的像素值
        '''
        img = image.resize((1, 1), Image.BICUBIC)
        return img.getpixel((0, 0))

    def color_distance(self, rgb_1, rgb_2):
        '''
        兩個RGB顏色的相似度計算,使用LAB顏色空間
        '''
        R_1, G_1, B_1 = rgb_1
        R_2, G_2, B_2 = rgb_2
        rmean = (R_1 + R_2) / 2
        R = R_1 - R_2
        G = G_1 - G_2
        B = B_1 - B_2
        return math.sqrt((2+rmean/256)*(R**2)+4*(G**2)+(2+(255-rmean)/256)*(B**2))

if __name__ == '__main__':
    current_path = os.path.dirname(os.path.abspath(__file__))   # 當前目錄的完整路徑
    img_path = os.path.join(current_path, 'dog')                # 存放源圖像的目錄
    target = os.path.join(current_path, 'xinxin.png')           # 目標圖像

    ma = MosaicImg(target, img_path, 70, scale_factor=0.1)      # 初始化
    img = ma.gen_mosaic_image()                                 # 生成圖像
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章