応用プログラミングで補助ツール作成

2020/06/04

AI CORE XスターターキットとOpenVINO™ですぐに始めるディープラーニング推論」シリーズの9回目記事です。

このシリーズは、「ディープラーニングとは何か」から始まり、「各種ツールの使い方」「プログラミング基礎」「プログラミング応用・実践」までをステップバイステップでじっくり学び、自分で理解してオリジナルのAIアプリケーションが作れるようになることを目指しています。

第9回目はバーチャル試着アプリで必要となる補助ツールを作成します。

目次

ツール概要

ここでは、メガネ画像や帽子画像のことを「アイテム画像」という名称で呼ぶことにします。
検出した目の位置に対して、どの位置にアイテム画像を表示するかをどうやって決めるかについて考えます。

例えば、左右の目の中心座標にアイテム画像の中心を合わせて表示することが考えられます。しかし、この場合はアイテム画像の中心が目の中心になっている前提です。
メガネ画像の場合は元々画像の中心が目の中心に近い場合が多いですが、帽子画像の場合は編集する必要があります。また、メガネ画像の場合であっても、ほんの少しだけ位置をずらした方が似合うことが多いです。

特に帽子の場合などは、中心を目に合わせるために余分な領域が増えて画像サイズが大きくなり、描画処理に影響が出そうです。

そこで考え方を逆にして、アイテム画像の原点に対してどの位置に2つの目ががあると良いかの座標を決めることにします。以後、アイテム画像に対して左目と右目がどこにあるべきかを示す座標のことを「EyePoint」と呼ぶことにします。

EyePointはアイテム画像外にあっても問題ないため、画像を編集する必要がありません。
事前に画像毎のEyePointが分かっていれば、目を検出したときに計算でアイテム画像を表示する位置や大きさ、角度が決められそうですね!

アイテム画像に対し、どこをEyePointにすると良さそうなのかは感覚的な内容ですので人が決める必要があります。
という訳で今回は、背景とアイテム画像を表示して、EyePointにすべき座標をマウスクリックすると、座標が表示されるような簡単なツールを作りたいと思います。

ndarrayから画像生成

まず真っ白な背景を作成したいと思います。
ndarrayを使うと実際の画像ファイルを用意しなくても、簡単に画像生成することが可能です。

まずは黒画を生成してみます。
np.zerosを使って第一引数にはタプルでHeight, Width, Color Channelの順に、第二引数にはnp.uint8を指定します。Color Channelはカラーなので3とし、HeightとWidthに任意のサイズを割り当てます。今回は 300500にしました。標準的な表記の500 x 300(Width x Height)とは順番が逆なのでご注意ください。

import cv2
import numpy as np
 
# 黒画の生成 
img = np.zeros((3005003), np.uint8) # HWC 
 
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

実行すると黒画が表示されたかと思います。

今回はnp.zerosを使って全ての画素を(0, 0, 0)にしました。
逆に白にしたい場合は(255, 255, 255)ですが、np.255のような関数はありませんので、NumPyの計算を活用してみたいと思います。

ndarrayに対する四則演算は全ての要素に反映されます。
例えば 10 を足すとどうなるか確認してみましょう

import numpy as np
 
= np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) + 10
print(a)

こちらのように全ての要素に10が足されています。

[[11 12 13 14]
 [15 16 17 18]
 [19 20 21 22]]

この性質を利用して先程の白画を黒画にしましょう

import cv2
import numpy as np
 
# 白画の生成 
img = np.zeros((3005003), np.uint8) + 255 # HWC 
 
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

これで 500 x 300 の白画を作ることができました

マウスのコールバック

次にマウスがクリックされたときの座標情報を得たいと思います。
下記のコードを実行してみてください。ウィンドウ内をクリックするとその位置に赤丸が描かれ、ターミナル画面にはクリックした位置の座標が表示されます。

import cv2
import numpy as np
 
# コールバック関数 
def callback_mouse(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        print(x, y)
        cv2.circle(img, (x, y), 5, (00255), thickness=-1)
 
# コールバック関数の登録 
cv2.namedWindow('image')
cv2.setMouseCallback('image', callback_mouse)
 
# 白画生成 
img = np.zeros((3005003), np.uint8) + 255
 
# メインループ 
while True:
    cv2.imshow('image', img)
    key = cv2.waitKey(1)
    if key != -1:
        break
 
# 終了処理 
cv2.destroyAllWindows()

マウス情報はOpenCVを使うことで簡単に得ることが可能です。
ボタンクリックやカーソル移動などマウスに何かしらアクションが起きたら、呼び出す関数を作ることができます。このような関数のことを「コールバック関数」と呼びます。
実際にプログラムで使用するには「コールバック関数」と「コールバック関数の登録」の2つが必要です。それではコードの詳細について見てゆきましょう

コールバック関数

今回はcallback_mouseという名前で関数を作成しました。コールバック関数の場合は得られる引数が決まっていますので、5つの引数を割り当てるようにします。ポイントは最初の3つの引数event, x , y です。
x, yはマウスのカーソル座標位置で、eventには以下のような情報が入っています。

※その他の詳細はこちらを参照して下さい

ちなみに先程「クリック」と書きましたが、実際にはボタンが「ダウン」かつ「アップ」されたときのアクションのことを指すので厳密には異なります。今回の対象としているアクションは左ボタンの「ダウン」だけです。

この関数の処理をまとめると、引数eventcv2.EVENT_LBUTTONDOWNつまり左ボタンダウンのときに、x, y の値を利用してprintcv.circleで座標値表示と赤丸描画を行っています。

コールバック関数の登録

コールバック関数が存在しているだけでは何も起きません。
OpenCVでは複数のウィンドウを扱うことができますので、どのウィンドウにおけるマウスイベントなのかと、どの関数をコールバックとするのか設定が必要です。
cv2.namedWindowにてウィンドウにimageという名前をつけて、cv2.setMouseCallbackにてimageウィンドウに対してcallback_mouseという関数を設定してます。

その他

白画生成は先程のコードの通りです。
今回はカメラ映像はありませんが、マウス操作を待つ必要があるためWhile True:を使ってカメラ映像のときのように無限ループにしています。

補助ツール作成

それではOpenCVのときに学んだPNGOverlayクラスを活用して、アイテム画像も表示して補助ツールを完成させましょう。
アイテム画像はこちらを使いました。背景が透明になっている「ダウンロード用画像(無料)」ボタンからダウンロードします。
ただ、この画像のままだと少しサイズが大きいので、トリミングして縮小しました。
下の画像を右クリックして保存してください。

imageフォルダに6629_trim_small.pngという名前で保存しています。
では以下のコードを実行してみて下さい。

import cv2
import numpy as np
from pngoverlay import PNGOverlay
 
# コールバック関数 
def callback_mouse(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        print(x, y)
        cv2.circle(img, (x, y), 10, (00255), thickness=-1)
 
# コールバック関数の登録 
cv2.namedWindow('image')
cv2.setMouseCallback('image', callback_mouse)
 
# 透過PNG画像のインスタンス生成 
item = PNGOverlay('image/6629_trim_small.png')
 
# 白画生成 
img = np.zeros((item.height + 300item.width3), np.uint8) + 255
 
# 透過PNGを描画 
item.show(img, int(item.width/2) , int(item.height/2))
 
# メインループ 
while True:
    cv2.imshow('image', img)
    key = cv2.waitKey(1)
    if key != -1:
        break
 
# 終了処理 
cv2.destroyAllWindows()

マウスをクリックすると赤丸が描画され、その座標が端末に表示されます。左右の目の位置にそれぞれ赤丸を描くことで、EyePoint座標が得られるというツールです。

先程のコードからの変更は3点です

ポイントは白画生成時のHeightとWidthとPNG画像描画時の座標です。
今までは500x300の白画像を生成していましたが、今回はアイテム画像のサイズに合わせています。item.heightitem.widthを使うことによりPNGOverlayでの画像サイズが得られます。widthはそのままで、heightは帽子画像でも使えるように+300しています。
PNGOverlayは指定座標に対し、画像の中心が来るように描画します。ですので、白画の原点に画像の左上が来るようにitem.width/2, item.height/2としています。

image/6629_trim_small.pngのサイズは500x112ですが、PNGOverlayで読み込んだ変数itemのサイズはprintなどで確認すると512x512となっています。この理由はPNGOverlayクラス側の都合で、処理しやすいように、対角線の長さを一辺とする正方形にしているためです。

これでEyePointを簡易的に得られるツールができました。

Advanced 補助ツール

機能としては先程のツールで事足りていますが、少し使いづらい点もあります。
ツールはもっと改善可能です。アイテムの拡大・縮小、顔画像を表示してそこにアイテムをドラッグする など、色々とツールに機能を入れることはできます。ぜひ色々と挑戦してみてください。

参考にもう少しUIを改善したツールのソースコードをこちらに展開します。自分で挑戦してみたい方はまず、ソースコードを見ずにやってみた方が良いかもしれません。
virtual_fitting_eyepoint_tool.py

使い方


次回はいよいよ、バーチャル試着アプリの作成を行います。
以上、「応用プログラミングで補助ツール作成」でした。