「こたつにはいってぬくぬくしている間に画像の下処理が終わってたらよいな~」などと思いましたが、サンタさんに頼んでも多分来ないので、作ることにしました。
ふわ~っとした知識で書いているので、「こんなことができるんだ~」程度に見ていただければと思います。マサカリ大歓迎です。
OpenCVとは
画像処理・画像解析および機械学習等の機能を持つC/C++、Java、Python、MATLAB用ライブラリ
だそう。
やること
資料作成だったり動画制作の際に行う白抜きを自動化します。
背景の白を透明色に置き換える操作ですね。
1~3枚ならツールで手作業でもよいのですが、40~50枚と数がかさばってくるとかなりストレスですよね。
こんなものはさっさと自動化してしまいましょう。
やってみた
キホン
ライブラリをインストールして...
pip install opencv-python
使いたいファイル内でインポートすればOK。
ついでにセットでよく使うNumPyも入れましょう。
import cv2
import numpy as np
NumPyに関しては別途インストールする必要はありません。
実は、セットで使われることを見越してOpenCVのインストール時に一緒についてきてくれます。やさしい。
仕様
- ファイル名を入力すると、そのファイルを白抜きする
- 拡張子はPNGに変換する
やってみた
まずは画像を読み込んで...
img = cv2.imread(imgFileName)
RGBA方式(透明度を扱える色画像)に変換...
rgbaImg = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
ここでちょっとだけ補足。
上の二行では変数にファイルを格納してるけど、実際に格納されているのは画像の各ピクセルが持つデータが格納されたndarrayです。
補足:画像の扱い方
どうせ数日後に見返したおれが「なんやねんそれ...」になるので、詳しく補足。
まず、ndarray
について。
これはNumPyが提供する多次元配列みたいな感じで、Python標準の多次元配列よりもいろいろなことができる、って感じだと思う。
たとえば、3x3x3
の多次元配列に「1, 2, 3, ..., 27」って感じで数値を入れたいなら、
import numpy as np
arr = np.arange(1, 28).reshape([3, 3, 3])
ってカンジ。
こんな感じで、多次元配列を手軽に扱えるのがndarray
のよいところ。1
次に、画像データの扱いについて。
画像のデータは各ピクセルごとのRGBA値がndarray
で管理されてて、例えば
img[0][0][0] # インデックスはそれぞれx, y, RGBA(0~3)
なら「画像の(0, 0)のRの値を参照するで!」ということになります。
本題にもどる
ということで続き。
RGBA変換した画像のうち、白の部分を透過していきます。
rgbaImg[..., 3] = np.where(np.all(img == 255, axis=-1), 0, 255)
はい。急に訳がわからなくなりました。
順番に整理していきます。
obj[..., 0]
こいつについて。
一見すると「引数めんどくさいから略したんか?」みたいなナリをしていますが、れっきとしたNumPyの構文です。
一応こいつは書き換えると
obj[:, :, 0]
となります。これでもよくわからん。
これ、実はPythonのスライスという構文になります。
スライスは簡単に言うと「配列をわざわざforループでまわすのめんどくさいし、短く書いたろ!」みたいなカンジの機能です。知らんけど。
そのなかでも、コロン単体のスライスは配列全体を取り出すという意味となります。
つまり、この三点リーダは 「省略した引数たちがとり得る値の組み合わせ」を全部出す という意味を持ちます。
長ったらしく説明しましたが、とりあえずrgbaImg[..., 3] =
が示す意味は、
rgbaImg
内のピクセルすべての透明度に対して代入するで~!
ってカンジです。
np.where()
右辺もひとつずつさらっていきましょう。
この場合におけるnp.where()
は、いわば三項演算子の役割を持っています。
今回の場合、
-
np.all(~)
が真偽値を返す - 結果が
True
であれば0(透明)
、False
であれば255(透明ではない)
を返す
といった感じですね。単純。
np.all()
さて。あとはこいつです。
こいつなんですが、与えた評価式を評価するという仕事を持ちます。
もちろんそれだけではありません。この引数に多次元配列を与えた場合、その多次元配列のすべての要素に対して順番に評価
します。
今回の場合img == 255
がそれですね。
これはimg
変数と数値を比較しているわけではなく、img
変数の各要素と比較している感じですね。
axis
オプションについても説明しようと思いましたが、今回はとくに重要ではないのと、とても分かりやすく解説されているサイトがあったので、よければ参考までに覗いてみてください(他力本願)
https://snowtree-injune.com/2020/04/29/axis-z005/#toc6
帰還
さて、舞い戻ってきました。どこまでいったっけ。
画像に対する処理は完了したので、あとは完成したファイルをPNG形式で保存します。
imgName = imgFileName.split(".")[0]
cv2.imwrite("result/result_" + imgName + ".png", rgbaImg)
これでオッケー。
全体像
今後のことも考え、いいカンジに関数化しておきます。
(白抜きのことを表す英動詞がいまいち見つからなかったので、それっぽいhollow
にしています。かっこいい。)
import cv2
import numpy as np
def hollow(imgFileName):
img = cv2.imread(imgFileName)
rgbaImg = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
rgbaImg[..., 3] = np.where(np.all(img == 255, axis=-1), 0, 255)
imgName = imgFileName.split(".")[0]
cv2.imwrite("result/result_" + imgName + ".png", rgbaImg)
print("hollowed: " + imgFileName)
def __main__():
imgFileName = input("ファイル名を入力してください(拡張子を含めること)")
hollow(imgFileName)
__main__()
実行
今回はいらすとやから画像をお借りしてきました。
「ラーメンの油をまとめる人」のイラストです。
準備ができたらメインファイルを実行して、ファイル名を入力すると...
ファイル名を入力してください(拡張子を含めること)merge_ramen_oil_man.jpg
hollowed: merge_ramen_oil_man.jpg
できた!!!!!!!!!!
ん...?
なんか...すごく、汚いです。
丼、穴空いてますし。
反省
- 対象の周囲がうまく白抜きできていない
- 白を全部抜いてしまうため、イラスト内の白も抜いてしまう
- 閾値(白と判定する範囲)が低すぎる
こんなところでしょうか。
ともあれ、よい経験になりました。
-
ほかにも「要素に対して型が割り当てられる」(PythonのリストはすべてObject型)などなど様々な面で便利だけど、全容はつかみきれなかった;; ↩