はじめに
よくあるpythonのdlibを使用して顔を検出するやつ、だいたいがopencv2を使用してcv2.imread
とかして読み込んでいるけど、opencv2を使いたくなかった。
そこでpillowで読み込んだ画像をnumpy形式に変換してdlibに食わせようとした。
で、画像が1枚の時は無事に動作、画像をfor文で複数与えようとすると2枚目で顔検出しなくなった。
どういう状況?
コードを一部
入力する画像は同じ人物を同じ角度から撮った顔の画像。
import dlib
from PIL import Image
import numpy as np
face_detector = dlib.get_frontal_face_detector()
image_list=["01.jpg","02.jpg","03.jpg","04.jpg","05.jpg"]
for img_path in image_list:
img = np.array(Image.open(img_path),dtype=np.uint8) ##画像を読み込む
img = img[:, :, [2, 1, 0]] ##opencvとおなじBGR順にする
faces = face_detector(img, 1)
#(後処理)
このとき、01.jpg
はちゃんと顔検出する。02.jpg
が顔検出しない。
同じ画像を5枚並べてもやっぱり2枚目で検出しない。
順番を入れ替えても1枚目成功、2枚目失敗となる。
IndexError: index 0 is out of bounds for axis 0 with size 0
エラーとしてはfacesで取得できるはずの顔検出の領域が入っていないと怒られている。つまり顔検出できていない。
解決した方法
①opencvを使用する
img = cv2.imread(img_path)
画像の読み込みをopencvにすれば問題ない。でもこれは根本的な解決になっていない。
②変数を新規宣言する
img = np.array(Image.open(img_path),dtype=np.uint8)
img_p = img[:, :, [2, 1, 0]]
読み込んだあとに、BGR順に変更するがこの時に同じ変数を用いず、新規変数にする。
こうすることで解決した。
追記
ちゃんとDeepCopyしたほうが安定する。
なぜこんなことが起こったのか?
おそらくnumpy内部でのポインタ管理が云々かんぬんだと思われる。
自身を参照するとなると見た目上は変わっていてもdlibに入れる際にちゃんとした参照になっていないんじゃないかと。
このあたりはちゃんと調査していない。
ダメだった時におけるimg配列をdata
やshape
やstrides
とかを使ってポインタを追えばわかるかもしれない。
余談
>> img_p = np.array(Image.open(img_path),dtype=np.uint8) ##PILで読み込む
>> img_p = img_p[:, :, [2, 1, 0]]
>> img_c = cv2.imread(img_path) ##OpenCVで読み込む
>> print(np.allclose(img_p,img_c)) ##配列の一致を確認する
True
numpy的には新規変数を宣言しない場合でも、データは一致という。なおこの状態で動かすと5枚とも顔検出する。np.allcloseを挟むことによってポインタ参照が再整理されたのか?
また途中でほかのnumpyの処理を挟むと同じように再整理されたりされなかったりする。