python实现马赛克拼图!

直接上代码!

代码如下:

#!/usr/local/bin/python3

#  --*-- coding:utf8 --*--

import getopt

import sys

import os

import logging

from PIL import Image

from multiprocessing import Process, Queue, cpu_count

TILE_SIZE = 30  # 素材图片大小

TILE_MATCH_RES = 10  #配置指数  ,值越大匹配度越高,执行时间越长

ENLARGEMENT = 4  # 生成的图片是原始图片的多少倍

TILE_BLOCK_SIZE = int(TILE_SIZE / max(min(TILE_MATCH_RES, TILE_SIZE), 1))

WORKER_COUNT = max(cpu_count() - 1, 1)

EOQ_VALUE = None

WARN_INFO = """ *缺少有效参数*

    参数:

        -i [--image]     : 原图片地址

        -t [--tiles_dir] : 素材目录地址

        -o [--outfile]   : 输出文件地址 【可选】

"""

class TileProcessor:

    def __init__(self, tiles_directory):

        self.tiles_directory = tiles_directory

    def __process_tile(self, tile_path):

        try:

            img = Image.open(tile_path)

            # tiles must be square, so get the largest square that fits inside the image

            w = img.size[0]

            h = img.size[1]

            min_dimension = min(w, h)

            w_crop = (w - min_dimension) / 2

            h_crop = (h - min_dimension) / 2

            img = img.crop((w_crop, h_crop, w - w_crop, h - h_crop))

            large_tile_img = img.resize((TILE_SIZE, TILE_SIZE), Image.ANTIALIAS)

            small_tile_img = img.resize((int(TILE_SIZE / TILE_BLOCK_SIZE), int(TILE_SIZE / TILE_BLOCK_SIZE)),

                                        Image.ANTIALIAS)

            return (large_tile_img.convert('RGB'), small_tile_img.convert('RGB'))

        except Exception as e:

            logging.warning(e)

            return (None, None)

    def get_tiles(self):

        large_tiles = []

        small_tiles = []

        logging.info('从 \'%s\' 获取图片素材...' % self.tiles_directory)

        # search the tiles directory recursively

        for root, subFolders, files in os.walk(self.tiles_directory):

            for tile_name in files:

                tile_path = os.path.join(root, tile_name)

                large_tile, small_tile = self.__process_tile(tile_path)

                logging.debug(large_tile)

                logging.debug(small_tile)

                if large_tile:

                    large_tiles.append(large_tile)

                    small_tiles.append(small_tile)

        logging.info('读取素材 %s 完成.' % len(large_tiles))

        return (large_tiles, small_tiles)

class TargetImage:

    def __init__(self, image_path):

        self.image_path = image_path

    def get_data(self):

        logging.info('处理主图片...')

        img = Image.open(self.image_path)

        w = img.size[0] * ENLARGEMENT

        h = img.size[1] * ENLARGEMENT

        large_img = img.resize((w, h), Image.ANTIALIAS)

        w_diff = (w % TILE_SIZE) / 2

        h_diff = (h % TILE_SIZE) / 2

        # if necesary, crop the image slightly so we use a whole number of tiles horizontally and vertically

        if w_diff or h_diff:

            large_img = large_img.crop((w_diff, h_diff, w - w_diff, h - h_diff))

        small_img = large_img.resize((int(w / TILE_BLOCK_SIZE), int(h / TILE_BLOCK_SIZE)), Image.ANTIALIAS)

        image_data = (large_img.convert('RGB'), small_img.convert('RGB'))

        logging.info('主图片处理完成.')

        return image_data

class TileFitter:

    def __init__(self, tiles_data):

        self.tiles_data = tiles_data

    def __get_tile_diff(self, t1, t2, bail_out_value):

        diff = 0

        for i in range(len(t1)):

            # diff += (abs(t1[i][0] - t2[i][0]) + abs(t1[i][1] - t2[i][1]) + abs(t1[i][2] - t2[i][2]))

            diff += ((t1[i][0] - t2[i][0]) ** 2 + (t1[i][1] - t2[i][1]) ** 2 + (t1[i][2] - t2[i][2]) ** 2)

            if diff > bail_out_value:

                # we know already that this isnt going to be the best fit, so no point continuing with this tile

                return diff

        return diff

    def get_best_fit_tile(self, img_data):

        best_fit_tile_index = None

        min_diff = sys.maxsize

        tile_index = 0

        # go through each tile in turn looking for the best match for the part of the image represented by 'img_data'

        for tile_data in self.tiles_data:

            diff = self.__get_tile_diff(img_data, tile_data, min_diff)

            # logging.info(diff)

            if diff < min_diff:

                min_diff = diff

                best_fit_tile_index = tile_index

            tile_index += 1

        return best_fit_tile_index

def fit_tiles(work_queue, result_queue, tiles_data):

    # this function gets run by the worker processes, one on each CPU core

    tile_fitter = TileFitter(tiles_data)

    while True:

        try:

            img_data, img_coords = work_queue.get(True)

            if img_data == EOQ_VALUE:

                break

            tile_index = tile_fitter.get_best_fit_tile(img_data)

            result_queue.put((img_coords, tile_index))

        except KeyboardInterrupt:

            pass

    # let the result handler know that this worker has finished everything

    result_queue.put((EOQ_VALUE, EOQ_VALUE))

class ProgressCounter:

    def __init__(self, total):

        self.total = total

        self.counter = 0

    def update(self):

        self.counter += 1

        sys.stdout.write(

            "进度: %s%% %s" % ((100 * self.counter / self.total), "\r"))

        # sys.stdout.write("Progress: %s%% %s" % (100 * self.counter / self.total, "\r"))

    sys.stdout.flush()

class MosaicImage:

    def __init__(self, original_img, outfile):

        self.image = Image.new(original_img.mode, original_img.size)

        self.x_tile_count = int(original_img.size[0] / TILE_SIZE)

        self.y_tile_count = int(original_img.size[1] / TILE_SIZE)

        self.total_tiles = self.x_tile_count * self.y_tile_count

        self.outfile = outfile

    def add_tile(self, tile_data, coords):

        img = Image.new('RGB', (TILE_SIZE, TILE_SIZE))

        img.putdata(tile_data)

        self.image.paste(img, coords)

    def save(self, path):

        self.image.save(path)

def build_mosaic(result_queue, all_tile_data_large, original_img_large, outfile):

    mosaic = MosaicImage(original_img_large, outfile)

    active_workers = WORKER_COUNT

    while True:

        try:

            img_coords, best_fit_tile_index = result_queue.get()

            if img_coords == EOQ_VALUE:

                active_workers -= 1

                if not active_workers:

                    break

            else:

                tile_data = all_tile_data_large[best_fit_tile_index]

                mosaic.add_tile(tile_data, img_coords)

        except KeyboardInterrupt:

            pass

    mosaic.save(mosaic.outfile)

    logging.info('============ 生成成功 ============')

def compose(original_img, tiles, outfile):

    logging.info('生成图片中,按下 Ctrl-C 中断...')

    original_img_large, original_img_small = original_img

    tiles_large, tiles_small = tiles

    mosaic = MosaicImage(original_img_large, outfile)

    all_tile_data_large = list(map(lambda tile: list(tile.getdata()), tiles_large))

    all_tile_data_small = list(map(lambda tile: list(tile.getdata()), tiles_small))

    work_queue = Queue(WORKER_COUNT)

    result_queue = Queue()

    try:

        # start the worker processes that will build the mosaic image

        Process(target=build_mosaic, args=(result_queue, all_tile_data_large, original_img_large, outfile)).start()

        # start the worker processes that will perform the tile fitting

        for n in range(WORKER_COUNT):

            Process(target=fit_tiles, args=(work_queue, result_queue, all_tile_data_small)).start()

        progress = ProgressCounter(mosaic.x_tile_count * mosaic.y_tile_count)

        for x in range(mosaic.x_tile_count):

            for y in range(mosaic.y_tile_count):

                large_box = (x * TILE_SIZE, y * TILE_SIZE, (x + 1) * TILE_SIZE, (y + 1) * TILE_SIZE)

                small_box = (

                    x * TILE_SIZE / TILE_BLOCK_SIZE, y * TILE_SIZE / TILE_BLOCK_SIZE,

                    (x + 1) * TILE_SIZE / TILE_BLOCK_SIZE,

                    (y + 1) * TILE_SIZE / TILE_BLOCK_SIZE)

                work_queue.put((list(original_img_small.crop(small_box).getdata()), large_box))

                progress.update()

    except KeyboardInterrupt:

        logging.info('\nHalting, saving partial image please wait...')

    finally:

        # put these special values onto the queue to let the workers know they can terminate

        for n in range(WORKER_COUNT):

            work_queue.put((EOQ_VALUE, EOQ_VALUE))

def mosaic(img_path, tiles_path, outfile):

    tiles_data = TileProcessor(tiles_path).get_tiles()

    image_data = TargetImage(img_path).get_data()

    compose(image_data, tiles_data, output)

if __name__ == '__main__':

    logging.basicConfig(filename='mosaic.log',

                        format='%(asctime)s  %(filename)s : %(levelname)s  %(message)s',

                        level=logging.INFO)

    logging.getLogger().addHandler(logging.StreamHandler())

    opts, args = getopt.gnu_getopt(sys.argv[1:], 'i:t:o:ts:tr:e:', ['image=', 'tiles_dir=', 'outfile=',""])

    base_image = None

    tiles_dir = None

    output = None

    for k, v in opts:

        if k in ("-i", "--image"):

            base_image = v

        if k in ("-t", "--tiles_dir"):

            tiles_dir = v

        if k in ("-o", "--outfile"):

            output = v

    # base_image = None

    # tiles_dir = None

    for value in (base_image, tiles_dir):

        if value is None:

            logging.error(WARN_INFO)

            sys.exit()

    if output is None:

        output = './mosaic.jpg'

    mosaic(base_image, tiles_dir, output)

注!!!***

这里不是直接运行的!这里你要在终端使用!

**命令:python mosaic_v2.py -i "D:\image\pic.jpg" -t "D:\image"

程序原图:

效果图:

梦的远方,温暖为向,所到之处,遍地阳光!

如果你在学习Python的过程当中有遇见任何问题,可以加入python交流学企鹅群:【611+530+101】,多多交流问题,互帮互助,群里有不错的学习教程和开发工具。学习python有任何问题(学习方法,学习效率,如何就业),可以随时来咨询我

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,639评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,093评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,079评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,329评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,343评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,047评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,645评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,565评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,095评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,201评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,338评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,014评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,701评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,194评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,320评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,685评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,345评论 2 358

推荐阅读更多精彩内容