TensorFlow.js dan clmtrackr.js: melacak arah pandangan pengguna di browser

Penulis artikel, yang terjemahannya kami terbitkan, menawarkan untuk berbicara tentang memecahkan masalah dari bidang visi komputer secara eksklusif melalui browser web. Memecahkan masalah seperti itu tidak begitu sulit berkat pustaka JavaScript TensorFlow . Alih-alih melatih model kami sendiri dan menawarkannya kepada pengguna sebagai bagian dari produk jadi, kami akan memberi mereka kesempatan untuk mengumpulkan data secara mandiri dan melatih model secara langsung di peramban, di komputer kami sendiri. Dengan pendekatan ini, pemrosesan data sisi server sama sekali tidak perlu.


Anda dapat mengalami apa yang didedikasikan materi ini untuk dibuat di sini . Anda akan memerlukan browser, webcam, dan mouse modern untuk ini. Ini adalah kode sumber proyek. Dia tidak dirancang untuk bekerja pada perangkat seluler, penulis materi mengatakan bahwa dia tidak punya waktu untuk perbaikan yang tepat. Selain itu, ia mencatat bahwa tugas yang dipertimbangkan di sini akan menjadi lebih rumit jika Anda harus memproses streaming video dari kamera yang bergerak.

Ide


Mari kita gunakan teknologi pembelajaran mesin untuk mencari tahu di mana tepatnya pengguna mencari ketika dia melihat halaman web. Kami melakukan ini dengan menonton matanya menggunakan webcam.

Sangat mudah untuk mengakses webcam di browser. Jika kita mengasumsikan bahwa seluruh gambar dari kamera akan digunakan sebagai input ke jaringan saraf, maka kita dapat mengatakan bahwa itu terlalu besar untuk tujuan ini. Sistem harus melakukan banyak pekerjaan hanya untuk menentukan tempat pada gambar di mana mata berada. Pendekatan ini dapat menunjukkan dirinya sendiri dengan baik jika kita berbicara tentang model yang dilatih sendiri dan disebarkan oleh server, tetapi jika kita berbicara tentang pelatihan dan penggunaan model di browser, ini terlalu banyak.

Untuk memudahkan tugas jaringan, kami hanya dapat menyediakannya dengan sebagian gambar - yang berisi mata pengguna dan area kecil di sekitarnya. Area ini, yang merupakan persegi panjang di sekitar mata, dapat diidentifikasi menggunakan perpustakaan pihak ketiga. Karena itu, bagian pertama dari pekerjaan kami terlihat seperti ini:


Input webcam, pengenalan wajah, deteksi mata, gambar yang dipangkas

Untuk mendeteksi wajah dalam gambar, saya menggunakan perpustakaan yang disebut clmtrackr . Itu tidak sempurna, tetapi berbeda dalam ukuran kecil, kinerja yang baik, dan, secara umum, mengatasi tugasnya dengan bermartabat.

Jika gambar kecil tapi dipilih secara cerdas digunakan sebagai input untuk jaringan saraf convolutional sederhana, jaringan dapat belajar tanpa masalah. Begini prosesnya:


Gambar input, model adalah jaringan saraf convolutional, koordinat, tempat yang diprediksi oleh jaringan pada halaman tempat pengguna mencari.

Implementasi minimum yang berfungsi penuh dari ide-ide yang dibahas dalam bagian ini akan dijelaskan di sini. Proyek, yang kodenya ada di dalam repositori ini , memiliki banyak fitur tambahan.

Persiapan


Untuk memulai, clmtrackr.js dari repositori yang sesuai. Kami akan memulai pekerjaan dengan file HTML kosong, yang mengimpor jQuery, TensorFlow.js, clmtrackr.js dan file main.js dengan kode kami, yang akan kami kerjakan nanti:

 <!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> 

Terima aliran video dari webcam


Untuk mengaktifkan webcam dan menampilkan streaming video pada halaman, kita perlu mendapatkan izin pengguna. Di sini saya tidak menyediakan kode yang memecahkan masalah kompatibilitas proyek dengan berbagai browser. Kami akan melanjutkan dari asumsi bahwa pengguna kami bekerja di Internet menggunakan versi terbaru Google Chrome.

Tambahkan kode berikut ke file HTML. Itu harus berada di dalam <body> , tetapi di atas <body> <script> :

 <video id="webcam" width="400" height="300" autoplay></video> 

Sekarang mari kita bekerja dengan file main.js :

 $(document).ready(function() { const video = $('#webcam')[0]; function onStreaming(stream) {   video.srcObject = stream; } navigator.mediaDevices.getUserMedia({ video: true }).then(onStreaming); }); 

Coba kode ini sendiri. Saat Anda membuka halaman, browser harus meminta izin, dan kemudian gambar dari webcam akan muncul di layar.

Nanti kita akan memperluas kode fungsi onStreaming() .

Pencarian wajah


Sekarang mari kita gunakan pustaka clmtrackr.js untuk mencari wajah di video. Pertama, inisialisasi sistem pelacakan wajah dengan menambahkan kode berikut setelah const video = ... :

 const ctrack = new clm.tracker(); ctrack.init(); 

Sekarang, dalam fungsi onStreaming() , kami menghubungkan sistem pencarian wajah dengan menambahkan perintah berikut di sana:

 ctrack.start(video); 

Itu yang kita butuhkan. Sekarang sistem akan dapat mengenali wajah dalam aliran video.

Tidak percaya Mari kita menggambar "topeng" di sekitar wajah Anda untuk memastikan ini benar.
Untuk melakukan ini, kita perlu menampilkan gambar di atas elemen yang bertanggung jawab untuk menampilkan video. Anda dapat menggambar sesuatu di halaman HTML menggunakan <canvas> . Karenanya, kami akan membuat elemen seperti itu dengan menambahkannya pada elemen yang menampilkan video. Kode berikut akan membantu kami dalam hal ini, yang harus ditambahkan ke file HTML di bawah elemen <video> yang sudah ada di sana:

 <canvas id="overlay" width="400" height="300"></canvas> <style>   #webcam, #overlay {       position: absolute;       top: 0;       left: 0;   } </style> 

Jika mau, Anda bisa memindahkan gaya inline ke file CSS yang terpisah.

Di sini kami menambahkan <canvas> dengan ukuran yang sama ke halaman sebagai elemen <video> . Fakta bahwa elemen akan ditempatkan di posisi yang sama dijamin oleh gaya yang digunakan di sini.

Sekarang, setiap kali browser menampilkan bingkai video berikutnya, kita akan menggambar sesuatu pada elemen <canvas> . Eksekusi kode apa pun selama output setiap frame dilakukan menggunakan mekanisme requestAnimationLoop() . Sebelum kita mengeluarkan apa pun ke elemen <canvas> , kita perlu menghapus dari apa yang ada di dalamnya, membersihkannya. Kami kemudian dapat menyarankan clmtrackr untuk menampilkan grafik langsung ke elemen <canvas> .

Berikut adalah kode yang mengimplementasikan apa yang baru saja kita bicarakan. Tambahkan di bawah perintah 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); } } 

Sekarang panggil fungsi trackingLoop() di fungsi onStreaming() segera setelah ctrack.start() . Fungsi ini sendiri akan merencanakan restart sendiri di setiap frame.

Refresh halaman dan lihat webcam. Anda akan melihat "topeng" hijau di sekitar wajah Anda di jendela video. Terkadang, agar sistem dapat mengenali wajah dengan benar, Anda perlu sedikit menggerakkan kepala Anda dalam bingkai.


Hasil pengenalan wajah

Identifikasi area gambar yang mengandung mata


Sekarang kita perlu menemukan area persegi panjang dari gambar di mana mata berada dan menempatkannya pada <canvas> terpisah.

Untungnya, cmltracker tidak hanya memberi kita informasi tentang lokasi wajah, tetapi juga 70 titik kontrol. Jika Anda melihat dokumentasi untuk cmltracker, Anda dapat memilih dengan tepat titik kontrol yang kami butuhkan.


Poin kontrol

Kami memutuskan bahwa mata adalah bagian persegi panjang dari gambar, perbatasan yang menyentuh titik 23, 28, 24 dan 26, diperluas dengan 5 piksel di setiap arah. Persegi panjang ini harus mencakup semua yang penting bagi kami, kecuali jika pengguna terlalu memiringkan kepalanya.

Sekarang, sebelum kita dapat menggunakan fragmen gambar ini, kita memerlukan satu lagi <canvas> untuk hasilnya. Dimensinya akan menjadi 50x25 piksel. Kotak dengan mata akan cocok dengan elemen ini. Deformasi gambar yang ringan tidak menjadi masalah.

Tambahkan kode ini ke file HTML yang menjelaskan elemen <canvas> , yang akan menyertakan bagian dari gambar yang memiliki mata:

 <canvas id="eyes" width="50" height="25"></canvas> <style>   #eyes {       position: absolute;       top: 0;       right: 0;   } </style> 

Fungsi berikut akan mengembalikan koordinat x dan y , serta lebar dan tinggi persegi panjang yang mengelilingi mata. Ini, sebagai input, mengambil berbagai positions diterima dari clmtrackr. Perhatikan bahwa setiap koordinat yang diterima dari clmtrackr memiliki komponen x dan y . Fungsi ini harus ditambahkan ke 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]; } 

Sekarang, di setiap frame, kita akan mengekstrak segi empat dengan mata dari aliran video, melingkari dengan garis merah pada elemen <canvas> , yang ditumpangkan pada elemen <video> , dan kemudian salin ke <canvas> . Harap perhatikan bahwa untuk mengidentifikasi area yang kami butuhkan dengan benar, kami akan menghitung indikator resizeFactorX dan resizeFactorY .

Ganti blok if di fungsi trackingLoop() dengan kode berikut:

 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 ); } 

Setelah memuat ulang halaman sekarang, Anda akan melihat persegi panjang merah di sekitar mata, dan apa yang berisi persegi panjang ini dalam <canvas> sesuai. Jika mata Anda lebih besar dari saya, bereksperimenlah dengan fungsi getEyeRectangle .


Elemen <canvas> yang menggambar persegi panjang yang berisi gambar mata pengguna

Pengumpulan data


Ada banyak cara untuk mengumpulkan data. Saya memutuskan untuk menggunakan informasi yang dapat diperoleh dari mouse dan keyboard. Dalam proyek kami, pengumpulan data terlihat seperti ini.

Pengguna memindahkan kursor di sekitar halaman dan melihatnya dengan matanya, menekan tombol pada keyboard setiap kali program perlu merekam sampel lain. Dengan pendekatan ini, mudah untuk dengan cepat mengumpulkan kumpulan data besar untuk melatih model.

TrackingMouse Tracking


Untuk mengetahui dengan tepat di mana penunjuk tetikus berada pada halaman web, kita memerlukan penangan event document.onmousemove . Fungsi kami, selain itu, menormalkan koordinat sehingga mereka masuk ke dalam rentang [-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; 

โ– Pengambilan Gambar


Untuk mengambil gambar yang ditampilkan oleh elemen <canvas> dan menyimpannya sebagai tensor, TensorFlow.js menawarkan fungsi helper tf.fromPixels() . Kami menggunakannya untuk menyimpan dan kemudian menormalkan gambar dari elemen <canvas> yang menampilkan kotak yang berisi mata pengguna:

 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)); }); } 

Perhatikan bahwa fungsi tf.tidy() digunakan untuk membersihkan setelah selesai.

Kami hanya bisa menyimpan semua sampel dalam satu set pelatihan besar, namun dalam pembelajaran mesin, penting untuk memeriksa kualitas pelatihan model. Itu sebabnya kita perlu menyimpan beberapa sampel dalam sampel kontrol terpisah. Setelah itu, kami dapat memeriksa perilaku model pada data baru untuknya dan mencari tahu apakah model tersebut sudah terlalu terlatih. Untuk tujuan ini, 20% dari jumlah total sampel dimasukkan dalam sampel kontrol.

Berikut adalah kode yang digunakan untuk mengumpulkan data dan sampel:

 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; }); } 

Dan akhirnya, kita perlu mengikat fungsi ini ke :

 $('body').keyup(function(event) { //         if (event.keyCode == 32) {   captureExample();   event.preventDefault();   return false; } }); 

Sekarang setiap kali Anda menekan , gambar mata dan koordinat dari pointer mouse ditambahkan ke salah satu set data.

Pelatihan model


Buat jaringan saraf convolutional sederhana. TensorFlow.js menyediakan API yang mengingatkan Keras untuk tujuan ini. Jaringan harus memiliki lapisan conv2d , lapisan maxPooling2d , dan akhirnya lapisan dense dengan dua nilai output (mereka mewakili koordinat layar). Sepanjang jalan, saya menambahkan lapisan dropout dan lapisan flatten ke jaringan, sebagai regulator, untuk mengubah data dua dimensi menjadi satu dimensi. Pelatihan jaringan dilakukan dengan menggunakan pengoptimal Adam.

Harap dicatat bahwa saya menetapkan pengaturan jaringan yang digunakan di sini setelah bereksperimen dengan MacBook Air saya. Anda dapat memilih konfigurasi model Anda sendiri.

Ini adalah kode model:

 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; } 

Sebelum memulai pelatihan jaringan, kami menetapkan jumlah era dan jumlah paket variabel yang tetap (karena kami mungkin akan bekerja dengan kumpulan data yang sangat kecil).

 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], }); } 

Sekarang tambahkan tombol ke halaman untuk mulai belajar. Kode ini menuju ke file HTML:

 <button id="train">Train!</button> <style>   #train {       position: absolute;       top: 50%;       left: 50%;       transform: translate(-50%, -50%);       font-size: 24pt;   } </style> 

Kode ini harus ditambahkan ke file JS:

 $('#train').click(function() { fitModel(); }); 

Di mana pengguna mencari?


Sekarang kita dapat mengumpulkan data dan menyiapkan model, kita dapat mulai memprediksi tempat di halaman tempat pengguna melihat. Kami menunjuk ke tempat ini dengan bantuan lingkaran hijau, yang bergerak di sekitar layar.

Pertama, tambahkan lingkaran ke halaman:

 <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> 

Untuk memindahkannya di sekitar halaman, kami secara berkala mengirimkan gambar mata saat ini dari jaringan saraf dan mengajukan pertanyaan tentang ke mana pengguna mencari. Model dalam respons menghasilkan dua koordinat di mana lingkaran harus dipindahkan:

 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); 

Saya mengatur interval ke 100 milidetik. Jika komputer Anda tidak sekuat milik saya, Anda dapat memutuskan untuk memperbesarnya.

Ringkasan


Sekarang kita memiliki semua yang kita butuhkan untuk mengimplementasikan ide yang disajikan pada awal materi ini. Alami apa yang telah kami lakukan. Gerakkan kursor mouse, mengikuti matanya, dan tekan bilah spasi. Kemudian klik tombol mulai pelatihan.

Kumpulkan lebih banyak data, klik tombol lagi. Setelah beberapa saat, lingkaran hijau akan mulai bergerak di sekitar layar setelah pandangan Anda. Pada awalnya, itu tidak akan sangat baik untuk sampai ke tempat di mana Anda melihat, tetapi, dimulai dengan sekitar 50 sampel yang dikumpulkan, setelah beberapa tahap pelatihan, dan jika Anda beruntung, itu akan cukup akurat untuk pindah ke titik pada halaman yang Anda cari. . Kode lengkap dari contoh yang diuraikan dalam bahan ini dapat ditemukan di sini .

Meskipun apa yang kami lakukan sudah terlihat cukup menarik, masih ada banyak perbaikan yang bisa dilakukan. Bagaimana jika pengguna menggerakkan kepalanya atau mengubah posisinya di depan kamera? Proyek kami tidak akan merusak kemungkinan mengenai pemilihan ukuran, posisi dan sudut persegi panjang yang membatasi area gambar di mana mata berada. Bahkan, beberapa fitur tambahan diimplementasikan dalam versi lengkap dari contoh yang dibahas di sini. Inilah beberapa di antaranya:

  • Opsi untuk menyesuaikan persegi panjang yang diuraikan di atas.
  • Konversi gambar menjadi skala abu-abu.
  • Menggunakan CoordConv .
  • Peta panas untuk memeriksa di mana model berkinerja baik dan di mana itu tidak.
  • Kemampuan untuk menyimpan dan memuat kumpulan data.
  • Kemampuan untuk menyimpan dan memuat model.
  • Pelestarian bobot yang menunjukkan kehilangan pelatihan minimal setelah pelatihan.
  • Antarmuka pengguna yang ditingkatkan dengan instruksi singkat untuk bekerja dengan sistem.

Pembaca yang budiman! Apakah Anda menggunakan TensorFlow?

Source: https://habr.com/ru/post/id426055/


All Articles