Pythonでモザイクアートを作った

Pythonでモザイク写真を作った。

モザイク写真とは、たくさんの写真を組み合わせて全体で一つの画像のようにしたもの。結婚式なんかでよく目にするやつ。モザイクアートとも呼ばれる。

今回はこの画像をモザイク写真にしていく。

f:id:dululuttu:20180906032435j:plain

こんなのができた

f:id:dululuttu:20180906022240j:plain

この画像を拡大すると小さい猫の画像の集合になっていることがわかる。

f:id:dululuttu:20180906022630j:plain


画像を大量に集める

まずは、モザイク写真を作るうえでその材料となる画像を大量に集める。今回はFlicker APIを使って画像を集めた。

FlickerAPIキーを取得し、それを使って猫の画像を集めていく。
コードは以下の通り

from flickrapi import FlickrAPI
import requests
import time

key = ""
secret = ""
wait_time = 1
text = 'cat'
savedir = './cats/'

flickr = FlickrAPI(key, secret, format='parsed-json')
result = flickr.photos.search(
    text = text,
    per_page = 500,
    media = 'photos',
    sort = 'relevance',
    safe_search = 1,
    extras = 'url_q, licence'
)

photos = result['photos']

for i, photo in enumerate(photos['photo']):
    try:
        url_q = photo['url_q']
    except:
        continue
    resp = requests.get(url_q)
    with open(savedir + url_q.split('/')[-1], 'wb') as f:
        f.write(resp.content)

    time.sleep(wait_time)


参考 ↓↓
qiita.com

これで猫の画像が500枚得られた。サイズはすべて150×150になってる。また、画像を取得する際サーバーへの負荷を考えて画像の取得毎に1秒待つことにしている。

モザイク写真の作り方

モザイク写真を作るときには、モザイク化したい画像を150×150の領域に分けていく。そして、分けられた領域の1つ1つと最も似ている画像を大量にある画像群から選んでいく。
似ている画像の選び方は、ピクセルごとに差をとり二乗したものを、150×150のすべてで計算し和をとる。その数値が一番小さかった画像が、分けられた領域と一番似ている画像ということにした。

コードは以下の通り
※最初は150×150の画像を使ってモザイク写真を作っていく予定だったが、計算量が多くて時間がかかってしまったため50×50にresizeしている。

import numpy as np
import cv2
import glob

files = glob.glob('./cats/*.jpg')
size = 50

#150×150の画像を50×50にresizeして行列で表す
small_img = []
for file in files:
    image = cv2.imread(file)
    image = cv2.resize(image, (size, size))
    image = image /255
    small_img.append(image)
        
small_img = np.asarray(small_img)

pic_filename = './cat_image.jpg' #モザイク写真にしたい画像のパス
big_img = cv2.imread(pic_filename)

#big_imgのサイズが50で割り切れるように指定する。このサイズを大きくするほど鮮明なモザイク写真ができる
height = (big_img.shape[0] - big_img.shape[0] % size) * 2
width = (big_img.shape[1] - big_img.shape[1] % size) * 2

big_img = big_img / 255
big_img = np.asarray(big_img)
big_img = cv2.resize(big_img, (width, height))

for i in range(int(height / size)):
    print('%d / %d' %(i + 1, int(height / size)))
    for j in range(int(width / size)):
        cut = big_img[size * i: size * (i + 1), size * j: size * (j + 1)]
        losses = []

        #small_imgの中で小さく分けた領域に一番似ているものを探す
        for s_img in small_img:
            loss = cut - s_img
            loss = loss * loss
            loss = np.sum(loss)            
            losses.append(loss)      
             
        losses = np.asarray(losses)
        big_img[size * i: size * (i + 1), size * j: size * (j + 1)] = small_img[np.argmin(losses)]

big_img = (big_img * 255).astype(np.int64)        
cv2.imwrite('cat_mosaic.jpg', big_img)

この記事の上のほうに貼ってあるモザイク写真は2200×2900にwidthとheightを指定している。
このサイズを大きくするほど、鮮明な画像になる。次の画像は4400×5800に指定した画像である。

f:id:dululuttu:20180906030903j:plain

完成したモザイク写真を見ると、配色が似ている部分には同じ画像が連続している。
これはちょっと嫌なので、もっともっと超大量に画像を準備して分ける領域を大きめにすれば鮮明さを保ったまま、同じ画像が連続することを防げるんじゃないかと思う。