يعرض كاتب المقال ، الذي ننشر ترجمته ، الحديث عن حل المشاكل من مجال رؤية الكمبيوتر حصريًا باستخدام متصفح الويب. حل هذه المشكلة ليس صعبًا للغاية بفضل مكتبة
TensorFlow JavaScript. بدلاً من تدريب النموذج الخاص بنا وتقديمه للمستخدمين كجزء من المنتج النهائي ، سنمنحهم الفرصة لجمع البيانات بشكل مستقل وتدريب النموذج مباشرة في متصفح على جهاز الكمبيوتر الخاص بنا. باستخدام هذا الأسلوب ، تكون معالجة البيانات من جانب الخادم غير ضرورية على الإطلاق.
يمكنك تجربة ما تم تخصيص هذه المواد لإنشائه
هنا . ستحتاج إلى متصفح حديث وكاميرا ويب وماوس لهذا الغرض.
هنا هو كود المصدر للمشروع. لم يتم تصميمه للعمل على الأجهزة المحمولة ، يقول مؤلف المادة أنه لم يكن لديه الوقت للتحسينات المناسبة. بالإضافة إلى ذلك ، يشير إلى أن المهمة التي تم النظر فيها هنا ستصبح أكثر تعقيدًا إذا كان عليك معالجة دفق الفيديو من كاميرا متحركة.
فكرة
دعنا نستخدم تقنية تعلُّم الآلة لمعرفة المكان الذي يبحث عنه المستخدم بالضبط عندما ينظر إلى صفحة ويب. نقوم بذلك من خلال مشاهدة عينيه باستخدام كاميرا ويب.
من السهل جدًا الوصول إلى كاميرا الويب في المتصفح. إذا افترضنا أنه سيتم استخدام الصورة الكاملة من الكاميرا كمدخلات للشبكة العصبية ، فيمكننا القول أنها كبيرة جدًا لهذه الأغراض. سيتعين على النظام القيام بالكثير من العمل فقط لتحديد المكان الذي توجد فيه الصورة في الصورة. يمكن أن يظهر هذا النهج نفسه جيدًا إذا كنا نتحدث عن نموذج يقوم المطور بتدريبه بنفسه ونشره على الخادم ، ولكن إذا كنا نتحدث عن التدريب واستخدام النموذج في متصفح ، فهذا كثير جدًا.
من أجل تسهيل مهمة الشبكة ، يمكننا تزويدها بجزء فقط من الصورة - الجزء الذي يحتوي على عيون المستخدم ومنطقة صغيرة حولهم. يمكن تحديد هذه المنطقة ، وهي مستطيل يحيط بالعينين ، باستخدام مكتبة طرف ثالث. لذلك ، يبدو الجزء الأول من عملنا كما يلي:
إدخال كاميرا الويب ، التعرف على الوجوه ، كشف العين ، الصورة المقطوعةللكشف عن الوجه في الصورة ، استخدمت مكتبة تسمى
clmtrackr . إنه ليس مثاليًا ، ولكنه يختلف في الحجم الصغير والأداء الجيد ، وبشكل عام ، يتواءم مع مهمته بكرامة.
إذا تم استخدام صورة صغيرة ولكن تم اختيارها بذكاء كمدخلات لشبكة عصبية تلافيفية بسيطة ، يمكن للشبكة التعلم دون أي مشاكل. إليك ما تبدو عليه هذه العملية:
صورة المدخلات ، النموذج عبارة عن شبكة عصبية تلافيفية ، إحداثيات ، المكان الذي تنبأت به الشبكة على الصفحة التي يبحث عنها المستخدم.سيتم وصف الحد الأدنى من التنفيذ الكامل للأفكار التي تمت مناقشتها في هذا القسم هنا. يحتوي المشروع ، الذي يوجد رمزه في
هذا المستودع ، على العديد من الميزات الإضافية.
تحضير
للبدء ، قم
clmtrackr.js
من
المستودع المناسب. سنبدأ العمل بملف HTML فارغ ، والذي يستورد jQuery و TensorFlow.js و
main.js
وملف
main.js
مع
main.js
، والذي سنعمل عليه بعد قليل:
<!doctype html> <html> <body> <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.12.0"></script> <script src="clmtrackr.js"></script> <script src="main.js"></script> </body> </html>
تلقي دفق فيديو من كاميرا ويب
لتنشيط كاميرا الويب وعرض دفق الفيديو على الصفحة ، نحتاج إلى الحصول على إذن المستخدم. لا أقدم هنا كودًا يحل مشاكل التوافق للمشروع مع متصفحات مختلفة. سننطلق من افتراض أن مستخدمينا يعملون على الإنترنت باستخدام أحدث إصدار من Google Chrome.
أضف التعليمات البرمجية التالية إلى ملف HTML. يجب أن يكون موجودًا داخل علامة
<body>
، ولكن فوق
<body>
<script>
:
<video id="webcam" width="400" height="300" autoplay></video>
الآن دعنا نعمل مع ملف
main.js
:
$(document).ready(function() { const video = $('#webcam')[0]; function onStreaming(stream) { video.srcObject = stream; } navigator.mediaDevices.getUserMedia({ video: true }).then(onStreaming); });
جرب هذا الرمز بنفسك. عندما تفتح الصفحة ، يجب أن يطلب المتصفح الإذن ، ثم تظهر صورة من كاميرا الويب على الشاشة.
سنقوم لاحقًا بتوسيع كود وظيفة
onStreaming()
.
البحث عن الوجه
الآن دعنا نستخدم مكتبة clmtrackr.js للبحث عن وجوه في الفيديو. أولاً ، قم بتهيئة نظام تتبع الوجه عن طريق إضافة الكود التالي بعد
const video = ...
:
const ctrack = new clm.tracker(); ctrack.init();
الآن ، في وظيفة
onStreaming()
، نقوم بتوصيل نظام البحث عن الوجه بإضافة الأمر التالي هناك:
ctrack.start(video);
هذا كل ما نحتاجه. الآن سيتمكن النظام من التعرف على الوجه في دفق الفيديو.
لا تصدق؟ دعنا نرسم "قناع" حول وجهك للتأكد من صحة ذلك.
للقيام بذلك ، نحتاج إلى عرض الصورة أعلى العنصر المسؤول عن عرض الفيديو. يمكنك رسم شيء ما على صفحات HTML باستخدام علامة
<canvas>
. لذلك ، سنقوم بإنشاء مثل هذا العنصر من خلال تركيبه على العنصر الذي يعرض الفيديو. سيساعدنا الكود التالي في ذلك ، والذي تحتاج إلى إضافته إلى ملف HTML تحت العنصر
<video>
بالفعل:
<canvas id="overlay" width="400" height="300"></canvas> <style> #webcam, #overlay { position: absolute; top: 0; left: 0; } </style>
إذا أردت ، يمكنك نقل النمط المضمن إلى ملف CSS منفصل.
أضفنا هنا عنصر
<canvas>
نفس الحجم إلى الصفحة كعنصر
<video>
. حقيقة أن العناصر ستوضع في نفس الموضع مضمونة من خلال الأنماط المستخدمة هنا.
الآن ، في كل مرة يعرض المستعرض الإطار التالي للفيديو ، سنقوم برسم شيء على
<canvas>
. يتم تنفيذ أي رمز أثناء إخراج كل إطار باستخدام آلية
requestAnimationLoop()
. قبل إخراج أي شيء إلى
<canvas>
، نحتاج إلى إزالة ما كان عليه من قبل ، ومسحه. يمكننا بعد ذلك اقتراح
clmtrackr
لإخراج الرسم مباشرة إلى
<canvas>
.
إليك الشفرة التي تنفذ ما تحدثنا عنه للتو.
ctrack.init()
أسفل الأمر
ctrack.init()
:
const overlay = $('#overlay')[0]; const overlayCC = overlay.getContext('2d'); function trackingLoop() { // , , // - . requestAnimationFrame(trackingLoop); let currentPosition = ctrack.getCurrentPosition(); overlayCC.clearRect(0, 0, 400, 300); if (currentPosition) { ctrack.draw(overlay); } }
الآن استدعاء دالة
trackingLoop()
في الدالة
onStreaming()
مباشرة بعد
ctrack.start()
. ستخطط هذه الوظيفة نفسها لإعادة تشغيلها في كل إطار.
قم بتحديث الصفحة وانظر إلى كاميرا الويب. يجب أن ترى "قناع" أخضر حول وجهك في نافذة الفيديو. في بعض الأحيان ، لكي يتعرف النظام على الوجه بشكل صحيح ، تحتاج إلى تحريك رأسك قليلاً في الإطار.
نتائج التعرف على الوجهحدد منطقة الصورة التي تحتوي على العينين
الآن نحن بحاجة إلى العثور على المنطقة المستطيلة من الصورة التي توجد فيها العيون ووضعها على عنصر
<canvas>
منفصل.
لحسن الحظ ، لا تعطينا cmltracker معلومات فقط حول موقع الوجه ، ولكن أيضًا 70 نقطة تحكم. إذا نظرت إلى
وثائق cmltracker ، يمكنك تحديد نقاط التحكم التي نحتاجها بالضبط.
نقاط التحكمنقرر أن العيون هي الجزء المستطيل من الصورة ، التي تلامس حدودها النقاط 23 و 28 و 24 و 26 ، موسعة بمقدار 5 بكسل في كل اتجاه. يجب أن يتضمن هذا المستطيل كل ما هو مهم بالنسبة لنا ، ما لم يميل المستخدم رأسه أكثر من اللازم.
الآن ، قبل أن نتمكن من استخدام هذا الجزء من الصورة ، نحتاج إلى عنصر
<canvas>
واحد
<canvas>
. ابعادها ستكون 50x25 بكسل. سوف يتناسب هذا العنصر مع مستطيل مع العيون. تشوه الصورة طفيف ليست مشكلة.
أضف هذا الرمز إلى ملف HTML الذي يصف
<canvas>
، والذي سيتضمن جزء الصورة الذي يحتوي على عيون:
<canvas id="eyes" width="50" height="25"></canvas> <style> #eyes { position: absolute; top: 0; right: 0; } </style>
ستقوم الوظيفة التالية بإرجاع إحداثيات
y
وص ، بالإضافة إلى عرض وارتفاع المستطيل المحيط بالعينين. كمدخل ، يأخذ مجموعة من
positions
المستلمة من clmtrackr. لاحظ أن كل إحداثي مستلم من clmtrackr يحتوي على مكونين
x
و
y
. يجب إضافة هذه الوظيفة إلى
main.js
:
function getEyesRectangle(positions) { const minX = positions[23][0] - 5; const maxX = positions[28][0] + 5; const minY = positions[24][1] - 5; const maxY = positions[26][1] + 5; const width = maxX - minX; const height = maxY - minY; return [minX, minY, width, height]; }
الآن ، في كل إطار ، سنقوم باستخراج مستطيل بعيون من دفق الفيديو ، وضع دائرة حوله بخط أحمر على
<canvas>
، والذي تم فرضه على عنصر
<video>
، ثم نسخه إلى
<canvas>
الجديد. يرجى ملاحظة أنه من أجل تحديد المنطقة التي نحتاجها بشكل صحيح ، سنقوم بحساب المؤشرات
resizeFactorX
و
resizeFactorY
.
استبدل كتلة
if
في وظيفة
trackingLoop()
بالكود التالي:
if (currentPosition) { // , // <canvas>, <video> ctrack.draw(overlay); // , , // const eyesRect = getEyesRectangle(currentPosition); overlayCC.strokeStyle = 'red'; overlayCC.strokeRect(eyesRect[0], eyesRect[1], eyesRect[2], eyesRect[3]); // , // // const resizeFactorX = video.videoWidth / video.width; const resizeFactorY = video.videoHeight / video.height; // // <canvas> const eyesCanvas = $('#eyes')[0]; const eyesCC = eyesCanvas.getContext('2d'); eyesCC.drawImage( video, eyesRect[0] * resizeFactorX, eyesRect[1] * resizeFactorY, eyesRect[2] * resizeFactorX, eyesRect[3] * resizeFactorY, 0, 0, eyesCanvas.width, eyesCanvas.height ); }
بعد إعادة تحميل الصفحة الآن ، من المفترض أن ترى مستطيلًا أحمر حول العينين ، وما يحتويه هذا المستطيل موجود في
<canvas>
المقابل. إذا كانت عيناك أكبر من عيناي ،
getEyeRectangle
وظيفة
getEyeRectangle
.
عنصر <canvas> يرسم مستطيلاً يحتوي على صورة عيون المستخدمجمع البيانات
هناك طرق عديدة لجمع البيانات. قررت استخدام المعلومات التي يمكن الحصول عليها من الماوس ولوحة المفاتيح. في مشروعنا ، يبدو جمع البيانات هكذا.
يقوم المستخدم بتحريك المؤشر حول الصفحة ويراقبه بعينيه ، بالضغط على
على لوحة المفاتيح في كل مرة يحتاج فيها البرنامج إلى تسجيل عينة أخرى. مع هذا النهج ، من السهل جمع مجموعة كبيرة من البيانات بسرعة لتدريب النموذج.
ouseتتبع الماوس
من أجل معرفة مكان مؤشر الماوس بالضبط على صفحة الويب ، نحتاج إلى معالج أحداث
document.onmousemove
. وظيفتنا ، بالإضافة إلى ذلك ، تطبيع الإحداثيات بحيث تتناسب مع النطاق [-1 ، 1]:
// : const mouse = { x: 0, y: 0, handleMouseMove: function(event) { // , [-1, 1] mouse.x = (event.clientX / $(window).width()) * 2 - 1; mouse.y = (event.clientY / $(window).height()) * 2 - 1; }, } document.onmousemove = mouse.handleMouseMove;
Cap التقاط الصور
لالتقاط الصورة التي يعرضها
<canvas>
وحفظها كموتر ، يوفر
tf.fromPixels()
وظيفة المساعد
tf.fromPixels()
. نستخدمها لحفظ الصورة ثم تطبيعها من
<canvas>
الذي يعرض مستطيلاً يحتوي على عيون المستخدم:
function getImage() { // return tf.tidy(function() { const image = tf.fromPixels($('#eyes')[0]); // <i><font color="#999999"></font></i>: const batchedImage = image.expandDims(0); // : return batchedImage.toFloat().div(tf.scalar(127)).sub(tf.scalar(1)); }); }
لاحظ أنه يتم استخدام وظيفة
tf.tidy()
للتنظيف بعد الانتهاء.
يمكننا فقط حفظ جميع العينات في مجموعة تدريب كبيرة واحدة ، ولكن في التعلم الآلي ، من المهم التحقق من جودة تدريب النموذج. هذا هو السبب في أننا بحاجة إلى حفظ بعض العينات في عينة تحكم منفصلة. بعد ذلك ، يمكننا التحقق من سلوك النموذج على البيانات الجديدة لذلك ومعرفة ما إذا كان النموذج قد تم تدريبه بشكل مفرط. لهذا الغرض ، يتم تضمين 20٪ من إجمالي عدد العينات في عينة التحكم.
فيما يلي الكود المستخدم لجمع البيانات والعينة:
const dataset = { train: { n: 0, x: null, y: null, }, val: { n: 0, x: null, y: null, }, } function captureExample() { // tf.tidy(function() { const image = getImage(); const mousePos = tf.tensor1d([mouse.x, mouse.y]).expandDims(0); // , ( ) const subset = dataset[Math.random() > 0.2 ? 'train' : 'val']; if (subset.x == null) { // subset.x = tf.keep(image); subset.y = tf.keep(mousePos); } else { // const oldX = subset.x; const oldY = subset.y; subset.x = tf.keep(oldX.concat(image, 0)); subset.y = tf.keep(oldY.concat(mousePos, 0)); } // subset.n += 1; }); }
وأخيرًا ، نحتاج إلى ربط هذه الوظيفة
:
$('body').keyup(function(event) { // if (event.keyCode == 32) { captureExample(); event.preventDefault(); return false; } });
الآن في كل مرة تضغط فيها على
، تتم إضافة صورة العين وإحداثيات مؤشر الماوس إلى إحدى مجموعات البيانات.
تدريب نموذجي
إنشاء شبكة عصبية تلافيفية بسيطة. يوفر TensorFlow.js واجهة برمجة تطبيقات تذكرنا بـ Keras لهذا الغرض. يجب أن تحتوي الشبكة على طبقة
conv2d
، وطبقة
maxPooling2d
، وأخيرًا طبقة
dense
maxPooling2d
إخراج (تمثل إحداثيات الشاشة). على طول الطريق ، أضفت طبقة
dropout
وطبقة
flatten
إلى الشبكة ، كمنظم ، من أجل تحويل البيانات ثنائية الأبعاد إلى أحادية البعد. يتم التدريب على الشبكة باستخدام محسن آدم.
يرجى ملاحظة أنني استقرت على إعدادات الشبكة المستخدمة هنا بعد تجربة جهاز MacBook Air الخاص بي. يمكنك اختيار التكوين الخاص بك للنموذج.
هنا هو رمز النموذج:
let currentModel; function createModel() { const model = tf.sequential(); model.add(tf.layers.conv2d({ kernelSize: 5, filters: 20, strides: 1, activation: 'relu', inputShape: [$('#eyes').height(), $('#eyes').width(), 3], })); model.add(tf.layers.maxPooling2d({ poolSize: [2, 2], strides: [2, 2], })); model.add(tf.layers.flatten()); model.add(tf.layers.dropout(0.2)); // x y model.add(tf.layers.dense({ units: 2, activation: 'tanh', })); // Adam 0.0005 MSE model.compile({ optimizer: tf.train.adam(0.0005), loss: 'meanSquaredError', }); return model; }
قبل البدء في تدريب الشبكة ، قمنا بتعيين عدد ثابت من العصور وحجم حزمة متغير (حيث من المحتمل أن نعمل مع مجموعات بيانات صغيرة جدًا).
function fitModel() { let batchSize = Math.floor(dataset.train.n * 0.1); if (batchSize < 4) { batchSize = 4; } else if (batchSize > 64) { batchSize = 64; } if (currentModel == null) { currentModel = createModel(); } currentModel.fit(dataset.train.x, dataset.train.y, { batchSize: batchSize, epochs: 20, shuffle: true, validationData: [dataset.val.x, dataset.val.y], }); }
الآن قم بإضافة زر إلى الصفحة لبدء التعلم. ينتقل هذا الرمز إلى ملف HTML:
<button id="train">Train!</button> <style> #train { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24pt; } </style>
يجب إضافة هذا الرمز إلى ملف JS:
$('#train').click(function() { fitModel(); });
أين ينظر المستخدم؟
الآن بعد أن نتمكن من جمع البيانات وإعداد النموذج ، يمكننا البدء في توقع المكان على الصفحة التي يبحث عنها المستخدم. نشير إلى هذا المكان بمساعدة دائرة خضراء تتحرك حول الشاشة.
أولاً ، أضف دائرة إلى الصفحة:
<div id="target"></div> <style> #target { background-color: lightgreen; position: absolute; border-radius: 50%; height: 40px; width: 40px; transition: all 0.1s ease; box-shadow: 0 0 20px 10px white; border: 4px solid rgba(0,0,0,0.5); } </style>
من أجل تحريكها حول الصفحة ، ننقل بشكل دوري الصورة الحالية لعيني الشبكة العصبية ونطرح عليها سؤالًا عن المكان الذي يبحث فيه المستخدم. ينتج عن النموذج استجابة إحداثيتين يجب تحريك الدائرة على طولهما:
function moveTarget() { if (currentModel == null) { return; } tf.tidy(function() { const image = getImage(); const prediction = currentModel.predict(image); // const targetWidth = $('#target').outerWidth(); const targetHeight = $('#target').outerHeight(); const x = (prediction.get(0, 0) + 1) / 2 * ($(window).width() - targetWidth); const y = (prediction.get(0, 1) + 1) / 2 * ($(window).height() - targetHeight); // : const $target = $('#target'); $target.css('left', x + 'px'); $target.css('top', y + 'px'); }); } setInterval(moveTarget, 100);
قمت بتعيين الفاصل الزمني إلى 100 مللي ثانية. إذا لم يكن جهاز الكمبيوتر الخاص بك قويًا مثل جهاز الكمبيوتر الخاص بي ، فقد تقرر تكبيره.
الملخص
الآن لدينا كل ما نحتاجه لتنفيذ الفكرة المقدمة في بداية هذه المواد. تجربة ما قمنا به. حرك مؤشر الماوس ، واتبع عينيه ، واضغط على مفتاح المسافة. ثم انقر فوق زر بدء التدريب.
جمع المزيد من البيانات ، انقر فوق الزر مرة أخرى. بعد فترة ، ستبدأ الدائرة الخضراء في التحرك حول الشاشة بعد نظراتك. في البداية ، لن يكون من الجيد بشكل خاص الوصول إلى المكان الذي تبحث فيه ، ولكن بدءًا من حوالي 50 عينة تم جمعها ، بعد عدة مراحل من التدريب ، وإذا كنت محظوظًا ، فسوف تنتقل بدقة إلى النقطة التي تبحث عنها في الصفحة . يمكن العثور على الرمز الكامل للمثال الذي تم تحليله في هذه المادة
هنا .
على الرغم من أن ما فعلناه يبدو مثيرًا للاهتمام بالفعل ، لا يزال هناك العديد من التحسينات التي يمكن إجراؤها. ماذا لو حرك المستخدم رأسه أو غيّر مكانه أمام الكاميرا؟ لن يضر مشروعنا بالإمكانيات المتعلقة باختيار حجم المستطيل وموضعه وزاويته التي تحيط بمنطقة الصورة التي توجد فيها العيون. في الواقع ، يتم تنفيذ عدد غير قليل من الميزات الإضافية في
النسخة الكاملة من المثال الذي نوقش هنا. هنا بعض منهم:
- خيارات تخصيص المستطيل المحيط بالعين الموصوف أعلاه.
- تحويل صورة إلى درجات رمادية.
- باستخدام إدارة الإحداثيات .
- خريطة حرارية للتحقق من أداء النموذج بشكل جيد وأين لم ينجح.
- القدرة على حفظ وتحميل مجموعات البيانات.
- القدرة على حفظ وتحميل النماذج.
- الحفاظ على الأوزان التي أظهرت الحد الأدنى من فقدان التدريب بعد التدريب.
- واجهة مستخدم محسنة مع تعليمات موجزة للعمل مع النظام.
أعزائي القراء! هل تستخدم TensorFlow؟
