おはようございます。
最近、キメラを召喚して対戦するゲームを作っています。
キメラの画像はコチラの透過pngの素材を使わせていただいているのですが、
どうにも余白が不揃い(全体的に右寄り)で、少し扱いづらい。
(でもメッチャ可愛いし使いたい)
なので、余白の自動調整をするプログラムを作ります。
ロジックとしては、
1.画像を読み込む
2.余白を全カットする
3.いい感じに余白をつけなおす
4.出力する
という想定。
というわけで、調査開始。
調べてみると、ちょうどいい記事を発見。
→【画像処理】OpenCVを使って画像周りの余白削除(トリミング)を自動化してみた!(1)
pythonは3日くらいしか触っていませんが、何となくopenCVをインストールして、ところどころ修正を入れながら、適当な画像で変換開始!
うん、真っ黒だね!!!
コードを触って確かめていくと、どうやら最初の画像読み込み(cv2.imread)の時点で、真っ黒になっているらしく、透過pngの場合には「flags = cv2.IMREAD_UNCHANGED」が必要だったらしい。
# 画像の読み込み
img = cv2.imread(image, cv2.IMREAD_UNCHANGED)
オプションをつけてから、読み込んだ画像をそのまま吐き出すと、無事に透過pngが扱えることを確認!
良かった~、と安心して、
修正したプログラムを再度実行!!

うん、真っ黒だね!!!
今度はなんだ(# ゚Д゚)!?
どうやら、グレースケール・2値化したときに、やっぱり真っ黒になっている。
現状のコードではダメらしい(知らんけど)
ロジックとしては、地と図で2値化できればいいはずなので、ChatGPTに聞いてみる。
(こういう限定的な用途での実装コードは結構頼れるなぁ)
# アルファチャンネルを抽出
alpha_channel = img[:, :, 3]
# 透明部分を黒(0)、不透明部分を白(255)に二値化
_, binary_mask = cv2.threshold(alpha_channel, 0, 255, cv2.THRESH_BINARY)
上記のコードで、いい感じに2値化できるらしい。
というわけで修正したコードを実行。
余白ゼロ、超コンパクト!
いい感じだ( *´艸`)
最後に、余白をいい感じに追加
余白の追加は、cv2.copyMakeBorder()でできるらしいので、
横は中央揃え、縦は下揃えとして適当に計算するコードを追加。
(めんどくさいので全部直書き)
#完成画像のサイズ定数
TARGET_HEIGHT = 1300
TARGET_WIDTH = 1300
BOTTOM_MARGIN = 100
# 余白の計算(bottom固定、左右中央揃え)
crop_h, crop_w = crop_img.shape[:2]
bm = BOTTOM_MARGIN
tm = TARGET_HEIGHT - crop_h - bm
lm = (TARGET_WIDTH - crop_w)//2
rm = TARGET_WIDTH - lm - crop_w
# 余白が足りない場合のエラー表示(要:定数の調整)
if(tm < 0 | lm < 0):
print("error:" + image_name)
continue
# 余白の追加
margined_image = cv2.copyMakeBorder(crop_img, tm, bm, lm, rm,
cv2.BORDER_CONSTANT, (0,0,0,0))
実行結果
Before
After
プログラムの完成版はこちら
import cv2
import os
import glob
# 余白を削除する関数
def crop(image): #引数は画像の相対パス
# 画像の読み込み
img = cv2.imread(image, cv2.IMREAD_UNCHANGED)
# アルファチャンネルを抽出
alpha_channel = img[:, :, 3]
# 透明部分を黒(0)、不透明部分を白(255)に二値化
_, binary_mask = cv2.threshold(alpha_channel, 0, 255, cv2.THRESH_BINARY)
# 輪郭を抽出
contours = cv2.findContours(binary_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]
# 輪郭の座標をリストに代入していく
x1 = [] #x座標の最小値
y1 = [] #y座標の最小値
x2 = [] #x座標の最大値
y2 = [] #y座標の最大値
for i in range(0, len(contours)):
ret = cv2.boundingRect(contours[i])
x1.append(ret[0])
y1.append(ret[1])
x2.append(ret[0] + ret[2])
y2.append(ret[1] + ret[3])
# 輪郭の一番外枠を切り抜き(※5pxの余裕を持たせた)
x1_min = min(x1)-5
y1_min = min(y1)-5
x2_max = max(x2)+5
y2_max = max(y2)+5
cv2.rectangle(img, (x1_min, y1_min), (x2_max, y2_max), (0, 255, 0), 3)
crop_img = img[y1_min:y2_max, x1_min:x2_max]
return img, crop_img
#フォルダ名、拡張子
INPUTDIR = 'images_fot_trim'
OUTPUTDIR = 'trimmed_images'
EXT = 'png'
#完成画像のサイズ定数
TARGET_HEIGHT = 1300
TARGET_WIDTH = 1300
BOTTOM_MARGIN = 100
# 編集後の画像の保存ディレクトリの作成
if not os.path.isdir(OUTPUTDIR):
os.mkdir(OUTPUTDIR)
# INPUTDIR内の全ての画像に対してループ
for image in glob.glob(INPUTDIR + '/*.' + EXT):
# 相対パスの部分を削除
image_name = os.path.basename(image)
# クロップ
img, crop_img = crop(image)
# 余白の計算(bottom固定、左右中央揃え)
crop_h, crop_w = crop_img.shape[:2]
bm = BOTTOM_MARGIN
tm = TARGET_HEIGHT - crop_h - bm
lm = (TARGET_WIDTH - crop_w)//2
rm = TARGET_WIDTH - lm - crop_w
# 余白が足りない場合のエラー表示(要:定数の調整)
if(tm < 0 | lm < 0):
print("error:" + image_name)
continue
# 余白の追加
margined_image = cv2.copyMakeBorder(crop_img, tm, bm, lm, rm,
cv2.BORDER_CONSTANT, (0,0,0,0))
# 切り取った画像を保存
cv2.imwrite(OUTPUTDIR + '/' + image_name, margined_image)
print(image_name + " finished")
print("margin adjustment completed!!")
余談
現状の処理ではモンスターの左右端から同じだけの余白を入れているが、
人の目で見て「中央に揃っている」という印象になるためには、画像の重心が中央にくる必要がありそうだ。
画像の2値化はできているので、x軸に沿って画素を数えることで重心を算出すれば、さらに良くなりそう。
人の目で見て「中央に揃っている」という印象になるためには、画像の重心が中央にくる必要がありそうだ。
画像の2値化はできているので、x軸に沿って画素を数えることで重心を算出すれば、さらに良くなりそう。
参考リンク
- かわいいモンスターシルエット素材
- 【画像処理】OpenCVを使って画像周りの余白削除(トリミング)を自動化してみた!(1)
- OpenCVで透過画像を扱う ~スプライトを舞わせる~
- OpenCVで画像に余白を追加
0 件のコメント:
コメントを投稿