您的位置 首页 > 体育运动

【fm2018制作球员头像】教程:用二次元头像拼成DOTA游戏的里的英雄角色!

写在前面

大家都玩过拼图,玩法是将一张张小的图像块拼成一张大图。因为小图是大图的一部分,大家可以根据大图的内容判断小图的位置,从而完成拼图。问题来了,如果每个图块是一张独立的图像呢,这样的拼图该怎么拼呢?今天在这里带大家制作这种由独立图像块组成的拼图,这样的图像也有另外一个名称——马赛克图像。使用的素材是二次元头像,目的是拼成一副DOTA游戏里痛苦女王和骷髅王的形象。下面是图片素材,大家可以自行感受下两者的画风差异。

二次元头像

DOTA英雄

拼图原理

马赛克拼图的原理很简单,大家都知道,一张图像是由若干个像素组成,例如一张64x64的图像就是由4096个像素组成。如果把这张64x64的图像分成16份4x4的图像,然后把每一个4x4的区域换成一个特征相近的4x4的独立图像,这样这张64x64的图像就变成了一张马赛克拼图,而且整体的结构信息不会有太大的改变。保证结构信息不变的关键有两个:

  1. 特征选择要合理,能表示颜色和纹理特征
  2. 用于拼图的图块要充足,至少要能覆盖大图的颜色

这里有一份示例代码,特征直接选择像素值,用欧几里德距离计算相似度,大家如果要改进可以直接换特征,比如换成LBP算子、HOG特征描述子等等,当然最完美的是采用卷积神经网络来提取特征。这里可以先看下主要的代码结构,总体分三步:

  1. 将大图按小图的个数分块
  2. 找到大图每个图像块区域颜色最接近的小图
  3. 将小图粘贴到大图对应位置
import ctypes import re import numpy as np from numpy import ctypeslib import os import cv2 import multiprocessing as mp from mul import RawArray from import euclidean from tqdm import tqdm IMG_DIR = "images" # 二次元图像路径 RATIO = 10 def resize(im, tile_row, tile_col): shape_row = im.shape[0] shape_col = im.shape[1] shrink_ratio = min(shape_row/tile_row, shape_col/tile_col) resized = cv2.resize(im, (int(shape_col/shrink_ratio)+1, int(shape_row/shrink_ratio)+1), interpolation=cv2.INTER_CUBIC) result = resized[:tile_row, :tile_col,:] return result def img_distance(im1, im2): if im1.shape != im2.shape: msg = "shapes are different {} {}".forma, im2.shape) raise Exception(msg) array1 = im1.flatten() array2 = im2.flatten() dist = euclidean(array1, array2) return dist def load_all_images(tile_row, tile_col): img_dir = IMG_DIR filenames = os.listdir(img_dir) result = [] print(len(filenames)) for filename in tqdm(filenames): if not re.search(".jpg", filename, re.I): continue try: filepath = os.(img_dir, filename) im = cv2.imread(filepath) row = im.shape[0] col = im.shape[1] im = resize(im, tile_row, tile_col) re(im)) except Exception as e: msg = "error with {} - {}".format(filepath, str(e)) print(msg) return np.array(result, dtype=np.uint8) def find_closest_image(q, shared_tile_images, tile_images_shape, shared_result, img_shape, tile_row, tile_col): tile_images_array = np.frombuffer(shared_tile_images, dtype=np.uint8) tile_images = tile_images_array.reshape(tile_images_shape) while True: [row, col, im_roi] = q.get() print(row) min_dist = float("inf") min_img = None for im in tile_images: dist = img_distance(im_roi, im) if dist < min_dist: min_dist = dist min_img = im im_res = np.frombuffer(shared_result, dtype=np.uint8).reshape(img_shape) im_res[row:row+tile_row,col:col+tile_col,:] = min_img q.task_done() def get_tile_row_col(shape): if shape[0] >= shape[1]: return [120, 90] else: return [90, 120] def generate_mosaic(infile, outfile): img = cv2.imread(infile) tile_row, tile_col = get_tile_row_col) img_shape = list) img_shape[0] = int(img_shape[0]/tile_row) * tile_row * RATIO img_shape[1] = int(img_shape[1]/tile_col) * tile_col * RATIO img = cv2.resize(img, (img_shape[1], img_shape[0]), interpolation=cv2.INTER_CUBIC) print(img_shape) im_res = np.zeros(img_shape, np.uint8) tile_images = load_all_images(tile_row, tile_col) shared_tile_images = mp., len(tile_images.flatten())) tile_images_shape = tile_images.shape np.copyto(shared_tile_images, dtype=np.uint8).reshape(tile_images_shape), tile_images) shared_result = mp., len())) q = mp.JoinableQueue() for i in range(5): p = mp.Process(target=find_closest_image, args=(q, shared_tile_images, tile_images_shape, shared_result, img_shape, tile_row, tile_col), daemon=True) p.start() print("started process") for row in range(0, img_shape[0], tile_row): for col in range(0, img_shape[1], tile_col): roi = img[row:row+tile_row,col:col+tile_col,:] q.put([row, col, roi]) q.join() cv2.imwrite(outfile, np.frombuffer(shared_result, dtype=np.uint8).reshape(img_shape)) if __name__ == "__main__": generate_mosaic(";, "out.jpg") # 是DOTA图像

效果展示

这里要说明下,上面的代码只是方便大家理解制作马赛克拼图的原理,但是要达到比较好的效果这份代码是远远不够的。但没关系,已经有人给大家做好了轮子!有个软件叫 Foto-Mosaik-Edda,下载地址 ,使用起来也很简单。这里我用2000张二次元头像拼成了两个DOTA英雄,效果还不错吧,放大看还能看到二次元头像的细节。大家快去尝试吧。

马赛克拼图效果

关于作者: luda

无忧经验小编鲁达,内容侵删请Email至wohenlihai#qq.com(#改为@)

热门推荐