Python用ライブラリ用のwrapperを作って使っている話

技術研究所 (技研) のまつけんです。

Python用のライブラリには、cv2 (OpenCV)、numpy (NumPy)、pandas (Pandas)などがあります。大抵のことが出来るので大変便利なのですが、引数の与え方などで不便を感じることがあります。そこで、今回は、私が普段、それらのライブラリをwrapするのに使っているサブルーチンを紹介したいと思います。

OpenCV用 (wrap_cv2.py)

色の定義

頻繁に利用する色 (無彩色、原色、補色) を定義します。OpenCVはRGBではなくBGRが基本なので、その順番となっています。

動画用

動画の読み込みと書き出しの際に使うものです。cv2.CAP_PROP_XXXは、4種類ともgetすることが多い上にタイプ量が多い上に、よく忘れてしまい、使うたびに調べることが多かったので纏めました。また、cv2.VideoWriter_fourccも同様です。

cap_open()のverboseをTrueにすると

のように動画の情報が表示されます。その際、秒を日・時・分・秒・ミリ秒に変換する関数time_str()を別途、importします (私の場合は、lib_etc.pyというファイルに入れています)。

このtime_str()は時間以外にも利用できるので便利です。引数unitsに((‘ M ‘, 1024 **2), (‘ k ‘, 1024), (‘ B’, 1))を指定すればMB、KBなどの容量に対応でき、[(‘,’, 1000 ** n) for n in range(10, 0, -1)] + [(”, 1)]を指定すれば3桁のコンマ区切りの文字列を生成できます。

画像の色変換

グレイスケール、RGB、BGR、HSV、YUVの変換です。cv2.cvtColorもcv2.COLOR_XXX2XXXもタイプ量が多いのと、こちらもよく忘れて頻繁に調べがちでした。基本的に、よく使う組み合わせは網羅していますが、今後も必要に応じて追加する予定です。

キャストや下準備が必要なもの

cv2.line()の座標は整数のtupleでないとエラーになります。画像の中央から直線を引く場合にcv2_line()ならば「np.array([width, height]) / 2」や「np.array(img.shape[: 2]) / 2」などをそのまま座標として与えることが出来ます。なお、色の指定については整数という制限が無いようなので、25%グレイを「WHITE / 4」として与えることも可能です。cv2_putText()において、textをstrでキャストしているのは、例えばpathlib.Path型をそのまま与えるためです。今後、必要ならば、rectangleやellipse、circleなどを作ります。

また、cv2.floodFill()は呼び出す前にmaskを作っておく必要がありますが、cv2_floodFill()は自動でそれを行います。

その他 (処理は単純だが1つの関数として用意されていないもの)

trim_and_resize()は、トリミングと縮小を同時にこなす (例えば、1920×1080の画像の両端を破棄して640×480に縮小する) ルーチンです。

rotate_img()は、「画像中央を中心として手軽に回転したい」という場合でも、それなりに記述が必要なので作りました。

NumPy用 (wrap_numpy.py)

np.zeros()やnp.ones()の引数に何らかの演算結果 (浮動小数点値) を与えたいことも多いので作りました。zerosとonesは「np_」を付けていません (私の場合、Pythonを使い始める以前は、長らくMATLAB/Octaveを使っていたので、しばしば、zeros、ones、repmatなどは「np.」を付け忘れたりします)。fullは他の変数名などとぶつかることが有り得るので、「np_full」としています。デフォルトの丸めをceilにしているのは、縮小画像の格納用に「np.array([height, width]) / n」を与えた場合に、n+1誤差で領域が足りなくなるのを防ぐためです。

複数の処理結果をstackしたいときに、前段の処理によっては、[]やNoneが含まれることがあるので、作りました。こちらも「np_」を付けていません。

画像の転置

cv2.flip()とcv2.rotate()の組み合わせでも可能ですが、np.transposeを使えば1回の変換で可能です。パラメタの「(1, 0, 2)」の順番を忘れがちなので、作りました。

Pandas用 (wrap_pandas.py)

データをCSVでsave/loadするときは、NumPy配列について、純粋にデータ部分だけを対象にすることが多いので、save_csv()とload_csv()を作りました。また、CSVで保存している途中で、誤って読み出すことを防ぐために、save_csv_safe()を作りました。

こちらは、dictや2次元listからDataFrameへの変換[1]です (dictや2次元listをCSVで保存したいときなどに便利です):

まとめ

如何でしょうか? 以上が私が使っているOpenCV、NumPy、Pandasのwrapperとなります。私は、

のようにインポートしています。なお、wrap_cv2.py、wrap_numpy.py、wrap_pandas.pyの先頭には、必要に応じて

などを書いておく必要があります。

このようなwrapperは、今後も増えていくことになると思いますので、適当なタイミングでアップデートを紹介したいと思います。

おまけ

前述のライブラリとは関係ありませんが、Pythonを便利に使うために使っているクラスやルーチンです (Python用のライブラリは色々とあるので、もしかしたら、既に存在するものを再発明している可能性もありますが、少し探してみて無さそうなら作るという方針で)。

UNIX系OSのteeコマンドに相当することをやりたい場合に使うクラスです:

C言語にはprintf()に対してsprintf()があり、コンソールに出力する代わりにバッファに格納することが出来ますが、Pythonにはその機能が無さそうなので作りました:

for i in range(100): print(result[i])のように、処理結果を表示するとき、result[i]が短い場合、右側がデッドスペースになるので、UNIX系OSのlsコマンドのように多段組で表示するクラスを作りました:

例えば、

を実行した場合、以下のような表示となります (lsコマンドのように各段の幅は自動調整されません):

こちらは、os.makedirs()に対するwrapperです:

os.makedirs()には、exist_okというオプション引数があります(「無ければ作る!」という、まるでエンジニアの生きざまのようなオプションですね)。が、これを指定しても、「有ったから作らなかった」のか「無かったから作った」のかは知りようがありません。そこで、敢えてexist_okを使わずに作ったかどうかを返します。なお、これはifを使って、

のように記述できます。しかしそれでは、存在確認と作成が不可分操作[2]として行われず、並列処理や分散処理でトラブルになることがありますので、try/exceptを使った実装としています。

参考文献

[1] 辞書をpd.DataFrameに変換
[2] Wikipedia: 不可分操作