مثال ۱۶.۰۲: ساخت برنامه تشخیص دسته بندی تصویر از وبکم با Tensorflow

بریم پردازش ویدیو با Tensorflow.js رو بیشتر بررسی کنیم. توی این مثال قراره از چیزایی که تا الان یاد گرفتیم استفاده کنیم و نتایج مربوط به شناسایی دسته بندی هر فریم از یک ویدیو در یک صفحه رو نمایش بدیم. برای این کار از یک حلقه بینهایت استفاده میکنیم و به محض دریافت دیتای جدید صفحه سایتمون رو آپدیت میکنیم. بریم وبکممون رو راه بندازیم و بدیمش به مدلمون؛ وقتشه شروع کنیم:

۱- در اینجا فایل HTML تقریبا با مثال قبلی یکسان هست و تنها تفاوتی که داره اینه که بجای تگ <image> از <video> استفاده میکنیم، که قراره حاوی خروجی وبکممون باشه. با توجه به این موضوع، فایل HTML شما باید به شکل زیر باشه:

<html>
<head>
<script src="https://unpkg.com/@tensorflow/tfjs"></script>
<script src="https://unpkg.com/@tensorflow-models/mobilenet"></script>
</head>
<body>
<p id="status"></p>
<video autoplayplaysinline muted id="video" width="896" height="670"></video>
<script src="tf.js"></script>
</body>
</html>

۲- حالا برای فایل جاوا اسکریپتمون هم میتونیم ادامه کدی که توی مثال قبلی قرار دادیم رو بنویسیم. این دفعه باید به وبکم دسترسی پیدا کنیم و دیتای استریم شده از اون رو به مدل Tensorflow خودمون بدیم. بخش اول فایل جاوا اسکریپتمون مثل فایل توی مثال قبلی هست، یعنی گرفتن رفرنس به المنت های video و status. بعد از اون فانکشن ()initWebcam رو کال میکنیم که قراره توی همین فایل بنویسیمش:

// tf.js
const videoElem = document.getElementById('video');
const statusElem = document.getElementById('status');
initWebcam();

۳- بعدش یک فانکشن برای تشخیص دسته بندی عکسها تعریف میکنیم که مقدار المنت status رو ست میکنه و مدل MobileNet رو لود میکنه:

async function classifyImage() {
statusElem.innerText = 'Loading MobileNet...'
let model = await mobilenet.load();

۴- اینجا هم یک حلقه بینهایت while ایجاد میکنیم. داخل حلقه دیتای استریم شده از وبکم رو به متد ()model.classify میدیم و نتایج رو به متغیر results اختصاص میدیم:

while (true) {
const results = await model.classify(videoElem);

۵- بعدش چک میکنیم که حداقل یک نتیجه وجود داشته باشه، و اگه وجود داشت مقدار innerText از المنت status رو بر اساس مقدار های دسته بندی و درصد اطمینان دریافت شده از مدل ست میکنیم:

if (results.length) {
let result = results[0];
statusElem.innerText = `${result.className} - ${result.probability}`;
}

۶- حالا از متد ()nextFrame از Tensorflow استفاده میکنیم، که باعث میشه thread اجرایی اصلی مرورگر مسدود نشه (کارش به این صورته که یک promise برمیگردونه که پس از کال شدن متد ()requestAnimationFrame مرورگر کامل میشه):

await tf.nextFrame();
}
}

۷- حالا وقتشه فانکشن initWebcam رو تعریف کنیم، که توی فانکشن قبلیمون کال شده بود. این فانکشن بررسی میکنه ببینه آیا خاصیت mediaDevices در آبجکت navigator مرورگر وجود داره یا نه، و پس از اون پشتیبانی از متد getUserMedia رو مورد بررسی قرار میده:

async function initWebcam() {
if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) {

۸- اگه پشتیبانی getUserMedia وجود داشته باشه، از try...catch برای دسترسی به وبکم استفاده میکنیم. برای کال کردن getUserMedia و درخواست ویدیو استریم وبکم از کلیدواژه await استفاده میکنیم و در نهایت استریم رو به خاصیت srcObject المنت video میدیم:

try {
const stream = await navigator.mediaDevices.getUserMedia({video:true});
console.log(stream);
videoElem.srcObject = stream;

۹- اگه اروری به وجود بیاد، مثلا کاربر اجازه دسترسی به وبکم رو نده، کد توی قسمت catch اون رو هندل میکنه:

} catch (error) {
console.log(`Error getting video: ${error}`);
}
}
}

۱۰- در نهایت فانکشن ()classifyImage رو کال میکنیم تا کار برنامه شروع بشه. برای این کار اون رو در یک شنونده رویداد قرار میدیم تا به محض اینکه المنت video دیتایی از وبکم کاربر دریافت کرد این فانکشن کال بشه و عمل تشخیص دسته بندی شروع بشه:

videoElem.onloadeddata = function() {
classifyImage();
}

۱۱- حالا صفحه HTML رو ریلود کنید. احتمالا از شما اجازه دسترسی به وبکمتون درخواست بشه که باید اون رو قبول کنید. پس از قبول کردنش باید تصویر گرفته شده از وبکمتون رو همراه با یک متنی که دائما در حال آپدیت شدن بر اساس اشیای موجود در تصویر هست ببینید. باحاله، مگه نه؟

Webcam image classification with highest probability

نتایجی که اینجا نشون داده میشن همیشه دقیق نیستن، اما معمولا برای اشیا واضح در تصویر نتایج خوبی به دست میارید. مثلا در بالا با احتمال %89 تونست تشخیص بده که ساعت توی عکس یک ساعت آنالوگ هست.

۱۲- بریم یه تغییر دیگه توی برنامه بدیم که خیلی بهترش میکنه. این دفعه میخوایم بجای اینکه فقط نتیجه مربوط به بالاترین درصد احتمال یک شی رو نمایش بده، نتایج مربوط به تمام اشیایی که بالای %30 احتمال درستی دارن رو نمایش بده. برای این کار از یک دستور if در فانکشن ()classifyImage استفاده میکنیم. قراره که صرفا هر کدوم از نتیجه ها رو چک کنیم و اون هایی که مقدار درستی 0.3 به بالا دارن رو اضافه کنیم:

if (results.length) {
let status = ''
results.forEach(result => {
if (result.probability> 0) status += `${result.className} -
${result.probability} \n`
})
statusElem.innerText = status;
}

بعد از آپدیت کردن کد سعی کردیم از یک تصویر حاوی بطری پلاستیکی استفاده کنیم که تونست بطری پلاستیکی رو با درستی %81 تشخیص بده، بقیه حدس هایی که زده بود حتی به این عدد نزدیک هم نبودن و نمیشه گفت حدس هایی که زده بخاطر تتو های روی دست بوده یا خود بطری:

Multiple predictions of webcam image using TensorFlow

یه سری اشیای دیگه هم درست تشخیص داده نمیشدن یا هی حدسی که ازشون میزد تغییر میکرد. مثلا زمانی که یک تصویر حاوی کاپ قهوه بهش دادیم فکر میکرد که شراب قرمزه:

Incorrect predictions using webcam images with TensorFlow

باز هم خودتون میتونید یه سری اشیای دیگه رو امتحان کنید تا ببینید کدوم درست تشخیص داده میشن.

در حال حاضر میشه خیلی کار ها در برنامه انجام داد، خاصیت های classNames و probability از نتایجمون به صورت پی در پی در حال تغییر هستن. بعدا توی همین فصل برمیگردیم سراغ این برنامه و بهتر و جالب ترش میکنیم. این صرفا یک نسخه آزمایشی بود تا بهتون نشون بدیم که چقدر راحت میشه یادگیری ماشین رو توی برنامه های خودمون پیاده سازی کنیم.