カテゴリー別アーカイブ: OpenCV

(101) OpenCV #6 : 写真から顔を自動検出

(101) OpenCV #6 : 写真から顔を自動検出

1. やりたいこと

Pythonで OpenCVの第 6回目、今回は OpenCVの Haar Cascades を使って写真から顔検出して遊んでみる。

敢えて「遊んでみる」と書いたのは、原理的な所を学習せずに顔検出機能を使ってみるだけだから…

2. 前準備

(1) 顔の写った写真を用意する。

顔検出に使った写真をこのブログに掲載するので、以下の条件を満たしている必要がある。
・自分や知り合いの顔が写っている写真はダメだ。
・ライセンス上の問題が発生する写真はダメだ。

ということで CC0ライセンス画像を使うことにする。
pixabay

(2) OpenCVで使用する学習済みデータを入手する。

こちらから入手する。
https://github.com/opencv/opencv

この中から以下の2ファイルを使用する。
・opencv/data/haarcascades/haarcascade_eye.xml
・opencv/data/haarcascades/haarcascade_frontalface_default.xml

3. やってみる

基本的にはこちらのチュートリアルのまんま書いて遊んでみる。
Face Detection using Haar Cascades

(1) まずは顔だけを抽出してみる。

import numpy as np
import cv2 as cv

# 顔検出対象の画像をロードし、白黒画像にしておく。
imgS = cv.imread('face.png')
imgG = cv.cvtColor(imgS, cv.COLOR_BGR2GRAY)

# 学習済みデータをロード
face_cascade = cv.CascadeClassifier('haarcascade_frontalface_default.xml')

# 顔検出を実行
faces = face_cascade.detectMultiScale(imgG, 1.3, 5)

# 顔検出箇所を矩形で囲む。
for (x,y,w,h) in faces:
    cv.rectangle(imgS,(x,y),(x+w,y+h),(0,255,255),2)

# 表示
cv.imshow('image',imgS)
cv.waitKey(0)

# 表示消去
cv.destroyAllWindows()

斜め後ろから見ている人を除き 5人を検出できた。

(2) 次に顔の中から目を検出してみる。

import numpy as np
import cv2 as cv

# 顔検出対象の画像をロードし、白黒画像にしておく。
imgS = cv.imread('face.png')
imgG = cv.cvtColor(imgS, cv.COLOR_BGR2GRAY)

# 学習済みデータをロード
face_cascade = cv.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade  = cv.CascadeClassifier('haarcascade_eye.xml')

# 顔検出を実行
faces = face_cascade.detectMultiScale(imgG, 1.3, 5)

# 顔検出箇所を矩形で囲む。
for (x,y,w,h) in faces:
    cv.rectangle(imgS,(x,y),(x+w,y+h),(0,255,255),4)
    # 目を検出
    imgG_face = imgG[y:y+h, x:x+w]
    imgC_face = imgS[y:y+h, x:x+w]
    eyes = eye_cascade.detectMultiScale(imgG_face)
    for (ex,ey,ew,eh) in eyes:
        cv.rectangle(imgC_face,(ex,ey),(ex+ew,ey+eh),(0,128,255),4)

# 表示
cv.imshow('image',imgS)
cv.waitKey(0)

# 表示消去
cv.destroyAllWindows()

口を誤検出しているが概ね良好な結果かも。

4. おまけ

(1) 検出パラメーターを変更してみる。

(scaleFactor, minNeighbors) = (1.1, 3)

(scaleFactor, minNeighbors) = (1.1, 5)

(scaleFactor, minNeighbors) = (1.3, 3)

(scaleFactor, minNeighbors) = (1.3, 5)

(scaleFactor, minNeighbors) = (2.0, 3)

(scaleFactor, minNeighbors) = (2.0, 5)

(2) 検出した顔をファイル保存する。

検出した顔をくり抜いて PNGファイルに保存してみる。

import numpy as np
import cv2 as cv
import os, shutil

imgS = cv.imread('face2.jpg')
imgG = cv.cvtColor(imgS, cv.COLOR_BGR2GRAY)

face_cascade = cv.CascadeClassifier('haarcascade_frontalface_default.xml')
faces = face_cascade.detectMultiScale(imgG, 1.1, 3)

img_out_path = './out'
if os.path.exists(img_out_path):
    shutil.rmtree(img_out_path)
os.mkdir(img_out_path)

i = 1
for (x,y,w,h) in faces:
    cv.rectangle(imgS,(x,y),(x+w,y+h),(0,255,255),4)
    imgC_face = imgS[y:y+h, x:x+w]
    fpath = img_out_path + '/face_%05d.png' % i
    cv.imwrite(fpath, imgC_face)
    i = i + 1

こんな感じで切り抜けた。
当然だが誤検出した物も PNGファイル出力されている。

5. 所感

いつかちゃんとアルゴリズムを学習しよう。
特徴点を抽出し、マッチングし、なのだとは思うが。


(100) OpenCV #5 : 画像カラーのバンド入れ替え(BGR → RGB)

(100) OpenCV #5 : 画像カラーのバンド入れ替え(BGR → RGB)

1. やりたいこと

カラー画像を OpenCVでロードした場合、画像データ配列中の並び順は BGR である。
これを RGB の並び順に変更したい。

2. 本当に BGRなのかを確認

こんな画像を用意した。

この画像を OpenCVでロードし、画像データの中身をバンド別(=チャネル別)に見てみる。

因みに…
OpenCVでロードした画像データは numpy配列に格納されている。

import cv2
img = cv2.imread("band.png")
type(img)

実行結果、以下のように numpy.ndarray と表示された。

<class 'numpy.ndarray'>

よって、ロードした画像データから任意のバンドのデータを抽出するには、numpy配列に対する制御をすればよいのだ。

まずは 1バンド目は?

import cv2
import numpy as np
img = cv2.imread("band.png")
cv2.namedWindow('image', cv2.WINDOW_AUTOSIZE)

# 1バンド目だけを取得&表示
img1 = img[:,:,0]
cv2.imshow('image', img1)
cv2.waitKey()
cv2.destroyAllWindows()

確かに B(Blue)が入っていた。

次に 2バンド目は?

# 2バンド目だけを取得&表示
img2 = img[:,:,1]
cv2.imshow('image', img2)
cv2.waitKey()
cv2.destroyAllWindows()

確かに G(Green)が入っていた。

最後に 3バンド目は?

# 3バンド目だけを取得&表示
img3 = img[:,:,2]
cv2.imshow('image', img3)
cv2.waitKey()
cv2.destroyAllWindows()

確かに R(Red)が入っていた。

cv2.imread()でロードした画像データの中身は、確かに BGR の順に格納されていた。

3. やってみる

(1) OpenCVの関数で BGR → RGB変換

便利な関数が用意されている。

import cv2
img = cv2.imread("band.png")

# OpenCVの関数 cvtColor()でバンド変換する。
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

(2) numpy配列のスワップで実現

前述の「2. 本当に BGRなのかを確認」で確認した通り、画像データ配列の3次元目には以下のように格納されている。
[][][0] : B (Blue)
[][][1] : G (Green)
[][][2] : R (Red)

よって…
[][][0]と [][][2]を入れ替えてやればよいのだ。

これは numpy配列に対する制御で簡単に書ける。

import cv2
import numpy as np
img = cv2.imread("band.png")

# 画像データの 3次元目を 2→1→0 の順に入れ替える。
imgRGB = img[:,:,[2,1,0]]

試しに PILで RGBカラー画像として表示してみる。

# 表示
from PIL import Image
imgP = Image.fromarray(imgRGB)
imgP.show()


OKだ!


(99) OpenCV #4 : ガンマ補正で画像を見やすく調整

(99) OpenCV #4 : ガンマ補正で画像を見やすく調整

1. やりたいこと

Pythonで OpenCVの第 4回目、今回は OpenCVの Look up table を使って画像のガンマ補正(gamma correction)をやってみる。

2. ガンマ補正とは

ガンマ補正(gamma correction)とは、以下の式で入力値に対する出力値を得ること。
これにより画像の輝度を所望の状態に調整すること。

まずは、入力値(I)と出力値(O)の関係をグラフ上で見てみる。
折角 Pythonを使っているので matplotlib でグラフを表示してみる。

import numpy as np
import matplotlib.pyplot as plt

ary_gamma = np.array([0.1, 0.2, 0.4, 0.67, 1, 1.5, 2.5, 5, 10, 25])

for var_gamma in ary_gamma:
    var_px = np.empty(256, np.uint8)
    for i in range(256):
        var_px[i] = np.clip(pow(i / 255.0, var_gamma) * 255.0, 0, 255)
    plt.plot(var_px, label=str(var_gamma))

plt.legend()
plt.xlabel("INPUT")
plt.ylabel("OUTPUT")
plt.show()

実行すると以下のグラフが表示される。
線ごとに色を指定せずとも自動的に色分けしてくれるので便利だ。

γ = 1 であれば入力値=出力値のリニアな関係になるので画像は何も変わらない。
γ < 1 であれば暗い部分の輝度変化が強調される。
γ > 1 であれば明るい部分の輝度変化が強調される。

3. やってみる

(1) 必要最小限の機能

まずは一切の装飾や利便性などを考慮せず、愚直にガンマ補正機能だけを実行してみる。

import cv2
import numpy as np

# ガンマ値を決める。
gamma = 0.8

# 処理対象の画像をロード
imgS = cv2.imread("img001.png")

# ガンマ値を使って Look up tableを作成
lookUpTable = np.empty((1,256), np.uint8)
for i in range(256):
    lookUpTable[0,i] = np.clip(pow(i / 255.0, gamma) * 255.0, 0, 255)

# Look up tableを使って画像の輝度値を変更
imgA = cv2.LUT(imgS, lookUpTable)

# 表示実行
cv2.namedWindow('image', cv2.WINDOW_AUTOSIZE)
cv2.imshow('image', imgA)
cv2.waitKey()

ガンマ値=0.8で実行したところ、暗い部分が少しだけ見やすくなったか?

(2) 少しだけ機能拡張

上記(1)のシンプル実装プログラムに対して、以下の二つの機能を追加実装した。
追加機能#1 : コマンドラインから、画像ファイルを指定できるようにした。
追加機能#2 : コマンドラインから、ガンマ値を指定できるようにした。

###########################################################
# ガンマ補正プログラム
import argparse
import cv2
import numpy as np

def main():
    # コマンドライン引数「γ値」を取得
    parser = argparse.ArgumentParser()
    parser.add_argument('-g','--gamma', required=True)
    parser.add_argument('-f','--filepath',  required=True)
    args = parser.parse_args()
    # γ補正実行
    exec_gamma_correction( args.filepath, float(args.gamma) )

def exec_gamma_correction( filepath, gamma ):
    # 処理対象の画像をロード
    imgS = cv2.imread(filepath)

    # γ値を使って Look up tableを作成
    lookUpTable = np.empty((1,256), np.uint8)
    for i in range(256):
        lookUpTable[0,i] = np.clip(pow(i / 255.0, gamma) * 255.0, 0, 255)

    # Look up tableを使って画像の輝度値を変更
    imgA = cv2.LUT(imgS, lookUpTable)

    # PILで表示用画像を作成
    from PIL import Image, ImageDraw, ImageFont
    imgA_RGB = cv2.cvtColor(imgA, cv2.COLOR_BGR2RGB)
    imgP = Image.fromarray(imgA_RGB)

    # 画像の左上にγ値の表示を埋め込む
    obj_draw = ImageDraw.Draw(imgP)
    obj_font = ImageFont.truetype("/usr/share/fonts/ipa/ipagp.ttf", 40)
    obj_draw.text((10, 10), "γ = %.1f" % gamma, fill=(255, 255, 255), font=obj_font)

    # 表示実行
    imgP.show()

if __name__=="__main__": main()

Shellから以下の二つのコマンドラインオプションを指定して実行する。
-f : 対象画像ファイルのパス
-g : ガンマ値

python3 gamma_correct.py -f img001.png -g 0.8

以下、いろいろなガンマ値で実行してみた。

でも…
iPhoneの自動補正機能が素晴らしいのか?
ガンマ値=1 が一番きれいに見えるなぁ







4. 参考

ありがとうございます。 m(_ _)m
https://docs.opencv.org/master/d3/dc1/tutorial_basic_linear_transform.html


(98) OpenCV #3 : Canny法でエッジ抽出

(98) OpenCV #3 : Canny法でエッジ抽出

1. やりたいこと

Pythonで OpenCVの第 3回目、今回は OpenCVの Canny関数を使って画像のエッジ抽出をやりたい。

2. やってみる

まずは元画像を読み込み、白黒画像に変換する。

import cv2
from PIL import Image

# 対象画像をロード
img = cv2.imread("img001.jpg")

# カラー画像を白黒画像に変換
imgG = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 画像を表示
pilG = Image.fromarray(imgG)
pilG.show()

次に Canny法(ヒステリシス min:100, max:200)でエッジを抽出してみる。
すなわち、Canny法のパラメーターとして、
・UP方向の場合の閾値は 200
・DOWN方向の場合の閾値は 100
・100~200は ON/OFF混在のモヤモヤ区間
とするのだ。
アルゴリズムの詳細は Canny法によるエッジ検出 を参照のこと。

# Canny法でエッジ抽出
edges = cv2.Canny(imgG,100,200)

# 画像を表示
pilE = Image.fromarray(edges)
pilE.show()

次に Canny法(ヒステリシス min:50, max:100)でエッジを抽出してみる。

次に Canny法(ヒステリシス min:150, max:200)でエッジを抽出してみる。

最後に Canny法(ヒステリシス min:125, max:175)でエッジを抽出してみる。

3. 所感

最適な閾値は対象画像により異なるはず。
どうやって見つけるのかな?

4. 参考

ありがとうございます。 m(_ _)m
http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_canny/py_canny.html


(97) OpenCV #2 : Sobelフィルターでエッジ抽出

(97) OpenCV #2 : Sobelフィルターでエッジ抽出

1. やりたいこと

Pythonで OpenCVの第 2回目、今回は OpenCVの Sobel関数を使って画像のエッジ抽出をやりたい。

2. やってみる

まずは元画像を読み込み、白黒画像に変換する。

import cv2
from PIL import Image

# 対象画像をロード
img = cv2.imread("img001.jpg")

# カラー画像を白黒画像に変換
imgG = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 画像を表示
pilG = Image.fromarray(imgG)
pilG.show()

次に X方向の勾配を算出する。

# X方向の勾配を算出
sx = cv2.Sobel(imgG, cv2.CV_64F, 1, 0, ksize=3)

# 画像を表示
pltSX = Image.fromarray(sx)
pltSX.show()

次に Y方向の勾配を算出する。

# Y方向の勾配を算出
sy = cv2.Sobel(imgG, cv2.CV_64F, 0, 1, ksize=3)

# 画像を表示
pltSY = Image.fromarray(sy)
pltSY.show()

最後に X,Y両方向を合わせた画像の勾配を算出する。

import numpy as np

# 両方向合わせた勾配を算出
grad = np.sqrt(sx ** 2 + sy ** 2)

# 画像を表示
pltGr = Image.fromarray(grad)
pltGr.show()

3. 所感

ちょっとエッジ抽出には向かない画像だったかな…

4. 参考

ありがとうございます。 m(_ _)m
http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_gradients/py_gradients.html


(96) OpenCV #1 : まずは使ってみる。

(96) OpenCV #1 : まずは使ってみる。

1. やりたいこと

Pythonで OpenCVを使ってみたい。

OpenCV については説明するまでもないが、CV(Computer Vision)の名の通り、画像認識などに代表される画像・映像処理関連のライブラリだ。

C++ではちょくちょく使っているが、Pythonでは使ったことがない。
画像データは2次元、3次元配列なので、配列操作を簡潔に書ける Pythonだと画像データを扱い易いはず。

2. やってみる

(0) 事前準備

ここでは anacondaでインストールした Python3 環境にインストールする。
pip等、anaconda配下のプログラムが起動するようにパスを通す。

export PATH=~/anaconda3/bin/:$PATH

※anacondaの入れ方はこちらを参照のこと。

(1) OpenCVをインストール

入門編なので、ソースからコンパイルではなく、パッケージとして用意されている物を使う。

# pipが入っているか?
python3 -m pip -V

# Case1 : なければインストール
wget https://bootstrap.pypa.io/get-pip.py
python3 get-pip.py
rm get-pip.py

# Case2 : あっても更新しておく。
pip install -U pip

# OpenCVをインストール
pip install opencv-python

# msgpackが古いと怒られたのでインストール
pip install --upgrade msgpack

# ついでに PILをインストールしておく。
pip install pillow

(2) カラー画像を白黒画像に変換する。

import cv2

# 画像ファイルを読み出す。
img = cv2.imread("pic.jpg")

# 平均値を求める。
imgG = img.mean(axis=2)

# 表示
from PIL import Image
imgP = Image.fromarray(imgG) # numpy配列から変換
imgP.show()

できた!

でも…
これではファイル読み出しに OpenCVを使っているだけで宝の持ち腐れだ。

そこで、今度は OpenCVで白黒画像に変換する。

import cv2

# 画像ファイルを読み出す。
img = cv2.imread("pic.jpg")

# 白黒画像に変換する。
imgG = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 表示
from PIL import Image
imgP = Image.fromarray(imgG) # numpy配列から変換
imgP.show()

できた!

3. おまけ

(1) 画像の表示方法(cv2を使う場合)

上記のプログラム例では PILを使って表示している。
OpenCV公式サイトのプログラム例では、cv2を使って以下のように表示している。

import cv2
img = cv2.imread('image.jpg')
cv2.namedWindow('image', cv2.WINDOW_AUTOSIZE)
cv2.imshow('image', img)
cv2.waitKey()
cv2.destroyAllWindows()

Windowのタイトルバーに任意の文字列を書けるのでこちらの方がよいか?
でも、最後に waitkey() なる一行を実行しないと表示されないのはなぜ?

詳細はこちらの公式ページを参照のこと。
Operations with images

4. 所感

とりあえず Pythonで OpenCV初体験なので、これぐらいにしておこう。
続くかな…