مقدمة :
مع دخول العالم مرحلة رفع الحظر التدريجي عن مرافقه العامة ومؤسساته وإعاد تدوير عجلته الاقتصادية بعد تراجع انتشار فيروس كورونا ، ومع ذلك فأن رفع الحظر هذا لا يعني ان الفيروس قد انتهى ولم ينتشر مرة اخرى ، ولذلك لابد من تطبيق اجراءات متزامنة مع رفع الحظر من ارتداء الكمامات وكفوف اليد و الحفاظ على مسافة امان بين الاشخاص لضمان عدم انتشار هذا الفيروس مجددا ، في هذا المقال نقدم لكم نظام ذكاء صنعي لمراقبة مدى التزام الاشخاص بارتداء كمامات الوجه ، حيث يمكن ربط هذا النظام مع كاميرات موجودة في مؤسسة ما او جامعة معينة مع اضافة ميزة التعرف على هوية الاشخاص بإضافة قاعدة بيانات له و يتم تحرير مخالفة او انذار في حق اي شخص لم يلتزم بارتداء قناع الوجه.
سنناقش طريقين لاكتشاف الشخص أن كان يرتدي قناع الوجه ام لا و سنتعلم كيف يمكن تحقيق ذلك باستخدام الـDeep Learning .
خطوات انشاء النموذج بشكل عام :
- بناء نموذج مهمته تصنيف هل الشخص يضع قناع ام لا بواسطة Python وتدريبه على dataset باستخدام مكتبة TensorFlow وKeras .
- التأكد من كفاءة النموذج من خلال حساب ال Accuracy .
النموذج السابق يمكن تحقيقه بطريقتين :
- اكتشاف الأقنعة الموجودة في الصور .
- اكتشاف الأقنعة الموجودة في الفيديوهات .
خطوات انشاء النموذج بشكل خاص :
سنقسم المشروع الى قسمين مستقلين عن بعضهمها البعض بحيث كل قسم يمتلك خطواته الخاصة به :
- Training :هي عبارة عن مرحلة تدريب النموذج بحيث يتم فيها اولاً تحميل الـ dataset الخاصة يتصنف قناع الوجه (الوجه بقناع / بدون قناع) , ثم تدريب النموذج على هذه الداتا عندها نكون قد انتهينا من تصميممصنف قادر على معرفة هل الشخص قد يضع قناع ام لا , بعد ذلك نقوم بحفظ الموديل المدرب في الذاكرة .
- Deployment : في هذه المرحلة سنقوم باجراء تطبيق لهذا النموذج على صور اشخاص , حيث في البدابة نقوم باكتشاف الوجه ثم تصنيف الوجه اذا كان بقناع أو لا .
الان سنتعرف على dataset المستخدمة في تدريب الموديل.
هي عبارة عن 1376 صورة تنقسم الى قسمين :
- 690 صورة لوجوه تحوي قناع .
- 686 صورة لوجوه لاتحوي قناع.
وهدفنا من هذه الـ dataset كما ذكرنا سابقاً , تدريب نموذج تعلم عميق مخصص لإكتشاف الأشخاص الذي يرتدون قناع أو لا .
هيكل المشروع :
المجلد dataset يحوي على مجموعة البيانات التي سنستخدمها لتدريب واختبار النموذج ,يتألف هذا المجلد من مجلدين ,الأول مجلد يدعى with_mask يضم بداخله 690 صورة لوجوه تحوي قناع , المجلد الثاني يدعى without_mask يضم بداخله 686 صورة لوجوه لاتحوي قناع .
المجلد examples يحوي على ثلاثة أمثلة (الصور) لاستخدامها في اختبار نموذج كشف قناع الوجه face mask detector .
المجلد face_detector : يحوي على نموذج لكتشاف الوجه وهو نموذج مدرب بشكل مسبقpre-trained.
لدينا أيضا ثلاث ملفات Python :
- الملف train_mask_detector : يحوي هذا الملف على النموذج المستخدم لتصنيف قناع الوجه(الوجه بقناع / بدون قناع).
- الملف detect_mask_image : يقوم هذا الملف باكتشاف قناع الوجه في الصور ثابتة.
- الملف detect_mask_video : باستخدام الكاميرا الخاصة بك يقوم هذا الملف باكتشاف قناع الوجه على كل إطار متدفق من الكاميرا.
في البداية سنقوم بتثبيت الحزم (المكتبات) التي سنتعامل معها :
pip install tensorflow pip install sklearn pip install numpy pip install matplotlib pip install opencv-python pip install imutils pip install argparse pip install time
ملاحظة : حزمة os تأتي بشكل تلقائي مع ملف بايثون لا داعي لتثبيتها .
ملاحظة : في حال هذه المكتبات موجودة ضمن بيئة العمل الخاصة بك لا داعي لتثبيتها .
سنقوم بأخذ مثال عن تثبيت أحد المكتبات على سبيل المثال مكتبة Scikit-Learn
نقوم بفتح الـ terminal ونضع التعليمة بالشكل التالي :
C:\Users\ASUS>pip install sklearn
تحقيق COVID-19 face mask detector باستخدام Keras و TensorFlow :
(ملف train_mask_detector.py)
اولاً :
# import the necessary packages from tensorflow.keras.preprocessing.image import ImageDataGenerator from tensorflow.keras.applications import MobileNetV2 from tensorflow.keras.layers import AveragePooling2D from tensorflow.keras.layers import Dropout from tensorflow.keras.layers import Flatten from tensorflow.keras.layers import Dense from tensorflow.keras.layers import Input from tensorflow.keras.models import Model from tensorflow.keras.optimizers import Adam from tensorflow.keras.applications.mobilenet_v2 import preprocess_input from tensorflow.keras.preprocessing.image import img_to_array from tensorflow.keras.preprocessing.image import load_img from tensorflow.keras.utils import to_categorical from sklearn.preprocessing import LabelBinarizer from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report from imutils import paths import matplotlib.pyplot as plt import numpy as np import argparse import os
استيراد مكتبة TensorFlow و Keras لكي نقوم بما يلي :
- زيادة البيانات Data Augmentation .
- تحميل ال MobilNetV2 Classifier هو عبارة عن نموذج CNN تم تدريبه مسبقاً سنستخدمه في بناء النموذج الحالي .
- بناء شبكة Fully Connected .
- المعالجة المسبقة للبيانات Pre-Processing .
استيراد مكتبة Scikit-Learn لكي نقوم بطباعة تقرير عن أداء الموديل .
استيراد مكتبة matplotlib حتى نقوم برسم منحني التدريب بالاعتماد على تابع الخطأ .
ثانيا :
نعرف وسطاء سطر الأوامر command line arguments المطلوبة لتشغيل برنامجنا
قبل معرفة ما هي الوسطاء المطلوب تعريفها سنأخذ لمحة توضيحية بسيطة نوضح فيها مفهوم command line arguments
التعامل مع وسطاء سطر الأوامر هي مهارة أولية يجب أن تتعلم كيفية استخدامها ، خاصة إذا كنت تحاول تطبيق مفاهيم التعلم العميق أو معالجة الصور أو رؤية الحاسوبية المتقدمة computer vision,حيث هذه الوسطاء تعطي معلومات إضافية الى برنامجنا خلال زمن التشغيل runtime وهذا ما يسمح لنا أن نعطي برنامجنا مدخلات مختلفة على الفور دون تغيير الكود.
نقوم بتعريف الوسيط التالي (سنخرج عن مشروعنا قليلاً لتوضيح الفكرة السابقة) :
بفرض لدينا تعليمات التالية :
# import the necessary packages import argparse # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-n", "--name", required=True, help="name of the user") args = vars(ap.parse_args()) # display a friendly message to the user print("Hi there {}, it's nice to meet you!".format(args["name"]))
نقوم في البداية باستيراد المكتبة argparse التي تمكننا من تعريف وسطاء سطر الأوامر.
نقوم بإنشاء كائن ArgumentParser ونسميه ap .
نضيف argument حيث يمكن تحديد تسمية الوسيط باستخدام تسمية مختصرة –n أو تسمية طويلة –name , يمكن استخدام أي منهما في command line.
بالنسبة لـ required=Trueأي يجب اسناد قيمة للوسيط من سطر الأوامر.
السلسلة help تعطي معلومات مساعدة في terminal .
نطبق vars على الكائن لتحويل وسيط سطر الأوامر إلى قاموس Python (تذكرة : قاموس Python يحوي على مفتاح key وقيمة Value) حيث يكون المفتاح هنا هو اسم وسيط سطر الأوامر (سواء تسمية مختصرة أو طويلة) والقيمة هي القيمة المقدم لوسيط سطر الأوامر (سواء تم ادخال هذه القيمة من سطر الأوامر أو اعطيناه قيمة افتراضية سنذكر كيفية إعطاء قيمة افتراضية لوسيط لاحقا).
لإدخال قيمة الى الوسيط الذي عرفناه سابقا name نقوم بفتح الـ terminal والذهاب الى مسار الموجود فيه ملف python واسناد قيم الى المتحول كما نلاحظ في الشكل التالي حيث اعطينا البرنامج مدخلات مختلفة دون تغيير الكود في زمن التشغيل .
$ python simple_example.py --name Adrian Hi there Adrian, it's nice to meet you! $ $ python simple_example.py --name Stephanie Hi there Stephanie, it's nice to meet you! $ $ python simple_example.py --name YourNameHere Hi there YourNameHere, it's nice to meet you!
بالنسبة لإعطاء قيمة افتراضي للوسيط حيث يتم اسناد هذه القيمة له في حال عدم ادخال القيمة من سطر الأوامر بالشكل التالي :
ap.add_argument("-n", "--name", type=str, default="Mohammad", help="name of the user")
حيث type هي نوع الوسيط String , و default هي القيمة الافتراضية.
بالعودة الى مشروعنا اصبح من السهل علينا فهم الوسطاء التالية :
# construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-d", "--dataset", required=True, help="path to input dataset") ap.add_argument("-p", "--plot", type=str, default="plot.png", help="path to output loss/accuracy plot") ap.add_argument("-m", "--model", type=str, default="mask_detector.model", help="path to output face mask detector model") args = vars(ap.parse_args())
حيث :
dataset– : مسار الملف الذي يحوي dataset.
plot– : مسار حفظ الصورة المولدة باستخدام مكتبة matplotlib ,توضح هذه الصورة منحنى التدريب ومنحنى الدقة validation خلال عملية التدريب.
model– : مسار الذي سوف نحفظ فيه النموذج بعد الانتهاء من تدريبه .
ثالثا :
نعرف معاملات الموديل من معدل التعليم learning rate و epochs و batch size.
# initialize the initial learning rate, number of epochs to # train for, and batch size INIT_LR = 1e-4 EPOCHS = 20 BS = 32
سوف نقوم بتطبيق learning rate decay ولهذا السبب سمينا معدل التعلم INIT_LR .
رابعا :
# grab the list of images in our dataset directory, then # initialize the list of data (i.e., images) and class images #Block 1 print("[INFO] loading images...") imagePaths = list(paths.list_images(args["dataset"])) data = [] labels = [] # loop over the image paths for imagePath in imagePaths: #Block 2 # extract the class label from the filename label = imagePath.split(os.path.sep)[-2] #Block 3 # load the input image (224x224) and preprocess it image = load_img(imagePath, target_size=(224, 224)) image = img_to_array(image) image = preprocess_input(image) # update the data and labels lists, respectively data.append(image) labels.append(label) #Block 4 # convert the data and labels to NumPy arrays data = np.array(data, dtype="float32") labels = np.array(labels)
نقوم بقراءة جميع مسارات الصور الموجودة في ملف dataset , حيث أن المتحول imagePaths هو عبارة عن قائمة list يحوي مسارات الصور(مسارات الصور وليس صور) Block 1.
حلقة for من أجل كل مسار موجود في القائمة imagePaths
في البداية نقوم بقراءة اسم المجلد التي توضع فيه الصورة التي مسارها imagePath ونضعها في متحول label لمعرفة تسمية هذه الصورة (كما ذكرنا سابقا أن ملف dataset يحوي مجلدين هما with_mask و without_mask) الـ Block 2.
نقوم بتحميل الصورة باستخدام التابع load_img حيث نمرر له مسار الصورة والبعد المطلوب , نجعل هنا البعد 224×224 وذلك لأن النموذج الذي سنستخدمها يتعامل مع صور ملونة بأبعاد 224×224 بيكسل , Block 3 .
استخدام التابع img_to_array لتحويل الصورة من نمط بايثون للصور (Python Imaging Library (PIL إلى مصفوفة بايثون العددية .
استخدام التابع preprocess_input وذلك لتحويل مجال قيم بكسلات صورة من 0-255 إلى 0-1 وهذا يفيد في تسريع عملية التدريب .
نقوم بإضافة صورة و تسميتها المقابلة كلاً منهما في list .
تحويل كلاً من المصفوفتين data و labels من مصفوفة بايثون العددية إلى مصفوفة NumPy , الـ Block 4.
خامساً :
#Block 1 # perform one-hot encoding on the labels lb = LabelBinarizer() labels = lb.fit_transform(labels) labels = to_categorical(labels) #Block 2 # partition the data into training and testing splits using 80% # of the data for training and the remaining 20% for testing (trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.20, stratify=labels, random_state=42) #Block 3 # construct the training image generator for data augmentation aug = ImageDataGenerator( rotation_range=20, zoom_range=0.15, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.15, horizontal_flip=True, fill_mode="nearest")
LabelBinarizer يقوم بتعيين رقم فريد لكل تسمية مختلفة فعلى سبيل المثال يقوم بتعين القيمة 0 لفئة الخرج without_mask والقيمة 1 لفئة الخرج with_mask.
يقوم التابع to_categorical بترميز القيم الفئوية كمصفوفة one hot encoding
وبالتالي تحويل كل قيمة في الخرج الى شعاع يحوي على القيمة 1 وما تبقى من القيم هي صفرية, Block 1.
تقسم البيانات الى 80% كبيانات تدريب و 20 % كبيانات اختبار باستخدام مكتبة Sklearn, الـ Block 2.
ثم توليد بيانات صور جديدة اعتماداً على بيانات الصور القديمة من خلال عمل مؤثرات على الصور مثل تقريب, تدوير ,ازاحة , انعكاس افقي او عمودي وهذا الخطوة تسمى بالـ Data Augmentation وتساهم هذه الخطوة في عملية الـ generalization للنموذح مما يجعله اكثر كفاءة , Block 3 .
سادساً:
#Block 1 # load the MobileNetV2 network, ensuring the head FC layer sets # are left off baseModel = MobileNetV2(weights="imagenet", include_top=False, input_tensor=Input(shape=(224, 224, 3))) #Block 2 # construct the head of the model that will be placed on top of # the base model headModel = baseModel.output headModel = AveragePooling2D(pool_size=(7, 7))(headModel) headModel = Flatten(name="flatten")(headModel) headModel = Dense(128, activation="relu")(headModel) headModel = Dropout(0.5)(headModel) headModel = Dense(2, activation="softmax")(headModel) #Block 3 # place the head FC model on top of the base model (this will be # come the actual model we will train) model = Model(inputs=baseModel.input, outputs=headModel) #Block 4 # loop over all layers in the base model and freeze them so they # will *not* be updated during the first training process for layer in baseModel.layers: layer.trainable = False
تحميل النموذج MobileNet وهو عبارة عن شبكة CNN تم تدريبها مسبقاً على مجموعة البيانات ImageNet مع الاخد بعين الاعتبار يتم تجاهل طبقة الخرج include_top=False , الـ Block 1.
بناء طبقة خرج جديدة توافق الصور الموجودة في dataset الخاصة بالنموذج COVID-19 face mask detector , الـ Block 2.
قمنا بإزالة طبقة الخرج لنموذج MobileNet لأن هذا النموذج مدرب بشكل مسبق على الف نوع من صور (صور قط , كلاب , إشارات مرور . . .الخ ) لذلك يقوم بتصنيف الصورة المدخلة إليه الى صنف واحد من بين الف صنف (أي طبقة الخرج تحوي الف عصبون ) وهذا مالا يتوفق مع نموذجنا لأننا نريد في هذا النموذج تصنيف الصورة المدخلة الى صنفين رئيسين أما with_mask أو without_mask أي جعل طبقة الخرج تحوي عصبونين رئيسين
وهذا ما يعرف بنمط “Transfer Learning”
وتعد هذه طريقة شائعة في مجال Deep Learning نظراً للموارد الهائلة والوقت الكثير اللازم لتطوير نماذج الشبكات العصبية المطلوبة لمثل هذه المهام
ثلاث فوائد لاستخدام “Transfer Learning “
- توفير الوقت اللازم في بناء النموذج (حيث كما رأينا سابقا أننا قمنا ببناء طبقة خرج وطبقة خفية قبلها فقط).
- لا نحتاج الى كمية كبيرة من البيانات.
- توفير الوقت اللازم لتدريب النموذج حيث يبدأ النموذج في اول epoch أثناء التدريب بدقة عالية.
قمنا بدمج طبقة الخرج التي تم بناؤها مع شبكة الـ Mobile Net التي تم تحميلها فينتج لدينا شبكة جديدة طبقة الخرج فيها هي طبقة تم بناؤها اما طبقة الدخل هي نفسها .بذلك قد استفدنا من قيم الأوزان المثالية لطبقة الدخل , Block 3.
عملية تدريب الشبكة ككل تعتمد ع الـ backpropagation اي تعديل الأوزان بناء على على تابع الخطأ cost Function ولدينا اوزان طبقة دخل تم ضبطها مسبقاً لذلك نقوم بتجميد هذه الاوزان وإيقاف تعديلها اثناء عملية Backpropagation , الـ Block 4 .
سابعاً :
#Block 1 # compile our model print("[INFO] compiling model...") opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS) model.compile(loss="binary_crossentropy", optimizer=opt, metrics=["accuracy"]) #Block 2 # train the head of the network print("[INFO] training head...") H = model.fit( aug.flow(trainX, trainY, batch_size=BS), steps_per_epoch=len(trainX) // BS, validation_data=(testX, testY), validation_steps=len(testX) // BS, epochs=EPOCHS)
تعريف خصائص الموديل :
تابع الخطأ حيث استخدمنا هنا تابع الخطأ binary crossentropy لان لدينا صنفين من البيانات فقط , والمحسن الذي استخدمناه هو محسن آدم Adam Optimizer ,وطريقة قياس اداء الموديل هي accuracy , الـ Block 1 .
تدريب الموديل وهنا عملية التدريب فقط تتضمن تعديل أوزان طبقة الخرج , Block 2.
استخدمنا هنا اثناء عملية التدريب مفهوم Validation وهو مفهوم مفيد لتحقق من كفاءة النموذج أثناء عملية التدريب ومنعه من الوقوع في مشكلة overfitting , ولابد من التنويه الى أمر مهم جداً أن مجموعة البيانات validation dataset فقط مستخدمة لتحقق من كفاءة النموذج على الرغم من استخدامها خلال مرحلة التدريب الا أنه لا يتم استخدامها ابدا لعملية تعديل الاوزان.
ملاحظة : في نموذجنا هذا جعنا بيانات Validation هي نفسها بيانات الاختبار (أي test dataset )نظراً لقلة البيانات لدينا , لكن في حال توفر بيانات كثيرة لابد من تقسيم البيانات الى ثلاثة اقسام القسم الاول للتدريب training dataset والثاني للتحقق من كفاءة التدريب validation dataset اما الثالث هو جزء منفصل تماما عن عملية التدريبtest dataset وهذا الجزء يستخدم لمرحلة اختبار كفاءة النموذج النهائية قبل تعميمه (أي اختبار النموذج على بيانات لم يراها من قبل ).
ثامناً :
#Block 1 # make predictions on the testing set print("[INFO] evaluating network...") predIdxs = model.predict(testX, batch_size=BS) # for each image in the testing set we need to find the index of # the label with corresponding largest predicted probability predIdxs = np.argmax(predIdxs, axis=1) # show a nicely formatted classification report print(classification_report(testY.argmax(axis=1), predIdxs, target_names=lb.classes_)) #Block 2 # serialize the model to disk print("[INFO] saving mask detector model...") model.save(args["model"], save_format="h5")
بعد اكتمال عملية التدريب سنقوم بتقييم النموذج الناتج على بيانات الإختبار Test dataset , الـ Block 1.
القيام بعملية حفظ الموديل , Block 2.
تاسعاً :
# plot the training loss and accuracy N = EPOCHS plt.style.use("ggplot") plt.figure() plt.plot(np.arange(0, N), H.history["loss"], label="train_loss") plt.plot(np.arange(0, N), H.history["val_loss"], label="val_loss") plt.plot(np.arange(0, N), H.history["accuracy"], label="train_acc") plt.plot(np.arange(0, N), H.history["val_accuracy"], label="val_acc") plt.title("Training Loss and Accuracy") plt.xlabel("Epoch #") plt.ylabel("Loss/Accuracy") plt.legend(loc="lower left") plt.savefig(args["plot"])
بالنهاية نقوم برسم منحنى التدريب ومنحنى الدقة ومن خلالهما يمكن معرفة حالة النموذج اذا كان يعاني من مشاكل معينة ام لا .
بعد ان انتهينا من عملية بناء الموديل سنقوم بعملية التدريب لإختبار كفاءته .
حيث نقوم بفتح terminal وننفذ الامر التالي :
$ python train_mask_detector.py --dataset dataset
كما نلاحظ ان معدل الـ Accuracy لـ validation set تقريبا 99 % وتعتبر نسبة عالية , ويوجد اشارات بسيطة تدل على ظهور الـ overfitting لأن لدينا قيمة تابع الخطأ لبيانات لـ Validation هي اقل بشيء بسيط من قيمة تابع الخطأ لبيلتات لـ Training . من خلال هذه النتائج نستنتج ان الموديل هو قابل للتعميم من اجل اي دخل .
تحقيق COVID-19 face mask detector للصور باستخدام OpenCV :
(ملف detect_mask_image.py)
اولاً :
# import the necessary packages from tensorflow.keras.applications.mobilenet_v2 import preprocess_input from tensorflow.keras.preprocessing.image import img_to_array from tensorflow.keras.models import load_model import numpy as np import argparse import cv2 import os
نستدعي المكتبات السابقة.
ثانياً :
# construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", required=True, help="path to input image") ap.add_argument("-f", "--face", type=str, default="face_detector", help="path to face detector model directory") ap.add_argument("-m", "--model", type=str, default="mask_detector.model", help="path to trained face mask detector model") ap.add_argument("-c", "--confidence", type=float, default=0.5, help="minimum probability to filter weak detections") args = vars(ap.parse_args())
image — : مسار الصورة المدخلة المطلوب تصنيفها هل تحوي قناع ام لا .
face –:مسار النموذج المستخدم لاكتشاف الوجوه في الصورة .
model –: مسار النموذج الذي قُمنا ببنائه لتصنيف قناع الوجه (الوجه بقناع / بدون قناع). .
confidence– : عتبة احتمالية اختيارية threshold لتخلص من اكتشافات الوجوه الضعيفة.
ثالثاً :
#Block 1 # load our serialized face detector model from disk print("[INFO] loading face detector model...") prototxtPath = os.path.sep.join([args["face"], "deploy.prototxt"]) weightsPath = os.path.sep.join([args["face"], "res10_300x300_ssd_iter_140000.caffemodel"]) net = cv2.dnn.readNet(prototxtPath, weightsPath) #Block 2 # load the face mask detector model from disk print("[INFO] loading face mask detector model...") model = load_model(args["model"])
تحميل نموذج اكتشاف الوجوه من OpenCV وهو نموذج pre-trained , الـ Block 1 .
حيث يعتمد على إطار (SSD (Single Shot MultiBox Detector ، باستخدام نموذج ResNet-10.
ملاحظة هامة : نموذج اكتشاف الوجه يختلف عن نموذج تصنيف قناع الوجه (الوجه بقناع / بدون قناع) الذي دربناه سابقا ,أي بشكل مختصر آلية العمل كالتالي في البداية نقوم باكتشاف جميع الأوجه الموجودة في صورة معينة (قد تحوي الصورة اكثر من وجه) باستخدام نموذج اكتشاف الوجه ,ثم نقوم بعمل قص لصورة الوجه الموجودة في الصورة الاصلية ونمرر هذه الصورة ( الوجه المقصوص) الى نموذج تصنيف قناع الوجه لمعرفة اذا كان الوجه يحوي قناع أم لا ثم نقوم بعد ذلك برسم مستطيل حول الوجه ,وهذا سيتم توضيحه برمجيا.
بالنسبة لملف prototxt يعرف بنية النموذج أي ترتيب الخلايا العصيبة في كل طبقة, وانماط الاتصال بين الطبقات ,ووظائف التنشيط , وكيفية تحويل الشبكة لمدخلاتها الى مخرجات .
الملفcaffemodel يحوي على اوزان الطبقات الفعلية.
تحميل نموذج اكتشاف قناع الوجه , Block 2.
رابعاً :
#Block 1 # load the input image from disk, clone it, and grab the image spatial # dimensions image = cv2.imread(args["image"]) orig = image.copy() (h, w) = image.shape[:2] #Block 2 # construct a blob from the image blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), (104.0, 177.0, 123.0)) #Block 3 # pass the blob through the network and obtain the face detections print("[INFO] computing face detections...") net.setInput(blob) detections = net.forward()
عند القيام بتحميل الصورة من الذاكرة يتم اخد نسخة منها من اجل الاستفادة منها لاحقاً , Block 1 .
التابع blobFromImage يقوم بتغيير حجم الصورة Image لتصبح 300*300 لكي تتوافق هذه الصورة مع نموذج اكتشاف الوجه caffemodel وذلك لأن هذا النموذج يقبل صورة دخل بأبعاد 300×300 بيكسل
بالنسبة للقيم (104.0, 177.0, 123.0) هي عبارة عن Mean subtraction
سنتحدث قليلاً عن هذا المفهوم :
متوسط الطرح Mean subtraction يستخدم هذا المفهوم للمساعدة في معالجة تغيرات الإضاءة في الصور المدخلة , لذلك يمكن اعتبرها تقنية تستخدم لمساعدة الشبكات العصبونية التلافيفية.
حيث قبل البدء في تدريب الشبكة العصبونية ، نقوم أولاً بحساب متوسط كثافة البكسل لكل طبقة ( الحمراء والخضراء والزرقاء) لجميع الصور الموجودة في dataset.
هذا يعني أننا ننتهي بثلاثة متغيرات:
µR , µG , µB
عندما نمرير صورة عبر شبكة (سواء للتدريب أو الاختبار) ، فإننا نطرح المتوسط من كل قيم البكسلات في الصورة المدخلة بالشكل التالي :
R = R – µR
G = G – µG
B = B – µB
ملاحظة هامة : القيم المتوسطة لمجموعة بيانات تدريب ImageNet هي R = 103.93 و G = 116.77 و B = 123.68 , وبالتالي سوف تستخدم هذه القيم إذا كنت تستخدم شبكة تم تدريبها مسبقًا على بيانات ImageNet , وهذا سبب وضعنا للقيم (104.0, 177.0, 123.0) في التابع blobFromImage.
وقد يكون لدينا عامل scale (سيجما ) والذي يضاف في Normalization
R = (R – µR) / σ
G = (G – µG) / σ
B = (B – µB) / σ
يستخدم هذا العامل لتحجيم قيم بكسلات الصورة المدخلة إلى نطاق معين
ستجد في اغلب الحالات يتم تنفيذ عملية الطرح فقط (وبالتالي إعداد sigma = 1.0)
الوسيط الثاني لمتحول blobFromImage هو scale factor حيث مررنا القيمة 1.0 أي قمنا بتنفيذ عملية الطرح فقط .
ملاحظة هامة : يقوم تابع blobFromImage بشكل تلقائي يتحويل الصورة الملونة من الترتيب RGB الى ترتيب BGR وذلك لان مكتبة OpenCV تتعامل مع صور الملونة بالترتيب BGR
التابع setInput لتطبيق تنسيق blob على الصورة المدخلة .
التابع forward يقوم بعملية التنبؤ بجميع الوجوه في الصورة للقيام بتصنيفها فينا بعد , Block.
حيث خرج النموذج (أي detections) هو مصفوفة 4D حيث :
البعد الثالث يمثل الوجوه المكتشفة .
البعد الرابع يعطي معلومات عن احداثيات الـ bounding box والاحتمالية confidence على سبيل المثال [detections[0,0,0,2 تعطي قيمة confidence للوجه الأول و [detections[0,0,0,3:6 يعطي احداثيات bounding box للوجه الاول
مثال اخر [:,detections[0,0,2 يعطي معلومات عن الوجه الثالث المكتشف
ملاحظة : الـ condidence هي احتمالية أن يطابق bounding box الوجه .
خامساً :
# loop over the detections for i in range(0, detections.shape[2]): # extract the confidence (i.e., probability) associated with the detection confidence = detections[0, 0, i, 2] # filter out weak detections by ensuring the confidence is # greater than the minimum confidence if confidence > args["confidence"]: #Block 1 # compute the (x, y)-coordinates of the bounding box for the object box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) (startX, startY, endX, endY) = box.astype("int") #Block 2 # ensure the bounding boxes fall within the dimensions of # the frame (startX, startY) = (max(0, startX), max(0, startY)) (endX, endY) = (min(w - 1, endX), min(h - 1, endY))
حلقة for تمر على جميع الاحتماليات لتوقع وجود الوجوه في الصورة
تعليمة if لتصفية الاحتماليات الضعيفة التي تقل عن حد العتبة threshold
خرج احداثيات bounding box مطبق عليها Normalization أي مجال قيمها [0،1], وبالتالي يجب ضرب هذه الاحداثيات في طول وعرض الصورة الأصلية للحصول على bounding box الصحيح على الصورة ,Block 1 .
التحقق اذا كان ال bounding box لكل وجه هو داخل حدود الصورة , Block 2 .
سادساً :
# extract the face ROI, convert it from BGR to RGB channel # ordering, resize it to 224x224, and preprocess it face = image[startY:endY, startX:endX] face = cv2.cvtColor(face, cv2.COLOR_BGR2RGB) face = cv2.resize(face, (224, 224)) face = img_to_array(face) face = preprocess_input(face) face = np.expand_dims(face, axis=0) #Block 1 # pass the face through the model to determine if the face # has a mask or not (mask, withoutMask) = model.predict(face)[0]
استخراج ROI من الصورة باستخدام مكتبة numpy وفي مثالنا ال ROI هو عبارة عن صورة الوجه .
التابع cvtColor يقوم بتحويل صورة الوجه من BGR الى RGB بالتبديل بين قناتي R و B في الصورة.
التابع preprocess_input لقيام بعملية Pre-process لـ ROI .
التابع expand_sims يستخدم هذا التابع لتوسيع بعد المصفوفة حيث أن صورة الوجه face هي ثلاث أبعاد
ونموذج تصنيف قناع الوجه (الوجه بقناع / بدون قناع) يتوقع الدخل لصورة ملونة رباعية الابعاد 224×224 بيكسل , لذلك نقوم بإضافة بعد رابع لصورة الوجهface .
القيام بعملية التنبؤ لمعرفة هل الوجه يحوي قناع ام لا , Block 1 .
سابعاً :
#Block 1 # determine the class label and color we'll use to draw # the bounding box and text label = "Mask" if mask > withoutMask else "No Mask" color = (0, 255, 0) if label == "Mask" else (0, 0, 255) #Block 2 # include the probability in the label label = "{}: {:.2f}%".format(label, max(mask, withoutMask) * 100) #Block 3 # display the label and bounding box rectangle on the output frame cv2.putText(image, label, (startX, startY - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.45, color, 2) cv2.rectangle(image, (startX, startY), (endX, endY), color, 2) #Block 4 # show the output image cv2.imshow("Output", image) cv2.waitKey(0)
تحديد اذا كان الوجه يحوي قناع او لا بناءً على القيمة المتوقعة من النموذج الـ mask detector ,
واذا كان الوجه يحوي قناع يتم اختبار اللون الأخضر , اما اذا كان الوجه لايحوي قناع يتم اختيار اللون الأحمر , Block 1.
حساب نسبة احتمال توقع الموديل , Block 2 .
وضع نص على الصورة يعبر عن احتمال توقع النموذج ,بالإضافة الى وضع اطار على شكل مستطيل حول الوجه يتم ذلك باستخدام مكتبة openCv , الـ Block 3 .
لرسم مستطيل باستخدام مكتبة الـ OpenCV يجب تحديد زاوية المستطيل العليا اليسرى وزاويته السفلى اليمنى
البارومتر الأول لتابع رسم المستطيل هو لصورة أما بالنسبة لبارامتر الثاني والثالث هما إحداثيات زاوية المستطيل العليا اليسرى والسفلى اليمنى
البارامتر الثالث هو لون خط المستطيل بالنسبة لـ BRG فأننا نمرر صف عل سبيل المثال :
نمرر الصف (255,0,0) بالنسبة للون الأزرق
ملاحظ : كما قلنا سابقا بأن مكتبة OpenCV تتعامل مع صور الملونة بأخذ الألوان بالترتيب BGR.
البارامتر الرابع هو لسماكت خط المستطيل
حيث يقوم هذا التابع بتعديل الصورة واضافة المستطيل لها
لأضافه نص للصورة علينا تحديد :
النص الذي نريد إدراجه , موضع النص بالنسبة للصورة (الزاوية السفلى اليسرى ) ,نوع الخط ,عامل مقياس الخط (الذي يتم ضربه في الحجم الأساسي للخط), اللون , السماكة.
اظهار الصورة , Block 4 .
عملية تجريب COVID-19 face mask detector باستخدام OpenCV على صور مختلفة:
1) تجربة النموذج على الصورة الأولى الموجودة ضمن مجلد examples :
نقوم بتحميل الصورة الأولى من خلال المسار الخاص بها
$ python detect_mask_image.py --image examples/example_01.png
النتيجة :
نلاحظ ان الموديل face mask detector قد نجح في عملية التنبؤ ( الشخص يضع قناع ) .
2)تجربة النموذج على الصورة الثانية :
تحميل الصورة الثانية من خلال المسار الخاص بها .
$ python detect_mask_image.py --image examples/example_02.png
النتيجة :
نلاحظ ان النموذج face mask detector قد نجح في عملية التنبؤ ( الشخص لايضع قناع ).
3) تجربة النموذج على الصورة الثالثة :
تحميل الصورة الثالثة من خلال المسار الخاص بها .
$ python detect_mask_image.py --image examples/example_03.png
النتيجة :
هنا قد حدثت مشكلة في النموذج نلاحظ أن الشخصين الذين يقفان في الخلف قد نجح النموذج بتنبأ انهما لا يرتديان أقنعة ولكن الفتاة التي تقف في الأمام تم تجاهلها من قبل النموذج وسبب ذلك ان النموذج ينقسم الى قسمين :
1) مرحلة اكتشاف الوجه .
2) مرحلة تصنيف هل الوجه يحوي كمامة ام لا .
المشكلة التي حصلت ان النموذج لم يستطع التعرف على الوجه اولا ً كما نلاحظ ان حجم الكمامة كبير جداً وتكاد تغطي معظم معالم الوجه .
ولحل هذه المشكلة يجب ان يتم تدريب النموذج على صور وجوه يوجد عليها أقنعة باحجام كبيرة .
نتيجة تنفيذ الموديل على صورة لي :
نلاحظ ان النموذج قد نجح في عملية التنبؤ (لا أضع قناع ).
تنفيذ تطبيق كاشف قناع الوجه في الوقت الفعلي عبر فيديو مباشر باستخدام مكتبة OpenCV :
(ملف detect_mask_video.py)
نحن نعلم كيف يمكن تطبيق كاشف قناع الوجه على صورة ثابتة ولكن ماذا عن تطبيقه عبر فيديو مباشر ؟
الخوارزمية التي نستخدمها لتطبيق كاشف قناع الوجه عبر فيديو (أو كاميرا) مباشر هي الخوارزمية ذاتها المستخدمة في تطبيق كاشف قناع الوجه في الصور ولكن مع اضافة Methods تسمح لنا بمعالجة كل frame من الفيديو بشكل مستقل وذلك عن طريق استدعاء الصف VideoStream والصف time .
حيث في البدية نقوم باستيراد المكتبات التالية :
# import the necessary packages from tensorflow.keras.applications.mobilenet_v2 import preprocess_input from tensorflow.keras.preprocessing.image import img_to_array from tensorflow.keras.models import load_model from imutils.video import VideoStream import numpy as np import argparse import imutils import time import cv2 import os
يقوم التابع التالي باكتشاف الوجوه الموجودة في الصورة أولا , ثم تطبق مصنف قناع الوجه على كلROL .
def detect_and_predict_mask(frame, faceNet, maskNet): # grab the dimensions of the frame and then construct a blob # from it (h, w) = frame.shape[:2] blob = cv2.dnn.blobFromImage(frame, 1.0, (300, 300), (104.0, 177.0, 123.0)) # pass the blob through the network and obtain the face detections faceNet.setInput(blob) detections = faceNet.forward() # initialize our list of faces, their corresponding locations, # and the list of predictions from our face mask network faces = [] locs = [] preds = [] # loop over the detections for i in range(0, detections.shape[2]): # extract the confidence (i.e., probability) associated with # the detection confidence = detections[0, 0, i, 2] # filter out weak detections by ensuring the confidence is # greater than the minimum confidence if confidence > args["confidence"]: # compute the (x, y)-coordinates of the bounding box for # the object box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) (startX, startY, endX, endY) = box.astype("int") # ensure the bounding boxes fall within the dimensions of # the frame (startX, startY) = (max(0, startX), max(0, startY)) (endX, endY) = (min(w - 1, endX), min(h - 1, endY)) # extract the face ROI, convert it from BGR to RGB channel # ordering, resize it to 224x224, and preprocess it face = frame[startY:endY, startX:endX] face = cv2.cvtColor(face, cv2.COLOR_BGR2RGB) face = cv2.resize(face, (224, 224)) face = img_to_array(face) face = preprocess_input(face) face = np.expand_dims(face, axis=0) # add the face and bounding boxes to their respective # lists faces.append(face) locs.append((startX, startY, endX, endY)) # only make a predictions if at least one face was detected if len(faces) > 0: # for faster inference we'll make batch predictions on *all* # faces at the same time rather than one-by-one predictions # in the above `for` loop preds = maskNet.predict(faces) # return a 2-tuple of the face locations and their corresponding # locations return (locs, preds)
هذا التابع يأخذ ثلاث وسطاء :
frame : الاطار المتدفق من الكاميرا .
faceNet : النموذج المستخدم لاكتشاف الوجوه من صورة .
maskNet : نموذج تصنيف قناع الوجه .
حيث يعيد هذا التابع قائمتين Two lists القائمة الأولى تمثل احداثيات الـ bounding box للوجوه المكتشفة يقابلها تصنيف كل وجه (قناع / دون قناع ) في القائمة preds.
كما ذكرنا سابقا أن الخوارزمية في هذا الملف هي نفسها في الملف السابق ، ولكن يتم تجميعها بطريقة تسمح بمعالجة كل إطار متدفق من الكاميرا ,وبالتالي لن نعيد شرح هذه التعليمات فهي مشروحة سابقا.
# construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-f", "--face", type=str, default="face_detector", help="path to face detector model directory") ap.add_argument("-m", "--model", type=str, default="mask_detector.model", help="path to trained face mask detector model") ap.add_argument("-c", "--confidence", type=float, default=0.5, help="minimum probability to filter weak detections") args = vars(ap.parse_args())
وسطاء سطر الأوامر هي :
face — : مسار نموذج اكتشاف الوجه.
model –: مسار نموذج تصنيف قناع الوجه (قناع/بدون قناع).
confidence– : عتبة احتمالية threshold.
نقوم باستيراد للنماذج السابقة :
# load our serialized face detector model from disk print("[INFO] loading face detector model...") prototxtPath = os.path.sep.join([args["face"], "deploy.prototxt"]) weightsPath = os.path.sep.join([args["face"], "res10_300x300_ssd_iter_140000.caffemodel"]) faceNet = cv2.dnn.readNet(prototxtPath, weightsPath) # load the face mask detector model from disk print("[INFO] loading face mask detector model...") maskNet = load_model(args["model"]) # initialize the video stream and allow the camera sensor to warm up print("[INFO] starting video stream...") vs = VideoStream(src=0).start() time.sleep(2.0)
لدينا الحلقة التالية الخاصة بالإطارات المتدفقة من الكاميرا :
# loop over the frames from the video stream while True: # grab the frame from the threaded video stream and resize it # to have a maximum width of 400 pixels frame = vs.read() frame = imutils.resize(frame, width=400) # detect faces in the frame and determine if they are wearing a # face mask or not (locs, preds) = detect_and_predict_mask(frame, faceNet, maskNet) # loop over the detected face locations and their corresponding # locations for (box, pred) in zip(locs, preds): # unpack the bounding box and predictions (startX, startY, endX, endY) = box (mask, withoutMask) = pred # determine the class label and color we'll use to draw # the bounding box and text label = "Mask" if mask > withoutMask else "No Mask" color = (0, 255, 0) if label == "Mask" else (0, 0, 255) # include the probability in the label label = "{}: {:.2f}%".format(label, max(mask, withoutMask) * 100) # display the label and bounding box rectangle on the output # frame cv2.putText(frame, label, (startX, startY - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.45, color, 2) cv2.rectangle(frame, (startX, startY), (endX, endY), color, 2) # show the output frame cv2.imshow("Frame", frame) key = cv2.waitKey(1) & 0xFF # if the `q` key was pressed, break from the loop if key == ord("q"): break # do a bit of cleanup cv2.destroyAllWindows() vs.stop()
التابع ()vs.read لالتقاط اطار من الكاميرا.
سنستفيد أيضًا من مكتبة imutils لتغيير حجم الاطار.
نطبق التابع detect_and_predict_mask على الاطار الملتقط لاكتشاف الوجوه وتصنيفها.
داخل حلقة for نقوم بتطبيق نتائج التنبؤ من اجل كل وجه مكتشف حيث نقوم برسم مستطيل ووضع نص حول كل وجه موجود في الاطار .
يتم التقاط ضغطات المفاتيح, إذا ضغط المستخدم على مفتاح (quit) ، فإننا نخرج من الحلقة.
الان نذهب الى terminal وننفذ الامر التالي :
$ python detect_mask_video.py
ملخص :
لأنشاء كاشف قناع الوجه قمنا بتدريب النموذج على صنفين من البيانات الصنف الاول لاشخاص يرتدون قناع الوجه والصنف الثاني لاشخاص لا يرتدون قناع الوجه , قمنا بضبط النموذج MobileNetV2 على هذه البيانات وحصلنا على دقة 99% تقريباً .
حيث أن عملية اكتشاف قناع الوجه مرت بمرحلتين اساسيتين :
1) مرحلة اكتشاف الوجه .
2) مرحلة تصنيف الوجه هل يحوي كمامة ام لا .
كما أن هذا النموذج يعاني من مشكلة اكتشاف الوجه في حال كانت الكمامة كبير جداً وتكاد تغطي معظم معالم الوجه ,وحل هذه المشكلة بتدريب الموديل على صور وجوه يوجد عليها أقنعة باحجام كبيرة .
بقلم :
محمد أحمد كردي
محمد مصطفى ذويب
رابط تحميل المشروع : http://shamra.sy/uploads/face-mask-detector.zip
المصادر :
https://towardsdatascience.com/finally-learn-how-to-use-command-line-apps-by-making-one-bd5cf21a15cd
https://medium.com/@vinuvish/face-detection-with-opencv-and-deep-learning-90bff9028fa8