ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【python/OpenCV/dlib】dlibとOpenCVを使って顔認識をする

rikoubou.hatenablog.com

 前回の記事でdlibをインストールしました。今回はそのdlibとOpenCVを使って顔認識をしていきます。

 では始めます。


1:学習済みデータのダウンロード
 顔の認識には学習済みのデータを使用します。

 上記のページを開き、一番下にある「shape_predictor_68_face_landmarks.dat.bz2」というファイルをダウンロードします。
f:id:rikoubou:20190314170724p:plain
 ファイルをダウンロードして解凍すると「shape_predictor_68_face_landmarks.dat」という学習済みデータであるdatファイルが作成されるので、これを利用していきます。(解凍すると100MBほどになるので注意してください)


2:顔認識のサンプルコード
 以下の内容をコピペして「dlibTest.py」というファイル名で保存します。

・dlibTest.py

#
# dlibとOpenCVを使った顔認識
#
import cv2
import dlib

def main():
	detector = dlib.get_frontal_face_detector()
	predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") # 学習済みファイル読み込み

	cap = cv2.VideoCapture(0) # 任意のカメラ番号に変更する

	while True:
		ret, frame = cap.read()

		k = cv2.waitKey(1)&0xff # キー入力を待つ
		if k == ord('q'):
			# 「q」キーが押されたら終了する
			break
		
		# 画面サイズを1/2にする
		height = frame.shape[0]
		width = frame.shape[1]
		frame = cv2.resize(frame , (int(width/2), int(height/2)))

		img = frame * 0
		dets = detector(frame[:, :, ::-1])
		if len(dets) > 0:
			parts = predictor(frame, dets[0]).parts() # 顔の点を取得

			# 確認
			for i in parts:
				# cv2.circle(frame, (i.x, i.y), 1, (255, 0, 0), -1)
				cv2.circle(img, (i.x, i.y), 1, (255, 0, 0), -1) # 点をプロット

		# cv2.imshow("camera", frame) # 画像を表示
		cv2.imshow("camera", img) # 画像を表示

	# キャプチャをリリースして、ウィンドウをすべて閉じる
	cap.release()
	cv2.destroyAllWindows()

if __name__ == '__main__':
	main()

 保存したらdlibTest.pyと同じ階層に1で準備した「shape_predictor_68_face_landmarks.dat」ファイルを配置します。
f:id:rikoubou:20190314171029p:plain

 Webカメラを繋げた状態でdlibTest.pyを実行すると以下のように顔の特徴点をとらえた画像が表示されます。
f:id:rikoubou:20190314171343p:plain


 以上がdlibとOpenCVを使って顔認識をする方法です。

 OpenCVだけでも顔認識などはできましたが、dlibを使った方がより精度が高く表情などが取れると思います。
 

・参考資料

【python】MacOSにdlibをインストールする

 OpenCVでも顔の検知はできますが、より精度の高い顔の検知をするためのライブラリとして「dlib」というのがあるのを知りました。

 今回はそのdlibをインストールする方法です。

 基本的にはpipが入っていればコマンド一つでインストールできるのですが、自分の場合は色々とエラーが起きたりしたのでその対処法も含めて紹介します。

 では始めます。


1:前提条件
 前提条件として以下のものがインストールされていることが必要です。

 インストールできてない場合は、自分の環境構築をした時の記事があるので参考にしてください。


2:dlibのインストール方法
 基本的には以下のコマンドでインストールできます。

$ pip install dlib

 上記のコマンドを実行して以下のようなエラーが出る場合があります。
f:id:rikoubou:20190313152251p:plain

 エラーにある通りに以下のコマンドを実行してpipをアップグレードします。

$ pip install --upgrade pip

 pipのアップグレードが完了したら「pip install dlib」コマンドをもう一度実行します。

 それでも以下のようなエラーが発生する場合があります。

    RuntimeError:
    *******************************************************************
     CMake must be installed to build the following extensions: dlib
    *******************************************************************

 このエラーが発生した場合はCMakeのインストールが必要なので以下のコマンドでCMakeをインストールします。

$ brew install cmake

 Cmakeのインストールには割と時間がかかります。

 ここまでできたらもう一度「pip install dlib」コマンドを実行すればインストールできるはずです。

 dlibがインストールできたかはimportしてエラーが出ないことで確認できます。
f:id:rikoubou:20190313152904p:plain


 以上がdlibのインストール方法です。

 コマンド一つでなかなか通らないこともありちょっと時間がかかったので、参考になれば幸いです。


・参考資料

【python/OpenCV】カメラ映像をキャプチャするプログラム

 pythonOpenCVを使って色々やってきた中で今更な感じですが、今回はカメラ画像を任意のタイミングで気軽に画像として保存できるようにするプログラムです。
 画像認識などを行う場合にはサンプルとして様々な画像を取得する必要があったりします。「楽してカメラのキャプチャを取れるようにしたい」という思いから自分が作ってみたので、その紹介です。


・カメラのキャプチャ画像を取得するプログラム
・saveCameraImg.py

#
# カメラ画像をキャプチャする
#
import cv2
from datetime import datetime

cap = cv2.VideoCapture(0) # 任意のカメラ番号に変更する

while True:
    ret, frame = cap.read()
    cv2.imshow("camera", frame)

    k = cv2.waitKey(1)&0xff # キー入力を待つ
    if k == ord('p'):
        # 「p」キーで画像を保存
        date = datetime.now().strftime("%Y%m%d_%H%M%S")
        path = "./img/" + date + ".png"
        cv2.imwrite(path, frame) # ファイル保存

        cv2.imshow(path, frame) # キャプチャした画像を表示
    elif k == ord('q'):
        # 「q」キーが押されたら終了する
        break

# キャプチャをリリースして、ウィンドウをすべて閉じる
cap.release()
cv2.destroyAllWindows()

 使い方としては、上記のプログラムをコピペして「saveCameraImg.py」というファイル名で保存して同じ階層に「imgフォルダ」を作成します。
f:id:rikoubou:20190307151838p:plain

 saveCameraImg.pyを実行するとカメラの映像が表示されるので「p」キーを押してキャプチャ、「q」キーを押して終了します。
 画像は秒単位の現在時刻でimgフォルダ内に保存されます。


 以上がカメラ画像をキャプチャするプログラムです。地味ですがこういうちょっとしたツールのようなものを作っておくと作業が楽になったりするので自由に使ってください。
 またコピペしたりフォルダ作るのが面倒という人もいるかもしれないので、以下のリンクに解凍するだけで使えるzipファイルを公開しておきます。

【python】pythonを使った最小二乗法でデータから数式を推定する方法

 今回はタイトルにある通り、pythonを使ってデータから数式を推定する方法の備忘録です。

 最小二乗法とか難しい言葉を使ってはいますが、要は「データをプロットすると一定の法則性があって数式化できそう」という場合に用いる方法になります。

 詳しい理論などは置いておいて、とりあえずデータから数式を推定するという部分のみの解説になるので注意してください。

 では始めます。


0:matplotlibを使えるようにする
 今回はmatplotlibというライブラリを使います。importしようとしてもエラーが発生する場合は以下の参考にさせていただいたページ様のやり方で解決できます。

 要は以下のコマンドを実行して、そのファイルの中身を開いて「backend : macosx」となっているところを「backend : Tkagg」に書き換えるというだけです。

$ python -c "import matplotlib;print(matplotlib.matplotlib_fname())"

 この方法はMacOSのみの場合なので、Windowsでエラーが出て使えない場合は各自で調べるようお願いします。


1:データの準備
 それでは本筋に入っていきますが、事前準備としてデータを準備します。今回は以下の適当なデータを使うことにします。

・x.csv

180,211,224,235,247,256,265,271,275,279,284,287,289,292,295,296,298,300,301,302,304,302,306,306,308,309,309

・y.csv

357,337,329,322,315,311,305,301,298,295,293,293,290,288,286,287,285,284,282,282,282,281,281,279,279,278,279

 上記の2つのデータをそれぞれ「x.csv」、「y.csv」として保存したファイルを使って以降の話を進めていきます。


2:データのプロット
 まずは上記のデータをプロットしたグラフを作成して傾向を見てみます。

 以下のプログラムをコピペして「dataPlot.py」という名前で保存します。

・dataPlot.py

#
# データプロット
#
import numpy as np
from matplotlib import pyplot as plt

def main():
    X_CSV_PATH = "x.csv"
    Y_CSV_PATH = "y.csv"
    SAVE_NAME = "result.png"

    x, y = loadCSVFile(X_CSV_PATH, Y_CSV_PATH) # CSVファイルを読み込み
    plt.scatter(x , y)     # データプロット
    plt.savefig(SAVE_NAME) # グラフを画像として保存

# CSVファイルを読み込む関数
def loadCSVFile(x_path, y_path):
    try:
        x_csv = np.loadtxt(x_path, delimiter=',')
        y_csv = np.loadtxt(y_path, delimiter=',')
    except Exception as e:
        raise e
    return x_csv, y_csv

if __name__ == '__main__':
    main()

 少し解説すると、「x_csv = np.loadtxt(x_path, delimiter=',')」、「y_csv = np.loadtxt(y_path, delimiter=',')」のところで第一引数のファイルパスをCSVとして読み込んでnumpyの配列として取得しています。この取得したデータを「plt.scatter(x , y)」に入れてグラフにプロットし、「plt.savefig(SAVE_NAME)」で画像として保存しています。

 pythonのファイルを作成したら、次は以下のように同じ階層にx.csvとy.csvを配置します。
f:id:rikoubou:20190228172205p:plain

 その状態でdataPlot.pyを実行すると「result.png」という名前でグラフの画像ファイルが作成されます。
f:id:rikoubou:20190228172301p:plain

 このグラフの傾向を見ると、直線の一次関数(y = ax + b)に近い形でデータがプロットされていることがわかります。


3:数式の推定
 2でデータをプロットしたことにより、なんとなく一次関数っぽくなっていることがわかりました。
 ここからは実際に数式を推定していきます。

 数式の推定にはpythonのライブラリであるnumpyの「polyfit関数」を使えば計算できます。

coefficient = np.polyfit([Xデータ], [Yデータ], [次元数])

 第一引数にXのデータ、第二引数にYのデータ、第三引数に次元数(整数)を入力すれば係数を計算してくれます。例えば次元数を3にすると「y = ax^3 + bx^2 + cx + d」の数式の各係数a,b,c,dの4つを得ることができます。
 またこれらの係数を使った数式のグラフは以下の記述で作成できます。

plt.plot(x, np.poly1d(coefficient)(x)) # グラフを記述

 係数の計算方法とグラフの記述方法がわかったので、2のプログラムにこの係数を推定する記述を追加して計算してみます。

・dataPlot2.py

#
# データプロットと次元の計算
#
import numpy as np
from matplotlib import pyplot as plt

def main():
    DIMENSION = 1 # 次元数

    X_CSV_PATH = "x.csv"
    Y_CSV_PATH = "y.csv"
    SAVE_NAME = "result.png"

    x, y = loadCSVFile(X_CSV_PATH, Y_CSV_PATH) # CSVファイルを読み込み
    plt.scatter(x , y)     # データプロット

    coefficient = np.polyfit(x, y, DIMENSION) # 係数を計算
    print(coefficient) # 係数を確認

    plt.plot(x, np.poly1d(coefficient)(x)) # グラフを記述

    plt.savefig(SAVE_NAME) # グラフを画像として保存

# CSVファイルを読み込む関数
def loadCSVFile(x_path, y_path):
    try:
        x_csv = np.loadtxt(x_path, delimiter=',')
        y_csv = np.loadtxt(y_path, delimiter=',')
    except Exception as e:
        raise e
    return x_csv, y_csv

if __name__ == '__main__':
    main()

 実行すると以下のように係数とグラフが得られます。

・係数

[ -0.60376793 464.66578936]

・グラフ
f:id:rikoubou:20190228174553p:plain

 グラフを見る限り、割と合っていそうな感じがします。


 以上がpythonを使ってデータから数式を推定する方法です。

 あらかじめ係数を計算しておきcoefficientを定数として持っておけば、他のxのデータが入ってきても推定した数式からおおよそのyの値を得ることができます。ちょっとした傾向などから値を推測する程度であればこのやり方で十分かと思います。
 ライブラリを使って楽に数式を作れるのはありがたいですね(正直原理はちゃんと理解していないです…)。

 また参考資料で紹介しているサイト様で他の関数を使った数式の推定も行なっているので、気になる方はそちらも参照してください。

 最近はビッグデータだなんだと言われる時代でもあるので、ちょっとだけでもデータを使って色々できるようになりたいです。

 今回作成したプログラムと使用したCSVを公開しておきます。


・参考資料

【python/OpenCV】画像の特定の色を抽出する方法

 今回はpythonOpenCVを使って画像の特定の色を抽出する方法の備忘録です。
 OpenCVでは2つの方法で特定の色を抽出できるので、その2つの方法を説明していきます。

 では始めます。


1:RGBでの色抽出方法
 まず一つ目の方法としてRGBでの色の抽出方法があります。その方法は以下の記述でできます。

import cv2
import numpy as np

image = cv2.imread('./test1.png') # ファイル読み込み

# BGRでの色抽出
bgrLower = np.array([102, 255, 255])    # 抽出する色の下限(BGR)
bgrUpper = np.array([102, 255, 255])    # 抽出する色の上限(BGR)
img_mask = cv2.inRange(image, bgrLower, bgrUpper) # BGRからマスクを作成
result = cv2.bitwise_and(image, image, mask=img_mask) # 元画像とマスクを合成

 ※OpenCVでは「RGBではなくBGRになっている」ので色の順番には注意してください。

 抽出する色の上限下限を作成してinRange関数でマスクを取得します。そのマスクを元画像に適応するとこで、範囲内の色以外を黒くするという方法になっています。


2:HSVでの色抽出方法
 もう一つの方法としてHSVでの色の抽出方法があります。

 HSVとは「色相(Hue)・彩度(Saturation)・明度(Value)」で色を表したもので、以下のリンクからRGB・HSVの変換ができます。

 ちなみにOpenCVでのそれぞれの範囲は以下の通りとなっています。

名称 値の範囲 備考
色相(H) 0〜180 本来は0〜360だがOpenCVでは1/2の範囲になる
彩度(S) 0〜255 値が0に近づくほど白く、255に近づくほどHの色になる
明度(V) 0〜255 値が0に近づくほど黒く、255に近づくほどHの色になる

 そのHSVを使った色の抽出方法は以下の記述でできます。

import cv2
import numpy as np

image = cv2.imread('./test1.png') # ファイル読み込み

# HSVでの色抽出
hsvLower = np.array([30, 153, 255])    # 抽出する色の下限(HSV)
hsvUpper = np.array([30, 153, 255])    # 抽出する色の上限(HSV)
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 画像をHSVに変換
hsv_mask = cv2.inRange(hsv, hsvLower, hsvUpper)    # HSVからマスクを作成
result = cv2.bitwise_and(image, image, mask=hsv_mask) # 元画像とマスクを合成

 1の手法と似ていますが、上限下限がHSVになっているのと一度画像をHSV形式に変換しているところが違っています。


3:実装例
 色抽出の方法がわかったので実際に以下の画像から背景色を抽出してみたいと思います。
f:id:rikoubou:20190221185338p:plain

 上記の画像を「test1.png」として保存し、同じ階層に以下のファイルを作成して実行します。

・colorExtraction.py

#
# 色抽出のサンプルコード
#
import numpy as np
import cv2
from time import sleep

# メイン関数
def main():
    image = cv2.imread('./test1.png') # ファイル読み込み

    # BGRでの色抽出
    bgrLower = np.array([102, 255, 255])    # 抽出する色の下限
    bgrUpper = np.array([102, 255, 255])    # 抽出する色の上限
    bgrResult = bgrExtraction(image, bgrLower, bgrUpper)
    cv2.imshow('BGR_test1', bgrResult)
    sleep(1)

    # HSVでの色抽出
    hsvLower = np.array([30, 153, 255])    # 抽出する色の下限
    hsvUpper = np.array([30, 153, 255])    # 抽出する色の上限
    hsvResult = hsvExtraction(image, hsvLower, hsvUpper)
    cv2.imshow('HSV_test1', hsvResult)
    sleep(1)

    while True:
        # キー入力を1ms待って、keyが「q」だったらbreak
        key = cv2.waitKey(1)&0xff
        if key == ord('q'):
            break

    cv2.destroyAllWindows()


# BGRで特定の色を抽出する関数
def bgrExtraction(image, bgrLower, bgrUpper):
    img_mask = cv2.inRange(image, bgrLower, bgrUpper) # BGRからマスクを作成
    result = cv2.bitwise_and(image, image, mask=img_mask) # 元画像とマスクを合成
    return result

# HSVで特定の色を抽出する関数
def hsvExtraction(image, hsvLower, hsvUpper):
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 画像をHSVに変換
    hsv_mask = cv2.inRange(hsv, hsvLower, hsvUpper)    # HSVからマスクを作成
    result = cv2.bitwise_and(image, image, mask=hsv_mask) # 元画像とマスクを合成
    return result


if __name__ == '__main__':
    main()

 すると以下のようにどちらの方法でも背景色のみが抽出できます。
f:id:rikoubou:20190221185739p:plain


 以上がpythonOpenCVで特定の色を抽出する方法です。

 基本的に画像から色を抽出する時ははっきりとした色ではなくグラデーションがある場合が多いです。
 どちらか一方だけではなく、画像の内容や目的によってBGRの方法とHSVの方法を使い分ける必要が出てくるかと思います。


・参考資料

【python】クラスの継承

rikoubou.hatenablog.com

 一つ前の記事では単体テストをやりました。今回はタイトルにある通りpythonでのクラスの継承です。

 正直参考資料のページを参照した方がわかりやすいかと思いますが、自分のための備忘録として記事に残しておきます。

 では始めます。


・継承の記述方法
 pythonでのクラスの継承の記述方法は簡単で以下の通りです。

class 新しいクラス名(継承元クラス名):
	# 以下略

 継承元の関数をそのまま使いたい場合は特に何も記述する必要はありません。
 逆に継承元の関数の内容を上書きしたい場合は、単純に同じ関数名で記述すればOKです。

 具体例として以下の単純なクラスの継承例を作りました。

#
# 継承テスト例
#

# 継承元のクラス
class ParentClass():
	def __init__(self, name):
		self.name = name
	
	def showName(self):
		print("Parent name is ", self.name)

# ParentClassを継承したクラス1
class ChildClass1(ParentClass):
	def showChildName(self):
		"""
		showChildName関数を新たに追加
		"""
		print("showChildName is ", self.name)

# ParentClassを継承したクラス2
class ChildClass2(ParentClass):
	def showName(self):
		"""
		showName関数を上書き
		"""
		print("Child2 name is ", self.name)

 また上記の内容を呼び出して実行するプログラムが以下の通りです。

# import文は省略

# メイン関数
def main():
	parent = ParentClass("Parent")
	parent.showName()

	child1 = ChildClass1("child1")
	child1.showName()
	child1.showChildName()

	child2 = ChildClass2("child2")
	child2.showName()

if __name__ == '__main__':
    main()

 上記を実行すると以下のような結果になります。
f:id:rikoubou:20190220162707p:plain

 2行目は「ChildClass1」からshowName関数を呼んだ結果が表示されています。ChildClass1自体にはshowName関数の記述は一切ありませんが、継承元の「ParentClass」に記述されているshowName関数が呼ばれているのがわかります。

 3行目は「ChildClass1」に新しく追加した関数であるshowChildName関数の結果が表示されています。

 4行目は「ChildClass2」で継承元の内容を上書きしたshowChildName関数の結果がちゃんと表示されています。


 以上がpythonでのクラスの継承方法です。

 似たような機能だけど少し違う、または元の機能にちょっとだけ追加するクラスが必要になった場合にはこの継承機能にお世話になることになると思います。


・参考資料

【python】単体テストの記述方法

rikoubou.hatenablog.com

 以前書いた記事でpythonのクラスとファイル分けをしました。

 今回はこの時作成したsumClass.pyを例として単体試験の書き方の備忘録という内容です。

 では始めます。


1:単体テスト用の書き方
 pythonにはデフォルトで単体試験用のクラスが用意されています。
 それを使用するには以下の一文で「unittest」をインポートします。

import unittest # 単体テストクラスインポート

 テスト内容の実装についてはunittest.TestCase のサブクラスとして作成し、その中に「test_」から始まるテスト関数を以下のように記述していきます。

import unittest # 単体テストクラスインポート

class TestSumClass(unittest.TestCase):
    def test_1(self):
        # テスト1の記述
    
    def test_2(self):
        # テスト2の記述
    
    # 以下略

 テストコードでの戻り値の確認などをする場合は以下のようなチェック関数があります。

# 第1引数と第2引数が同じ場合OK、違う場合NG
self.assertEqual('foo'.upper(), 'FOO')

# 引数の結果がTrueならOK、FalseならNG
self.assertTrue('FOO'.isupper())

# 引数の結果がFalseならOK、TrueならNG
self.assertFalse('Foo'.isupper())

 基本的には上記ぐらいが扱えれば特に問題はないかと思います。


2:単体テスト用のファイルの作成
 テスト用のファイルはどこでもいいのですが、前の記事で作成したmain.pyからsumClass.pyを呼び出しているので同じ階層に作るのが一番わかりやすくて良いかと思います。import文も呼び出しているところのコピペで済みます。
 今回は以下の場所に「test_sum.py」として単体テスト用ファイルを作成しました。
f:id:rikoubou:20190205172929p:plain


3:実際に単体テストを書いてみる
 単体テストの書き方がわかったので実際に「test_sum.py」を書いてみます。

 SumClassの中の「mySum」関数のテストコードを書いてみたのが以下になります。

#
# 単体テストコード
#
import unittest # 単体テストクラスインポート
from sum import sumClass as sc # SumClass読み込み

# テストのクラス
class TestSumClass(unittest.TestCase):
	"""
	test class of sumClass.py
	"""

	# テスト1
	def test_mySum1(self):
		sumInstance = sc.SumClass()
		result = sumInstance.mySum(10, 11)
		self.assertEqual(result, 21)

	# テスト2
	def test_mySum2(self):
		sumInstance = sc.SumClass()
		result = sumInstance.mySum(1, 11)
		self.assertEqual(result, 2)

if __name__ == '__main__':
    unittest.main()

 上記を実行すると「test_mySum1」についてはOKで「test_mySum2」についてはNGになります。
f:id:rikoubou:20190205174834p:plain

 一つでもNGとなった場合は、NGとなった関数名が出た後「FAILED(failures=[NG数])」としてNG数も表示されます。

 ちなみに全てOKだった場合は以下のように、OKとだけ表示されます。
f:id:rikoubou:20190205175114p:plain


 以上が簡単ですがpythonでの単体テストの記述方法です。

 複雑なものを作る場合には単体テストをやる必要が出てくるかと思うので、自分も記述するようにしていきたいです。


・参考資料