8.4 顔の検出

(1/1)
顔検出
前回のコラムで、画像の Exif情報からプライバシーが漏れる懸念があることを書いた。スナップ写真からプライバシーが漏れる最大の懸念は、顔だろう。
Python の外部ライブラリ OpenCV には顔検出機能がある。これを使って、写真の顔を隠したり顔にスタンプを貼るプログラムを作ってみる。

なお、OpenCV の顔検出機能はかならず顔をキャッチアップできるとは限らないことを、あらかじめご承知おき願いたい。
👉コラム:カスケード分類器とディープラーニングの違い

目次

サンプル・プログラム

圧縮ファイルの内容
recognizeFaces.py画像ファイルにある顔を認識するサンプル・プログラム
shadeFaces.py画像ファイルにある顔をぼかすサンプル・プログラム
stampFaces.py画像ファイルにある顔にスタンプを貼るサンプル・プログラム
Lenna.png画像サンプル
smile.pngスタンプのサンプル

画像ファイルにある顔を認識するサンプル・プログラムの使い方

画像ファイルにある顔を認識するには、画像処理を行う外部ライブラリ OpenCV を使う。また、WebP画像読み込みに備え、imageio も使う。まだインストールしていない方は、pipコマンドを使ってインストールしてほしい。
pip install opencv-python
pip install imageio
顔画像を認識 - Python
インストールできたら、プログラム "recognizeFaces.py" を実行してみてほしい。前回つくったものと同じようなファイルダイアログを表示するので、顔を認識させたい画像ファイルを1つ選択する。顔と認識した矩形領域を青い線で囲んだ画像を画面に表示する。

同梱のサンプル画像は顔が1つしかないが、同一画像ファイルに複数の顔がある場合には、1つ1つの顔を青い線で囲むようになっている。

解説:画像ファイルにある顔を認識するプログラム

recognizeFaces.py
# メイン・プログラム =======================================================
# Haar Cascade ClassifierのXMLファイルの読み込み
faceCascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")

# 画像ファイルを読み込む image, fullname = readImage()
# 画像をグレースケールに変換 grayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 顔を検出する faces = faceCascade.detectMultiScale(grayImage, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
# 検出された顔の周りに矩形を描く for (x, y, w, h) in faces: cv2.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 2)
# 結果を表示 cv2.imshow("recognizeFaces", image)
# 何かキーが押されるまで待機 cv2.waitKey(0) # ウィンドウを閉じる cv2.destroyAllWindows()
画像ファイルの選択ダイアログは「8.2 画像の拡大・縮小」で紹介したものとほぼ同じなので、そちらをご覧いただきたい。ここでは、メイン・プログラムで顔を認識する処理について説明する。
カスケード分類器 - Python
カスケード分類器の概念図
まず、OpenCVカスケード分類器 cv2.CascadeClassifier を設定する。

カスケード分類器とは、与えられた画像から特徴量を抽出し、その特徴量の分類を複数の方法を使って逐次的に行う仕組みのことである。分類の途中で棄却と判断されれば、そこで分類は中断する。最後まで棄却されずに残っている画像が採用される。
OpenCV には複数のカスケード分類器がXMLファイルの形で用意されている。その一覧を掲げる。ここでは顔を認識するカスケード分類器 "haarcascade_frontalface_default.xml" を利用する。
XMLファイル分類対象特徴量
haarcascade_eye.xmlHaar-like
haarcascade_eye_tree_eyeglasses.xmlメガネHaar-like
haarcascade_frontalcatface.xml猫の顔 (正面)Haar-like
haarcascade_frontalcatface_extended.xml猫の顔 (正面)Haar-like
haarcascade_frontalface_alt.xml顔 (正面)Haar-like
haarcascade_frontalface_alt2.xml顔 (正面)Haar-like
haarcascade_frontalface_alt_tree.xml顔 (正面)Haar-like
haarcascade_frontalface_default.xml顔 (正面)Haar-like
haarcascade_fullbody.xml全身Haar-like
haarcascade_lefteye_2splits.xml左目Haar-like
haarcascade_licence_plate_rus_16stages.xmlロシアのナンバープレートHaar-like
haarcascade_lowerbody.xml下半身Haar-like
haarcascade_profileface.xml顔 (正面)Haar-like
haarcascade_righteye_2splits.xml右目Haar-like
haarcascade_russian_plate_number.xmlロシアのナンバープレートHaar-like
haarcascade_smile.xml顔 (笑顔)Haar-like
haarcascade_upperbody.xml上半身Haar-like
画像ファイルを読み込んだら、特徴量抽出を簡単にするために cv2.cvtColor を使ってグレースケール画像に変換する。カラー画像でも検出可能だが、グレースケールにした方が高速に検出できるからだ。

そして、faceCascade.detectMultiScale を使って顔の検出を行う。引数 scaleFacto は、縮小量のステップを意味する。定義域は1.0を超える数値で、この値が多くなればなるほど計算は離散的になり、計算速度は向上する。その分、検出率は低下する。そこで、1.0 を超えるなるべく小さい値として 1.1 を代入した。
物体候補となる矩形は,最低でも minNeighbors の数だけの近傍矩形を含む必要がある。定義域は 0 以上の数値だ。minNeighbors が小さければ小さいほど見逃しは少なくなるが、誤検出が増える。逆に、minNeighbors が大きければ大きいほど見逃しは増えるが、誤検出が減る。見逃しや誤検出が多いようであれば、minNeighbors の値を変えてみてほしい。
minSize は、検出する最小サイズだ。(幅, 高さ)をピクセル数で入力する。画像全体の大きさにもよるが、フルHD画像の中に30×30ピクセル以下の顔があったとしてもぼやけて見えないことが多いので、この値にした。

faceCascade.detectMultiScale の戻り値は、検出した物体(顔)の矩形領域を表すタプル (左上X座標, 左上Y座標, 幅, 高さ) がリストになったものである。
これを for文:blue]に渡して、1つ1つの矩形領域に cv2.rectangle でを使って青い四角形を描く。
結果は cv2.imshow を使ってグラフィックウィンドウに表示する。
最後に cv2.destroyAllWindows を使って全てのウィンドウを閉じる。
なお、このプログラムでは認識結果は画面に表示するだけで、保存はしない。

画像ファイルにある顔をぼかすサンプル・プログラムの使い方

プライバシー保護の観点から、画像ファイルにある顔を隠すプログラムを作ってみよう。ここでは、上述の顔認識で検出された矩形領域に対し、ガウスぼかし と呼ばれるぼかしをかけることにする。ぼかしをかけるには、引きつづき外部ライブラリ OpenCV を使う。
画像中の顔をぼかす - Python
プログラム "shadeFaces.py" を実行してみてほしい。ファイルダイアログを表示するので、顔をぼかしたい画像ファイルを1つ選択する。すると、顔をぼかした画像を画面に表示し、元のファイルと同じフォルダに、ファイル名に "_shade" を追加した画像ファイルを保存する。もし同名ファイルがあれば上書きしない(保存しない)。

同梱のサンプル画像は顔が1つしかないが、同一画像ファイルに複数の顔がある場合には、1つ1つの顔をぼかすようになっている。

解説:画像ファイルにある顔をぼかすプログラム

shadeFaces.py
# メイン・プログラム =======================================================
# Haar Cascade ClassifierのXMLファイルの読み込み
faceCascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")

try: # 画像ファイルを読み込む image, sourFname = readImage() # 画像をグレースケールに変換 grayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 顔を検出する faces = faceCascade.detectMultiScale(grayImage, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
# 検出された顔をぼかす for (x, y, w, h) in faces: # ぼかす矩形領域 roi = image[y:(y + h), x:(x + w)] # ガウスぼかしをかける(パラメータは奇数でなければならない) bw = int(w / 2) + (1 if int(w / 2) % 2 == 0 else 0) bh = int(h / 2) + (1 if int(h / 2) % 2 == 0 else 0) shadeRoi = cv2.GaussianBlur(roi, ksize=(bw, bh), sigmaX=0) # ぼかした矩形領域を画像に上書きする image[y:(y + h), x:(x + w)] = shadeRoi
# 保存ファイル名を作成する fname, fextension = os.path.splitext(os.path.basename(sourFname)) newFname = fname + '_shade' + fextension destFname = os.path.join(os.path.dirname(sourFname), newFname) # 画像をファイルに保存する saveImage(destFname, image)
# 結果を画面に表示する cv2.imshow("shadeFaces", image)
except Exception as e: messagebox.showerror("error", str(e)) cv2.destroyAllWindows() exit()
# 何かキーが押されるまで待機 cv2.waitKey(0) # ウィンドウを閉じる cv2.destroyAllWindows()
画像ファイルの選択ダイアログと、処理後の画像の保存については、「8.2 画像の拡大・縮小」で紹介したものとほぼ同じなので、そちらをご覧いただきたい。ここでは、メイン・プログラムで顔をぼかす処理について説明する。

顔を検出して矩形領域を求めるところまでは、"recognizeFaces.py" と同じである。
ぼかしをかけるのに cv2.GaussianBlur を用いる。この関数は、ノイズ除去やアンシャープマスク処理に使われるガウスぼかしを実行することができる。
第1引数には、ぼかす画像配列を指定する。OpenCV は、読み込んだ画像データを内部的には配列形式で保持しており、image[y:(y + h), x:(x + w)] と指定すると、画像データ image の左上座標 (y, y + h)、右下座標 (x, x + w) の矩形領域(部分画像)を取り出すという作用になる。この矩形領域に対してガウスぼかしをかける。
ksize はカーネルサイズで、横方向と縦方向をピクセル単位で指定する。カーネルサイズが大きければ大きいほど、ぼける。ここでは、顔検出した矩形領域からカーネルサイズを計算するようにした。なお、カーネルサイズは必ず奇数で亡ければならない。
sigmaX, sigmaY は標準偏差で、滑らかさを設定する。0を代入すると、OpenCV が自動計算してくれる。

なお、ガウスぼかしではなくモザイク処理にしたいときは、「8.2 画像の拡大・縮小」で学んだ画像のリサイズを利用することで実装できる。課題としてチャレンジしてみてほしい。

画像ファイルにある顔にスタンプを貼るサンプル・プログラムの使い方

Instagramなどでよく行われている、画像ファイルにある顔にスタンプを貼るプログラムを作ってみよう。ここでは、上述の顔認識で検出された矩形領域に対し、あらかじめ用意したスタンプ画像 "smile.png" を合成することにする。顔認識では引きつづき外部ライブラリ OpenCV を使うが、画像合成に「8.3 Exif情報」で使った外部ライブラリ Pillow(PIL)が必要になるので、まだインストールしていない方は、pipコマンドを使ってインストールしてほしい。
pip install PIL
画像中の顔にスタンプを貼る - Python
プログラム "stampFaces.py" を実行してみてほしい。ファイルダイアログを表示するので、顔にスタンプを貼りたい画像ファイルを1つ選択する。すると、顔にスタンプを貼った画像を画面に表示し、元のファイルと同じフォルダに、ファイル名に "_stamp" を追加した画像ファイルを保存する。もし同名ファイルがあれば上書きしない(保存しない)。

同梱のサンプル画像は顔が1つしかないが、同一画像ファイルに複数の顔がある場合には、1つ1つの顔に大きさを合わせたスタンプを貼るようになっている。

解説:画像ファイルにある顔にスタンプを貼るプログラム

stampFaces.py
# メイン・プログラム =======================================================
# Haar Cascade ClassifierのXMLファイルの読み込み
faceCascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")

try: # スタンプに使う画像を読み込む imageStamp = Image.open(imageStampFilename)
# 画像ファイルを読み込む image, sourFname = readImage() # 画像をグレースケールに変換 grayImage = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 顔を検出 faces = faceCascade.detectMultiScale(grayImage, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30)) # OpenCV画像をPillow画像に変換する imagePIL = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) # 検出された顔にスタンプを貼る for (x, y, w, h) in faces: # スタンプをリサイズする imageStampResize = imageStamp.resize((w, h), Image.LANCZOS) # スタンプを貼り付ける imagePIL.paste(imageStampResize, (x, y), imageStampResize)
# 保存ファイル名を作成する fname, fextension = os.path.splitext(os.path.basename(sourFname)) newFname = fname + '_stamp' + fextension destFname = os.path.join(os.path.dirname(sourFname), newFname)
# Pillow画像をNumPy配列に変換する imageOpenCV = np.array(imagePIL) # 色のチャンネル順序をBGRに変換する(PillowはRGB、OpenCVはBGRを使用) imageOpenCV = cv2.cvtColor(imageOpenCV, cv2.COLOR_RGB2BGR) # 画像をファイルに保存する saveImage(destFname, imageOpenCV)
# 結果を画面に表示する cv2.imshow("stampFaces", imageOpenCV)
except Exception as e: messagebox.showerror("error", str(e)) cv2.destroyAllWindows() exit()
# 何かキーが押されるまで待機 cv2.waitKey(0) # ウィンドウを閉じる cv2.destroyAllWindows()
画像ファイルの選択ダイアログと、処理後の画像の保存については、「8.2 画像の拡大・縮小」で紹介したものとほぼ同じなので、そちらをご覧いただきたい。ここでは、メイン・プログラムで顔にスタンプを合成する処理について説明する。

顔を検出して矩形領域を求めるところまでは、"recognizeFaces.py" と同じである。
スタンプ画像 "smile.png" は背景を透明(アルファチャネル)にしたものであるが、OpenCV は、アルファチャネルのある画像の合成が苦手である。そこで、PIL を使って合成することにした。OpenCVPIL とでは、内部的な画像データ形式が異なるので、顔検出が済んだら、PIL にある fromarray を使って OpenCV から PIL の画像形式に変換する。
あとは、1つ1つの顔に対し、Image.resize を使ってスタンプ画像を矩形領域に合わせてリサイズし、Image.paste を使って合成する。OpenCV に比べてアルファチャネルのある画像の合成を paste だけで実行できるので楽だ。

ここまでは Pillow画像データで処理してきたが、処理結果のファイルへの保存と画面表示のために、OpenCV画像形式に戻してやる必要がある。
OpenCV画像の実体は NumPy配列なので、まず、np.array を使って NumPy配列に変換する。次に、色のチャンネル順序が Pillow画像データと OpenCV画像で異なるため、cv2.cvtColor を使って変換する。こうして、OpenCV画像形式の imageOpenCV が得られる。

練習問題

次回予告

Python は外部ライブラリを活用することで、ゲームなどに使うアニメーション効果をプログラミングすることができる。そこで次回は、グラフを動かしたり、背景画像に雪を降らせたり、打ち上げ花火をあげるアニメーション・プログラムを作ってみることにする。

コラム:サンプル画像「レナ」とは

サンプル画像「レナ」
サンプル画像「レナ」
同梱したサンプル画像 "Lenna.png" だが、画像処理に携わったことがある方なら、どこかで目にしたことがあるだろう。
OpenCV のカスケード分類器のようなライブラリが用意されていなかった時代、数学の参考書を読みながら画像処理をしていた1980年代前半、私もこの画像を使ってプログラムの評価をしていた。本編で紹介したように、実質2行で顔認識ができてしまうというのは隔世の感がある。
さて、「CULTURE, COMMUNICATION, AND AN INFORMATION AGE MADONNA」(IEEE Professional Communication Society, 2001年5月)によると、この画像は論文用に、1973年(昭和48年)に512×512ピクセルの画像として雑誌からスキャンされたものだという。その雑誌というのが、1972年(昭和47年)発行のプレイボーイ誌である。ネットを探せばこの写真の全体像がヒットするのだが、当サイトでは紹介することができない😓

研究者たちが40年以上にわたってこの画像を利用し続けてきたのは、上述のIEEEのレターによれば、ディテールがそこそこ細かく、陰影やテクスチャーが含まれているからだという。好んでプレイボーイ誌の写真を使いたかったわけではないだろう。
プレイボーイは画像の無断使用を禁止しているわけが、この画像があまりにも広く流布してしまったため、その利用を黙認することにしたそうだ。

なお、モデルのレナさんは、1997年(平成9年)5月にボストンで開催された画像科学技術協会の50周年記念カンファレンスに出席されたとのこと。レナさんの母国はスウェーデンで、本名の綴りは Lena だが、英語圏でリーナと誤読される恐れがあることから、データ名は Lenna に変更している。http://www.lenna.org/というサイトが開設されている。

コラム:カスケード分類器とディープラーニングの違い

学ぶ人工知能のイラスト
最近流行のAI(人工知能)はディープラーニングと呼ぶ仕組みを利用し、画像から顔を検出することもできる。
今回のサンプル・プログラムを使ってみて、検出できなかった顔があったと思う。では、ディープラーニングなら抜け漏れなく検出できるかというと、そういうわけでもない。
結論を先に申し上げると、カスケード分類器ディープラーニングも、機械学習という手法を用いており、出発点は同じである。しかし、用途が異なる。
本編の一覧表に掲げたように、カスケード分類器は検出する物体ごとに機械学習を行う。その物体を含んだ画像を集めて機械学習させるので、ディープラーニングに比べて少量の学習データで成果を得ることができる。
これに対してディープラーニングは、大量の画像を学習データとして投入し、この画像には顔が含まれている、この画像には目が含まれているといった判断を加えてやる。
したがって、もし同量の学習データを使って機械学習を行っているとするならば、検出する目的(物体)が明らかなときはカスケード分類器の方が精度の高い結果を得られる。
現実には、世界中の研究者や企業がこぞってディープラーニングを学習させており、圧倒的な量の学習データが集まっている。とくに有料のディープラーニングサービスであれば、OpenCVカスケード分類器よりも精度の高い結果が得られるだろう。逆に言えば、貧弱な学習データで機械学習させたディープラーニングよりは、カスケード分類器の方が精度の高い結果を出すことができる。

コラム:顔検出と顔認証

顔認証システムのイラスト
マイナンバーカードやパスポートには、あなたの顔写真がデジタル化された情報として記録されているICチップが内蔵されている。これを使って、医療機関や通関で、それを持っている人物があなた自身であることを証明することができる。これを顔認証と呼ぶ。
顔認証は、本編で紹介した顔検出とは異なる技術である。顔検出は、そこに顔があるかどうか分からないような画像から顔を検出する技術である。一方の顔認証は、2つの顔画像を比較して、それが同一であるかどうかを判定する技術である。
顔検出は、本編で紹介したカスケード分類器の前段にある特徴量抽出を行う。ここでは不特定多数の顔の特徴量をキャッチアップできるような計算方法を用いる。
一方の顔認証も、2つの顔画像の特徴量抽出を行うのだが、両者が同一人物の顔である特徴量をキャッチアップできるような計算方法を用いる点が異なる。当然、顔認証の特徴量抽出計算の方が高い精度を求められるので、認証カメラの指定の位置に顔を合わせ、帽子やサングラスなどを外すことが求められる。

参考サイト

(この項おわり)
header