مكتبة OpenCV (الجزء السابع) : تحديد أماكن الوجه و تتبع الكائنات

1٬438

في هذا الدرس، سنتعلم كيفية إكتشاف الكائنات بإستخدام ال Haar Cascades.

في البداية سنتعرف على كيفية تحديد أماكن الوجه والعيون. للقيام بالتعرف على هذه الكائنات، تحتاج لملفات ال cascade. هذه الملفات موجودة بالفعل للمهام المشهورة كالتعرف على الوجوه، السيارات، الإبتسامة، العيون، لوحات السيارات، وغيرها.

سيتم توضيح كيفية استخدام هذه الملفات وبعد ذلك بناء الملفات الخاصة بنا لنقوم بالتعرف على أي كائن لا يوجد له هذه الملفات.

يمكنك إستخدام محرك بحث جوجل للبحث عن تلك الملفات. سنستخدم ال Face Cascade وال Eye Cascade وهما موجودان في الروابط التالية:

https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml

https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_eye.xml

لنبدأ في الكود. بعد تحميل هذه الملفات، سيكون لدينا ملفين بالأسماء التالية:

  • haarcascade_frontalface_default.xml
  • haarcascade_eye.xml
import numpy as np

import cv2

# multiple cascades: https://github.com/Itseez/opencv/tree/master/data/haarcascades

#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml

face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_eye.xml

eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

cap = cv2.VideoCapture(0)

:يمكننا بعد ذلك إلتقاط صور من الكاميرا وتحديد الوجوه والعيون بداخلها من خلال الكود التالي

while 1:

ret, img = cap.read()

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

faces = face_cascade.detectMultiScale(gray, 1.3, 5)

للمزيد عن دالة detectMultiScale، يمكنك قراءة ال documentation في الرابط التالي:

https://docs.opencv.org/2.4/modules/objdetect/doc/cascade_classification.html#cascadeclassifier-detectmultiscale

هي تقوم بالتعرف على الوجوه. لكننا نريد أيضاً أن نتعرف على العيون. لأن العيون تكون موجوده داخل الوجه، فعلينا التعرف علي الوجه في البداية وبعد تحديد مكانه يمكننا البحث عن العيون في هذا المكان. بإستخدام معلومات مثل لون الجلد وأماكن الحواجب يمكننا أن نقوم بتحديد أماكن العيون. فالخطوة التالية هي تقسيم الوجه قبل التعرف علي العيون.

for (x,y,w,h) in faces:

cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)

roi_gray = gray[y:y+h, x:x+w]

roi_color = img[y:y+h, x:x+w]

قمنا في الكود أعلى بتحديد مكان الوجه وحجمه ورسم مستطيل حوله لتحديد منطقة الهدف. بعد ذلك يمكننا تحديد أماكن العيون:

eyes = eye_cascade.detectMultiScale(roi_gray)

for (ex,ey,ew,eh) in eyes:

cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)

بعد التعرف على أماكن العيون، يمكننا رسم مستطيلان آخران لتحديد تلك العيون.

cv2.imshow('img',img)

k = cv2.waitKey(30) & 0xff

if k == 27:

break

cap.release()

cv2.destroyAllWindows()

الكود الكامل:

import numpy as np

import cv2

# multiple cascades: https://github.com/Itseez/opencv/tree/master/data/haarcascades

#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml

face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

#https://github.com/Itseez/opencv/blob/master/data/haarcascades/haarcascade_eye.xml

eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

cap = cv2.VideoCapture(0)

while 1:

ret, img = cap.read()

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

faces = face_cascade.detectMultiScale(gray, 1.3, 5)

for (x,y,w,h) in faces:

cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)

roi_gray = gray[y:y+h, x:x+w]

roi_color = img[y:y+h, x:x+w]



eyes = eye_cascade.detectMultiScale(roi_gray)

for (ex,ey,ew,eh) in eyes:

cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)

cv2.imshow('img',img)

k = cv2.waitKey(30) & 0xff

if k == 27:

break

cap.release()

cv2.destroyAllWindows()

الناتج:

D:\Work\Jisr Labs\3.OpenCV\16\jisrlabs-opencvtut16-fig-1.png

لاحظ أن الصورة ليست بها نظارة لأنها ستسبب بعض العقبات. الفم أيضاً في الكثير من الأحيان تم التعرف عليه علي أنه عين وفي مرات أخرى على أنه وجه. لون الجلد يسبب أيضاً العديد من المتاعب لأن تغيير الألوان يتسبب في عدم التعرف عليه.

أما لتتبع أي كائن object نريده عند بناء ال Haar Cascade، يجب توفر صور من الفئات الإيجابية positive والسلبية negative . الصور الإيجابية هي التي تحتوي على الكائن الذي نريده. بإستخدام تلك الصور، سنقوم ببناء ملف vector والذي يحتوي على كل هذه الصور. الميزة في هذا الصور أننا فقط يمكننا إستخدام صورة واحدة positive والآلاف من الصور ال negative.

بإستخدام الصورة ال positive، يمكننا إستخدام أمر opencv_createsamples لبناء العديد من الصور ال positive بإستخدام ال negative images. سنجعل الأمر بسيط بإستخدام صورة واحدة positive فقط وبعد ذلك بناء العديد من الصور ال negative.

الصورة ال positive:

D:\Work\Jisr Labs\3.OpenCV\17\jisrlabs-opencvtut17-fig-1.jpg

لا مشكلة في إستخدام صورة واحدة positive. يجب الآن الحصول علي الآلاف من الصور ال negative. في المستقبل، يمكننا إضافة أيضاً الآلاف من الصور ال positive. من قاعدة بيانات مثل ImageNet، يمكننا الحصول علي أي صور نريدها. نريد في هذا الدرس استخدام صور الساعات. إذا كنت تريد التعرف على كل الساعات، يمكننا إستخدام 50,000 صورة positive وأيضاً على الأقل 25,000 صورة negative.

نريد أن نجعل هذه الصور بنفس الحجم وأن يكون هذا الحجم صغير. يمكننا القيام بذلك بإستخدام script بسيط لمكتبة ال OpenCV. بإستخدام ال URL الخاص بالصور، يمكننا تحميلها، تغيير حجمها، وحفظها بالحجم الجديد. هذا سيتكرر لكل الصور. يمكنك تشغيل الكود بالأسفل.

import urllib.request

import cv2

import numpy as np

import os

def store_raw_images():

neg_images_link = '//image-net.org/api/text/imagenet.synset.geturls?wnid=n00523513'

neg_image_urls = urllib.request.urlopen(neg_images_link).read().decode()

pic_num = 1



if not os.path.exists('neg'):

os.makedirs('neg')



for i in neg_image_urls.split('\n'):

try:

print(i)

urllib.request.urlretrieve(i, "neg/"+str(pic_num)+".jpg")

img = cv2.imread("neg/"+str(pic_num)+".jpg",cv2.IMREAD_GRAYSCALE)

# should be larger than samples / pos pic (so we can place our image on it)

resized_image = cv2.resize(img, (100, 100))

cv2.imwrite("neg/"+str(pic_num)+".jpg",resized_image)

pic_num += 1



except Exception as e:

print(str(e))

سوف نلاحظ وجود العديد من الصور المفقودة بسبب أخطاء في التحميل. لا مشكلة. يمكننا تلاشي هذا الشيئ أو حل الأخطاء. يمكننا الإحتفاظ بالصور الخاطئة في مجلد إسمه على سبيل المثال ugly حتي لا يتم التأثير بالسلب على الصور الصحيحة.

def find_uglies():

match = False

for file_type in ['neg']:

for img in os.listdir(file_type):

for ugly in os.listdir('uglies'):

try:

current_image_path = str(file_type)+'/'+str(img)

ugly = cv2.imread('uglies/'+str(ugly))

question = cv2.imread(current_image_path)

if ugly.shape == question.shape and not(np.bitwise_xor(ugly,question).any()):

print('That is one ugly pic! Deleting!')

print(current_image_path)

os.remove(current_image_path)

except Exception as e:

print(str(e))

يمكننا أيضاً الحصول علي الصور ال negative من خلال الكود التالي:

def store_raw_images():

neg_images_link = '//image-net.org/api/text/imagenet.synset.geturls?wnid=n07942152'

neg_image_urls = urllib.request.urlopen(neg_images_link).read().decode()

pic_num = 953



if not os.path.exists('neg'):

os.makedirs('neg')



for i in neg_image_urls.split('\n'):

try:

print(i)

urllib.request.urlretrieve(i, "neg/"+str(pic_num)+".jpg")

img = cv2.imread("neg/"+str(pic_num)+".jpg",cv2.IMREAD_GRAYSCALE)

# should be larger than samples / pos pic (so we can place our image on it)

resized_image = cv2.resize(img, (100, 100))

cv2.imwrite("neg/"+str(pic_num)+".jpg",resized_image)

pic_num += 1



except Exception as e:

print(str(e))

الآن لدينا أكثر من 2000 صورة. الخطوة الأخيرة هي إنشاء ملف ال descriptor للصور ال negative كالتالي:

def create_pos_n_neg():

for file_type in ['neg']:



for img in os.listdir(file_type):

if file_type == 'pos':

line = file_type+'/'+img+' 1 0 0 50 50\n'

with open('info.dat','a') as f:

f.write(line)

elif file_type == 'neg':

line = file_type+'/'+img+'\n'

with open('bg.txt','a') as f:

f.write(line)

بعد تشغيل هذا الكود، سنحصل علي ملف bg.txt. نحتاج إلي إرسال الصور ال negative بالإضافة لهذا الملف إلي ال server. الملفات في ال server كالتالي:

 

opencv_workspace 

--neg 

----negimages.jpg 

--opencv 

--info 

--bg.txt 

--watch5050.jpg

من الممكن عدم وجود مسار info لكن يمكن إنشائه باستخدام أمر mkdir info.

يمكننا إنشاء العينات الإيجابية الآن باستخدام صورة watch5050.jpg باستخدام هذه الأمر وأنت داخل مسار ال workspace:

opencv_createsamples -img watch5050.jpg -bg bg.txt -info info/info.lst -pngoutput info -maxxangle 0.5 -maxyangle 0.5 -maxzangle 0.5 -num 1950

يقوم هذا الأمر بإنشاء عينات بناءً علي الصورة الإيجابية وال bg الذي يحمل معلومات عن الخلفية. ال pngoutput هو مكان تخزين العينات الجديدة. الآن استطعنا إنشاء حوالي 2000 صورة جديدة في مسار ال info. يوجد ملف اسمه info.list والذي يحمل الصور الإيجابية. هذا الملف يبدو كالتالي:

0001_0014_0045_0028_0028.jpg 1 14 45 28 28

لدينا اسم الملف، عدد الكائنات في الصورة، وأيضاً مكان هذه الكائنات. لدينا فقط كائن واحد. هذه هي إحدي الصور:

D:\Work\Jisr Labs\3.OpenCV\17\jisrlabs-opencvtut17-fig-2.jpg

الآن لدينا الصورة ال positive ونريد إنشاء ملف ال vector والذي يحمل كل الصور ال positive. سنستخدم ال opencv_createsamples مرة أخرى:

opencv_createsamples -info info/info.lst -num 1950 -w 20 -h 20 -vec positives.vec

نريد يعد ذلك تدريب ال cascade. لكن يجب تخزين المخرجات في مكان بإستخدام أمر mkdir data.

ال workspace يبدو كالتالي:

 

opencv_workspace 

--neg 

----negimages.jpg 

--opencv 

--info 

--data 

--positives.vec --bg.txt 

--watch5050.jpg

يمكننا الآن تشغيل أمر التدريب:

opencv_traincascade -data data -vec positives.vec -bg bg.txt -numPos 1800 -numNeg 900 -numStages 10 -w 20 -h 20

قمنا بتحديد مكان البيانات، ال vector file، ملف ال bg، عدد العينات الإيجابية والسلبية، عدد المراحل، العرض والإرتفاع.

10 مراحل تستغرق حوالي ساعتين بإستخدام 2GB من ال RAM. في النهاية ستحصل علي ملف cascade.xml. يمكننا تغيير إسمه إلي watchcascade10stage.xml حتي يعبر عن ما قمنا به.

import numpy as np

import cv2

face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

#this is the cascade we just made. Call what you want

watch_cascade = cv2.CascadeClassifier('watchcascade10stage.xml')

cap = cv2.VideoCapture(0)

while 1:

ret, img = cap.read()

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

faces = face_cascade.detectMultiScale(gray, 1.3, 5)



# add this

# image, reject levels level weights.

watches = watch_cascade.detectMultiScale(gray, 50, 50)



# add this

for (x,y,w,h) in watches:

cv2.rectangle(img,(x,y),(x+w,y+h),(255,255,0),2)

for (x,y,w,h) in faces:

cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)



roi_gray = gray[y:y+h, x:x+w]

roi_color = img[y:y+h, x:x+w]

eyes = eye_cascade.detectMultiScale(roi_gray)

for (ex,ey,ew,eh) in eyes:

cv2.rectangle(roi_color,(ex,ey),(ex+ew,ey+eh),(0,255,0),2)

cv2.imshow('img',img)

k = cv2.waitKey(30) & 0xff

if k == 27:

break

cap.release()

cv2.destroyAllWindows()

الناتج:

D:\Work\Jisr Labs\3.OpenCV\17\jisrlabs-opencvtut17-fig-3.png

ال boxes المحيطة بالساعة صغيرة. السبب أن صور التدريب كانت صغيرة وبحجم 20×20. يمكننا التدريب باستخدام حجم أكبر مثل 100×100. ستأخذ وقت أطول بالطبع لكن سيكون هناك ناتج أفضل.

حل آخر هو عدم إظهار box ولكن نص علي الساعة كالتالي:

font = cv2.FONT_HERSHEY_SIMPLEX

cv2.putText(img,'Watch',(x-w,y-h), font, 0.5, (11,255,255), 2, cv2.LINE_AA)

الناتج كالتالي:

D:\Work\Jisr Labs\3.OpenCV\17\jisrlabs-opencvtut17-fig-4.png


الدرس الأصلي:

https://pythonprogramming.net/haar-cascade-object-detection-python-opencv-tutorial

https://pythonprogramming.net/haar-cascade-face-eye-detection-python-opencv-tutorial

تعليقات