顔認証ゴール
pythonとopenCV使って顔認証。
完成形の画面には、PCのカメラのリアルタイム映像に人間の顔を認識した箇所が白い枠として出てくる。
加えて、青い正方形の枠が学習した顔を認識した箇所を示し、名前を上に出す。
開発環境
Macbook Air (M1チップ)
anaconda
ANACONDA.NAVIGATORでGUIを使った仮想環境を用いる。
M1チップでもRosetta2で普通に使える。
python 3.7.9
openCVが3.7以降だと失敗した気がする。
numpy 1.19.2
opencv 3.4.2
pillow 8.1.0
全体の流れ
2つのプログラムを書きました。
あとは不正解用画像を数十枚。
-
face_learner.py (学習用プログラム)
カメラを起動し学習用顔写真(正解画像)を撮影
学習用顔写真を傘増しする
学習したデータをxmlとして吐き出す -
face_id.py (認証用プログラム)
カメラを起動
人間の顔認識用xmlから当てはまる箇所に白い正方形を表示
自分の顔xmlから当てはまる箇所に青い正方形と設定した名前を表示
長いので一番最後にコード公開してます。
ファイル構成
Face_detectionディレクトリ
├ face_learner.py (学習用プログラム)
├ face_id.py (認証用プログラム)
├ pos
└ 撮った正解画像と傘増しした画像
├ pos.txt (正解画像のリスト)
├ pos.vec (正解画像の特徴量を示したベクトルデータ)
├ neg
└ 不正解画像
├ neg.txt (不正解画像のリスト)
├ haarcascade_frontalface_default.xml (人間の顔認識用xml)
├ cascade
└ 認証する人の顔認識用xml
不正解画像は自分で適当に集め、以下のようにlistを作成。
./neg/neg1.jpg
./neg/neg2.jpg
./neg/neg3.jpg
./neg/neg4.jpg
./neg/neg5.jpg
haarcascade_frontalface_default.xmlはgithubから持ってくる。
(opencv/data/haarcascades/haarcascade_frontalface_default.xml)
face_learner.pyで正解画像撮影/画像傘増し/学習をする。
実行するとカメラが起動し、自分の顔を撮影される。
終わるとcascadeフォルダにcascade.xmlが生成されるのでそれをトップディレクトリに移動させる。
face_id.pyで実際に認証を行なっている。
実行すると、カメラが起動し、青い正方形とその上に名前が出る。
コード公開
コードは参考程度に公開してます。
動くものを作ったお話なので汚さはご了承ください。
# ======================================================================
# Project Name : Face Identify
# File Name : face_lerner.py
# Encoding : utf-8
# Creation Date : 2021/02/20
# ======================================================================
import os
import re
import numpy as np
import time
import glob
import shutil
import PIL.Image
from PIL import ImageEnhance
import subprocess
import cv2
def takePic(cascade, picnum_max):
"""take pictures for learning
"""
cap = cv2.VideoCapture(0)
color = (255,255,255)
picture_num = 1
while True:
ret, frame = cap.read()
facerect = cascade.detectMultiScale(frame, scaleFactor=1.7, minNeighbors=4, minSize=(100,100))
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(frame, str(picture_num), (10,500), font, 4,(0,0,0),2,cv2.LINE_AA)
if len(facerect) > 0:
for (x,y,w,h) in facerect:
picture_name = './pos/pic' + str(picture_num) + '.jpg'
cv2.imwrite(picture_name, frame)
picture_num += 1
cv2.imshow("frame", frame)
if picture_num == picnum_max + 1:
break
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
def removePic():
"""remove files for initialize
"""
os.chdir('cascade')
for x in glob.glob('*.xml'):
os.remove(x)
os.chdir('../')
os.chdir('pos')
for x in glob.glob('*.jpg'):
os.remove(x)
os.chdir('../')
if os.path.exists('pos.txt'):
os.remove('pos.txt')
def countPic():
"""count a number of taken pictures
"""
files = os.listdir("./pos")
count = 0
for file in files:
count = count + 1
return count
def bulkOut():
"""Bult out pics
"""
# a number of taken pics
originalnum = countPic()
# a number of present total pics
imageNum = countPic()+1
# Flip horizontal
for num in range(1, originalnum + 1 ):
fileName = './pos/pic' + str(num) + '.jpg'
if not os.path.exists(fileName):
continue
img = cv2.imread(fileName)
yAxis = cv2.flip(img, 1)
newFileName = './pos/pic' + str(imageNum) + '.jpg'
cv2.imwrite(newFileName,yAxis)
imageNum += 1
print('*** Flip horizontal is finished *** \n')
# Change Saturation
SATURATION = 0.5
CONTRAST = 0.5
BRIGHTNESS = 0.5
SHARPNESS = 2.0
for num in range(1, 2 * originalnum + 1):
fileName = './pos/pic' + str(num) + '.jpg'
if not os.path.exists(fileName):
continue
img = PIL.Image.open(fileName)
saturation_converter = ImageEnhance.Color(img)
saturation_img = saturation_converter.enhance(SATURATION)
newFileName = './pos/pic' + str(imageNum) + '.jpg'
saturation_img.save(newFileName)
imageNum += 1
print('*** Change Saturation is finished *** \n')
# Change Contsract
for num in range(1, 3 * originalnum + 1):
fileName = './pos/pic' + str(num) + '.jpg'
if not os.path.exists(fileName):
continue
img = PIL.Image.open(fileName)
contrast_converter = ImageEnhance.Contrast(img)
contrast_img = contrast_converter.enhance(CONTRAST)
newFileName = './pos/pic' + str(imageNum) + '.jpg'
contrast_img.save(newFileName)
imageNum += 1
print('*** Change Constract is finished *** \n')
# Change Brightness
for num in range(1, 4 * originalnum + 1):
fileName = './pos/pic' + str(num) + '.jpg'
if not os.path.exists(fileName):
continue
img = PIL.Image.open(fileName)
brightness_converter = ImageEnhance.Brightness(img)
brightness_img = brightness_converter.enhance(BRIGHTNESS)
newFileName = './pos/pic' + str(imageNum) + '.jpg'
brightness_img.save(newFileName)
imageNum += 1
print('*** Change Brightness is finished *** \n')
# Change Sharpness
for num in range(1, 5 * originalnum + 1):
fileName = './pos/pic' + str(num) + '.jpg'
if not os.path.exists(fileName):
continue
img = PIL.Image.open(fileName)
sharpness_converter = ImageEnhance.Sharpness(img)
sharpness_img = sharpness_converter.enhance(SHARPNESS)
newFileName = './pos/pic' + str(imageNum) + '.jpg'
sharpness_img.save(newFileName)
imageNum += 1
print('*** Change Sharpness is finished *** \n')
# Rotate by 15 deg.
for num in range(1, 6 * originalnum + 1):
fileName = './pos/pic' + str(num) + '.jpg'
if not os.path.exists(fileName):
continue
# read original file
img = cv2.imread(fileName)
h, w = img.shape[:2]
size = (w, h)
# define angle to rotare
angle = 15
angle_rad = angle/180.0*np.pi
# caluclate a size of pic after rotation
w_rot = int(np.round(h*np.absolute(np.sin(angle_rad))+w*np.absolute(np.cos(angle_rad))))
h_rot = int(np.round(h*np.absolute(np.cos(angle_rad))+w*np.absolute(np.sin(angle_rad))))
size_rot = (w_rot, h_rot)
# rotate
center = (w/2, h/2)
scale = 1.0
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
# add translation)
affine_matrix = rotation_matrix.copy()
affine_matrix[0][2] = affine_matrix[0][2] -w/2 + w_rot/2
affine_matrix[1][2] = affine_matrix[1][2] -h/2 + h_rot/2
img_rot = cv2.warpAffine(img, affine_matrix, size_rot, flags=cv2.INTER_CUBIC)
cv2.imwrite(newFileName, img_rot)
newFileName = './pos/pic' + str(imageNum) + '.jpg'
saturation_img.save(newFileName)
imageNum += 1
print('*** Rotation by 15 deg. is finished *** \n')
# Rotate by -15 deg.
for num in range(1, 7* originalnum + 1):
fileName = './pos/pic' + str(num) + '.jpg'
if not os.path.exists(fileName):
continue
# read original file
img = cv2.imread(fileName)
h, w = img.shape[:2]
size = (w, h)
# define angle to rotare
angle = -15
angle_rad = angle/180.0*np.pi
# caluclate a size of pic after rotation
w_rot = int(np.round(h*np.absolute(np.sin(angle_rad))+w*np.absolute(np.cos(angle_rad))))
h_rot = int(np.round(h*np.absolute(np.cos(angle_rad))+w*np.absolute(np.sin(angle_rad))))
size_rot = (w_rot, h_rot)
# rotate
center = (w/2, h/2)
scale = 1.0
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
# add translation)
affine_matrix = rotation_matrix.copy()
affine_matrix[0][2] = affine_matrix[0][2] -w/2 + w_rot/2
affine_matrix[1][2] = affine_matrix[1][2] -h/2 + h_rot/2
img_rot = cv2.warpAffine(img, affine_matrix, size_rot, flags=cv2.INTER_CUBIC)
cv2.imwrite(newFileName, img_rot)
newFileName = './pos/pic' + str(imageNum) + '.jpg'
saturation_img.save(newFileName)
imageNum += 1
print('*** Rotation by -15 deg. is finished ***\n')
print('*** Bulking out is completed ***\n')
def generatePosFile(cascade):
"""make text file of face positions in pictures
"""
fpos = open('pos.txt', 'a')
for fileName in glob.glob('./pos/*.jpg'):
img = cv2.imread(fileName)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = cascade.detectMultiScale(gray)
for (x,y,w,h) in faces:
text = fileName + ' 1 ' + str(x) + ' ' + str(y) + ' ' + str(w) + ' ' + str(h) + '\n'
fpos.write(text)
print('*** making pos.txt is finished ***')
# user_fileName = input('Please input your fileName\n')
cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
picnum_max = input('please input a number of pictures to take\n')
# remove pos and files in pic/pos
removePic()
# start video and take pictures
takePic(cascade, int(picnum_max))
# count a number of picutres
bulkOut()
# make text file of face positions in pictures
generatePosFile(cascade)
subprocess.call('opencv_createsamples -info pos.txt -vec pos.vec -num ' + str(countPic()), shell=True)
posnum = input('please input a number of created pos\n')
subprocess.call('opencv_traincascade -data ./cascade -vec pos.vec -bg neg.txt -numPos ' + posnum + ' -numNeg 40', shell=True)
# ======================================================================
# Project Name : Face Identify
# File Name : face_id.py
# Encoding : utf-8
# Creation Date : 2021/02/22
# ======================================================================
import cv2
font = cv2.FONT_HERSHEY_SIMPLEX
if __name__ == "__main__":
cap = cv2.VideoCapture(0)
cascade_path_human = 'haarcascade_frontalface_default.xml'
cascade_path = 'cascade.xml'
cascade_human = cv2.CascadeClassifier(cascade_path_human)
cascade = cv2.CascadeClassifier(cascade_path)
while True:
ret, frame = cap.read()
facerect_human = cascade_human.detectMultiScale(frame, scaleFactor=1.7, minNeighbors=4, minSize=(100,100))
facerect = cascade.detectMultiScale(frame, scaleFactor=1.7, minNeighbors=4, minSize=(100,100))
if len(facerect_human) > 0:
for rect in facerect_human:
cv2.rectangle(frame, tuple(rect[0:2]), tuple(rect[0:2]+rect[2:4]), (255, 255, 255), thickness=2)
if len(facerect) > 0:
for rect in facerect:
cv2.rectangle(frame, tuple(rect[0:2]), tuple(rect[0:2]+rect[2:4]), (255, 0, 0), thickness=2)
cv2.putText(frame, 'name', tuple(rect[0:2]), font, 2,(0,0,0),2,cv2.LINE_AA)
cv2.imshow("frame", frame)
# quit with q key
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
以下のnameを自分の名前に変更すればOK。
cv2.putText(frame, 'name', tuple(rect[0:2]), font, 2,(0,0,0),2,cv2.LINE_AA)
まとめ
撮った写真40枚程度 (傘増し後1200枚程度)で、精度はともかくそれっぽい認識はできていた。
便利なライブラリを用いたので機械学習については何も学べていない。