本日は"写真の油彩化"をします.
というのは,今やってる研究の先行研究でoilPainting Effectが使われていたので,実装して勉強しようと思ったからです.
具体的な処理は下で書いてますが,とりあえず処理前後の画像で印象を掴みましょう.
これが元画像です.
これをoilPainting Effectで処理すると以下のように変化します.
OpenCVのxphotoにあるoilPaintingで処理したものになります.
以下でいくつかコードを見せますが,最後に各処理で出力された画像を置いておきます.
さて,まずは"oilPainting effect, python"で実装例を調べた結果,はじめに見つかったのがこちらです.
https://github.com/tartarskunk/OilPainting
gitに落ちていたコードでしたが,処理速度が遅かったです.
500x300程度の画像の場合,処理に10分近くかかりました.
""" oilPaintingEffect 半径Rの円形フィルターを用いた処理 フィルター適応範囲内における輝度最頻値pixelのcolor積算値を最頻値頻度で割る このブログ上ではコメントを追加しました. """ import cv2 import sys import numpy as np import time start_time = time.time() image_filename = "imgs/sea_fish.jpg" color_image = cv2.imread(image_filename, cv2.IMREAD_COLOR) height = color_image.shape[0] width = color_image.shape[1] channel = color_image.shape[2] gray_image = cv2.cvtColor(color_image, cv2.COLOR_BGR2GRAY) oil_image = np.zeros((height, width, channel)) R = 8 stroke_mask = [] for y in range(-R, R): for x in range(-R, R): if y*y + x*x < R*R: stroke_mask.append((y, x)) for y in range(height): for x in range(width): progress = np.round(100*(y*width+x)/(width*height), 2) #print("Progress: ", str(progress)+"%", end='\r') local_histogram = np.zeros(256) local_channel_count = np.zeros((channel, 256)) #フィルター適応範囲におけるhistogramの測定 for dy, dx in stroke_mask: yy = y+dy xx = x+dx if yy < 0 or yy >= height or xx <= 0 or xx >= width: continue intensity = gray_image[yy, xx] local_histogram[intensity] += 1 for c in range(channel): local_channel_count[c, intensity] += color_image[yy, xx, c] #color積算 max_intensity = np.argmax(local_histogram) #輝度最頻値 max_intensity_count = local_histogram[max_intensity] #最頻値の頻度 for c in range(channel): oil_image[y, x, c] = local_channel_count[c,max_intensity] / max_intensity_count oil_image = oil_image.astype('int') cv2.imwrite("imgs/sea_fish_git.jpg", oil_image) end_time = time.time() print(end_time - start_time)
少し冗長で読みにくいですね.
numpyを使ってもう少し読みやすくすることができそうです.
通常のopencv-python(opencv-contrib-pythonをなし)を用いて書き換えました.
処理時間は約20秒であり,処理速度は10~100倍になりました.
""" oilPaintingEffect 矩形フィルターを用いた処理 フィルター適応範囲内における輝度最頻値pixelのcolor積算値を最頻値頻度で割る """ import numpy as np import cv2 import time start_time = time.time() src = cv2.imread("imgs/sea_fish.jpg") height, width, channel = src.shape dst = np.zeros((height,width,channel), dtype=np.uint8) gray_img = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) ksize = 8 for i in range(height): for j in range(width): if i-ksize < 0 or j-ksize < 0 or i+ksize >= height or j+ksize >= width: continue else: applying_area_gray = gray_img[i-ksize:i+ksize, j-ksize:j+ksize] applying_area_gray_1d = applying_area_gray.flatten() #np.bincountは1dで利用可能 gray_histogram = np.bincount(applying_area_gray_1d) #輝度histogram gray_modal = np.argmax(gray_histogram) #輝度最頻値 gray_modal_frequency = gray_histogram[gray_modal] #最頻値の頻度 applying_area_color = src[i-ksize:i+ksize, j-ksize:j+ksize] gray_modal_color = np.sum(applying_area_color[np.where(applying_area_gray == gray_modal)], axis = 0) #輝度最頻値のcolor積算値 applying_color = np.array(gray_modal_color/gray_modal_frequency, dtype=np.uint8) dst[i,j] = applying_color cv2.imwrite("imgs/sea_fish_temp.jpg",dst) end_time = time.time() print(end_time - start_time)
全く同じ処理でなくとも,PILを用いると簡潔に似た処理が可能になります.
処理内容が異なるため,出力画像が若干異なりますが,印象的には同じようなものに思えます.
PILを用いると処理時間は約1秒で,処理速度は100~1000倍程度に高速化しました.
""" oilPaintingEffect 矩形フィルターを用いた処理 フィルター適応範囲内において最頻値を適用 """ import numpy as np from PIL import Image from PIL import ImageFilter import time start_time = time.time() src = Image.open('imgs/sea_fish.jpg') width, height = src.size ksize = 16 dst = src.filter(ImageFilter.ModeFilter(ksize)) #最頻値適用filter dst.save('imgs/sea_fish_PIL.jpg',quality=100) end_time = time.time() print(end_time-start_time)
opencv-contrib-pythonを用いた場合,xphotoが利用できるので大変高速に処理ができます.
pip install opencv-contrib-python
pipでopencv-contrib-pythonをインストールすることで,opencvのxphotoを利用できます.
処理時間は0.5秒で,処理速度は約1000倍になりました.
""" oilPaintingEffect 矩形フィルターを用いた処理 フィルター適応範囲内における輝度最頻値pixelのcolor積算値を最頻値頻度で割る """ import numpy as np import cv2 from cv2 import xphoto import time start_time = time.time() src = cv2.imread("imgs/sea_fish.jpg") dst = xphoto.oilPainting(src, 10, 1, cv2.COLOR_BGR2Lab) #oilPainting処理 cv2.imwrite("imgs/sea_fish_cv2oilPainting.jpg",dst) end_time = time.time() print(end_time - start_time)
かなり短くなりましたね.
それでは各処理結果を置いておきます.
元画像
gitに落ちていた処理によるoilPainting Effect
僕が少し改善した処理によるoilPainting Effect
PILによる擬似的oilPainting Effect
cv2.xphotoを利用したoilPainting Effect