概要
こんな画像から
出典:フォクすけの画像
こんな画像を作成します。
画像は透過PNGであることが前提です。
コード
import numpy as np
import cv2
from PIL import Image, ImageFilter
# 上下左右に縁取りを追加
def add_border(img, size):
border_h = np.zeros((size, img.shape[1], img.shape[2]), dtype=np.uint8)
border_v = np.zeros((img.shape[0] + 2*size, size, img.shape[2]), dtype=np.uint8)
img2 = np.vstack([border_h, img, border_h])
img2 = np.hstack([border_v, img2, border_v])
return img2
# 輪郭をなめらかに膨張
# img : 中身が白、背景が黒のグレースケール画像
# size : 膨張サイズ[pix]
def inflate(img, size):
# 円形(なめらか)に膨張
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (size,size))
img2 = cv2.morphologyEx(img, cv2.MORPH_DILATE, kernel)
#cv2.imwrite('morph.png', img2)
# モード(最頻)フィルターを適用
img2 = Image.fromarray(img2) # cv -> PIL
img2 = img2.filter(ImageFilter.ModeFilter(size))
img2 = np.array(img2, dtype=np.uint8) # PIL -> cv
#cv2.imwrite('mode.png', img2)
# 外接する輪郭を取得して内部を塗りつぶす
contours, hierarchy = cv2.findContours(img2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
img2 = cv2.drawContours(img, contours, -1, (255,255,255), thickness=cv2.FILLED)
return img2
size = 50 # 膨張サイズ
img = cv2.imread('side.png', cv2.IMREAD_UNCHANGED) # 透過情報もそのまま読み込む
# 上下左右に縁取りを追加
img = add_border(img, size)
# 透過画像から中身が白、背景が黒のグレースケール画像を作成
img_gray = img.copy()
th = img_gray[:,:,3] >= 127
img_gray[th] = (255,255,255,255)
img_gray[np.logical_not(th)] = (0,0,0,0)
img_gray = img_gray[:,:,0:3]
img_gray = cv2.cvtColor(img_gray, cv2.COLOR_BGR2GRAY)
#cv2.imwrite('gray.png', img_gray)
# 輪郭画像を作成
img_inf = inflate(img_gray, size)
img_inf = cv2.cvtColor(img_inf, cv2.COLOR_GRAY2BGR)
# 元画像を透過情報を維持しつつ合成
img_ret = img_inf[:,:] * (1 - img[:,:,3:] / 255) + img[:,:,:3] * (img[:,:,3:] / 255)
cv2.imwrite('ret.png', img_ret)
コード解説
まずは元の画像から、中身が白で背景が黒の2値画像を作成します。
次にcv2.morphologyEx
で周囲が円形になるように画像を膨張させます。
ただ、そのまま膨張させると凹部分が鋭角のままになってしまいます。
そこでPILのModeFilter
という最頻値をとるフィルターをかけます。
凹部分もうまくなめらかになりました。
あとはcv2.findContours
で外接する輪郭を取得して内側を白で塗りつぶします。
これはドーナツのように内側に穴のあるような画像を考慮しての後処理になります。
その他
わりといいかんじにできました。
元ネタ:画像輪郭のなめらか処理の方法