整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          10分鐘內(nèi)瀏覽器中,手把手教你構(gòu)建一個手勢識別模型 - 代碼開源

          沉 發(fā)自 宇宙中心

          量子位 出品 | 公眾號 QbitAI

          深度學(xué)習(xí)技術(shù)的不斷普及,越來越多的語言可以用來進行深度學(xué)習(xí)項目的開發(fā),即使是JavaScript這樣曾經(jīng)只是在瀏覽器中運行的用于處理輕型任務(wù)的腳本語言。

          TensorFlow.js是谷歌推出的基于JavaScript的深度學(xué)習(xí)框架,它提供的高級API使得開發(fā)可以直接在瀏覽器中運行的深度學(xué)習(xí)算法變得輕而易舉。

          這不,美國的一位老哥Gant Laborde使用TensorFlow.js開發(fā)了一款是用深度學(xué)習(xí)技術(shù)在瀏覽器中識別“石頭剪刀布”游戲手勢的網(wǎng)頁應(yīng)用,放出了demo并將代碼開源在了Github上。

          對于JavaScript開發(fā)者來說,這是打開深度學(xué)習(xí)大門的極佳入門教材。只需10分鐘,你就可以訓(xùn)練一個準確率可觀的手勢識別模型,并且調(diào)用攝像頭對實時視頻中的手勢進行識別。


          使用運行在瀏覽器中的深度學(xué)習(xí)模型識別手勢

          在一切開始之前

          在打開新世界的大門之前,我們總是需要做一些準備工作。

          在這里,給大家簡單地介紹一下典型的深度學(xué)習(xí)算法的開發(fā)步驟,目的是希望讀者們在接下來的操作中明確地知道自己在做什么,而不僅僅是點幾個按鈕罷了。

          這里不會涉及任何艱澀的數(shù)學(xué)公式,請放心食用。


          我們平常所說的深度學(xué)習(xí)算法,更確切地說,應(yīng)該是基于深度神經(jīng)網(wǎng)絡(luò)的算法(或者說模型)。

          這里并不需要知道深度神經(jīng)網(wǎng)絡(luò)究竟是個什么東西(你可能需要再花百倍于此的時間才有可能搞明白其具體原理),只需要知道,它可以視作是一個函數(shù)f,一個很難用簡單公式表達出來的函數(shù)。

          所謂函數(shù),就要有自變量x和因變量y。

          自變量x,我們一般稱之為輸入(input),在這個問題中就是一張做出“石頭”、“剪刀”或“布”手勢的手的圖像。

          而因變量y,我們一般稱之為輸出(output),在這個問題中是三個取值為0-1的數(shù)值,分別對應(yīng)輸入手勢是“石頭”、“剪刀”和“布”的概率。

          我們依靠這個函數(shù)f得到我們想要的結(jié)果,但是f并不是天上掉下來的,它由人為選取的模型和(大量的)模型參數(shù)組成。

          其中模型參數(shù)往往由大量數(shù)據(jù)學(xué)習(xí)得到,這個讓模型學(xué)習(xí)參數(shù)的過程我們稱之為模型訓(xùn)練(train),是深度學(xué)習(xí)算法開發(fā)中最關(guān)鍵的一步。

          在這個問題中,我們需要大量(x,y)數(shù)據(jù)對來進行訓(xùn)練,也就是大量(圖像,手勢)數(shù)據(jù)對,如(圖像1,剪刀)、(圖像2、石頭)、(圖像3、布)…… 這些數(shù)據(jù)對往往需要由人為搜集、標注得到。

          我們可以通過一些評估指標來衡量模型的好壞程度,比如在這個問題中,手勢識別的準確度。通過這些評估指標我們可以驗證(validate)模型是否經(jīng)過了充分的訓(xùn)練、效果有沒有達到我們的預(yù)期。如果是,我們可以將其部署投入使用,測試其在現(xiàn)實情況中的表現(xiàn)。

          總結(jié)來說,一個深度學(xué)習(xí)算法的開發(fā),需要經(jīng)過數(shù)據(jù)準備模型選擇與訓(xùn)練模型效果評估模型測試這四個階段。

          現(xiàn)在,正式開始!

          數(shù)據(jù)準備

          我們之前提到,需要大量的(圖像,手勢)數(shù)據(jù)對來進行模型的訓(xùn)練。搜集這樣的數(shù)據(jù)無疑是一個繁瑣的工作,拍照、標注……

          幸運的是,谷歌工程師Laurence Moroney為我們提供了這樣一個數(shù)據(jù)集,其中包含了白色背景下的三種手勢共2892張圖像及對應(yīng)的手勢標簽,一些例子:


          Moroney提供的數(shù)據(jù)集的一些例子

          數(shù)據(jù)集網(wǎng)址:

          http://www.laurencemoroney.com/rock-paper-scissors-dataset/

          一切看似都是這么的順利。等等,我們怎么把這么一坨圖像搞進瀏覽器里去?

          在瀏覽器里執(zhí)行JavaScript,好像并不能從本地讀取文件。

          一個顯見的想法是,我們把訓(xùn)練數(shù)據(jù)當做網(wǎng)頁中的圖片,讀進DOM的img元素中。我們先將訓(xùn)練數(shù)據(jù)中每一張圖像“拉直“成1像素高的圖像,再將所有圖像一行一行堆疊在一起。

          比如我們原圖大小為64x64,“拉直”之后尺寸為1x4096,訓(xùn)練集的2520張圖像堆疊后形成大小為4096x2520的巨大圖像(雖然它在視覺上已經(jīng)失去了意義),像下面這樣。

          這張巨大圖像被稱為精靈表單(sprite-sheet),包含了許多小圖像。

          這個網(wǎng)頁應(yīng)用的作者提供了生成sprite-sheet的Python代碼,在github倉庫根目錄的spritemaker文件夾下。


          生成的尺寸為4096x2520的sprite-sheet

          在demo頁面中,點擊“Load and Show Examples(讀取數(shù)據(jù)并展示樣例)”按鈕,等待一陣,我們可以看到數(shù)據(jù)被讀入了瀏覽器,并且出現(xiàn)了一個側(cè)邊欄,其中展示了42張從數(shù)據(jù)集中隨機選取的圖像。

          這個側(cè)邊欄由TensorFlow Visor提供,可以幫助我們直觀地觀察模型的訓(xùn)練過程,我們可以隨時按下鍵盤左上方的`鍵切出或隱藏該面板。


          TensorFlow Visor界面中展示的數(shù)據(jù)樣例

          模型選擇、訓(xùn)練與效果評估

          接下來我們將面臨抉擇。

          兩個按鈕擺在我們的面前,“Create Simple Model(創(chuàng)建簡單模型)”和“Create Advance Model(創(chuàng)建高級模型)”。



          先從簡單的來吧,我們點擊“Create Simple Model”。按`鍵切出TensorFlow Visor面板,可以看到上面出現(xiàn)了剛剛創(chuàng)建的簡單模型的網(wǎng)絡(luò)結(jié)構(gòu),這是一個5層的卷積神經(jīng)網(wǎng)絡(luò)模型(Flatten層不計入層數(shù)),你只需要知道它可以看做是一個一個相對簡單函數(shù)的堆疊,并且這確實是一個非常簡單基礎(chǔ)的卷積神經(jīng)網(wǎng)絡(luò)模型。



          TensorFlow Visor界面中展示的網(wǎng)絡(luò)結(jié)構(gòu)

          點擊“Check Untrained Model Results(查看未訓(xùn)練模型結(jié)果)”,面板中出現(xiàn)了一個Accuracy(準確率)表格,和一個矩陣,它們就是這個問題中我們對于模型的評價指標。

          準確率表格中,每一行是一個手勢類別的準確率值;矩陣中,手勢X的行和手勢Y的列確定的單元格代表實際是手勢X,被算法認為是手勢Y的圖像數(shù)量,這樣的矩陣我們叫做“混淆矩陣”,因為它展現(xiàn)了算法對于兩兩手勢容易搞混的程度。

          可以看到,因為我們的模型還沒有進行訓(xùn)練,所以算法認為所有輸入圖像中的手勢都是“剪刀”,它還很懵懂。

          那么就開始訓(xùn)練它吧!點擊“Train Your Simple Model(訓(xùn)練簡單模型)”!TensorFlow Visor面板中出現(xiàn)了“Model Training(模型訓(xùn)練)”一欄,展示了訓(xùn)練中實時的準確率(Accuracy)和損失(Loss)值,正常情況下,我們應(yīng)該可以看到隨著訓(xùn)練的進行,準確率不斷上升,而損失不斷下降。訓(xùn)練在12個epoch(60個batch)后停止。


          TensorFlow Visor界面中展示的訓(xùn)練進程

          訓(xùn)練結(jié)束后,點擊“Check Model After Training(查看訓(xùn)練后模型結(jié)果)”。在原來的準確率表格和混淆矩陣下方出現(xiàn)了訓(xùn)練后模型的準確率(Trained Accuracy)和混淆矩陣(Trained Confusion Matrix)。

          Amazing!訓(xùn)練后,模型在驗證數(shù)據(jù)上對于三種手勢的識別準確率都超過了95%,混淆矩陣也是健康的(對角線深,其余淺)。


          TensorFlow Visor界面中展示的訓(xùn)練后模型效果

          你也許會想,“高級的東西總比簡單的東西好吧?高級模型效果一定更好。” 其實這是一個常見的誤區(qū)。

          如果你選擇“Create Advance Model(創(chuàng)建高級模型)”,重復(fù)上述操作,會發(fā)現(xiàn)高級模型不僅訓(xùn)練時間更長,效果也不如簡單模型那么好。

          更進一步,高級模型如果訓(xùn)練時間過長,會出現(xiàn)過擬合(overfitting)的情況。

          過擬合是指,模型太注重完美擬合訓(xùn)練數(shù)據(jù),導(dǎo)致其雖然在訓(xùn)練數(shù)據(jù)上的表現(xiàn)極佳,但是對于訓(xùn)練數(shù)據(jù)之外它沒有見過的數(shù)據(jù)效果較差,或者我們也會說模型此時的泛化(generalize)能力較差。

          模型測試

          既然已經(jīng)有了一個表現(xiàn)很不錯的簡單模型,那么讓我們立刻將它投入使用吧!

          點擊“Launch Webcam(打開攝像頭)”,對準一面白墻,對著攝像頭做出不同的手勢,應(yīng)用會定時捕捉視頻圖像,通過訓(xùn)練好的模型算法,告訴你當前手勢屬于三種類別的概率,是不是很酷炫呢?


          使用已訓(xùn)練模型識別視頻中的手勢

          Done!

          至此,你已經(jīng)在完全在瀏覽器中訓(xùn)練了一個用于手勢分類的深度學(xué)習(xí)模型,通過一些指標驗證了它的有效性,并且在現(xiàn)實情境中對它進行了測試。

          盡管這些步驟很簡單,但你了解它們在做什么——歡迎來到深度學(xué)習(xí)的世界!

          傳送門

          源代碼倉庫:

          https://github.com/GantMan/rps_tfjs_demo

          Demo頁面:

          https://rps-tfjs.netlify.com/

          — 完 —

          誠摯招聘

          量子位正在招募編輯/記者,工作地點在北京中關(guān)村。期待有才氣、有熱情的同學(xué)加入我們!相關(guān)細節(jié),請在量子位公眾號(QbitAI)對話界面,回復(fù)“招聘”兩個字。

          量子位 QbitAI · 頭條號簽約作者

          ?'?' ? 追蹤AI技術(shù)和產(chǎn)品新動態(tài)

          先使用jQuery選擇器獲取到想要綁定click事件的img元素,然后可以直接綁定click方法,也可以通過bind方法綁定。這里詳細介紹一下bind方法。jQuery 事件 - bind() 方法 —— 定義和用法

          html中如何給圖片添加點擊事件的詳解


          bind() 方法為被選元素添加一個或多個事件處理程序,并規(guī)定事件發(fā)生時運行的函數(shù)。

          jQuery 事件 - bind() 方法 ——將事件和函數(shù)綁定到元素

          規(guī)定向被選元素添加的一個或多個事件處理程序,以及當事件發(fā)生時運行的函數(shù)。

          jQuery 事件 - bind() 方法——語法

          1 $(selector).bind(event,data,function)

          jQuery 事件 - bind() 方法——參數(shù)描述

          event 必需。規(guī)定添加到元素的一個或多個事件。由空格分隔多個事件。必須是有效的事件。

          data 可選。規(guī)定傳遞到函數(shù)的額外數(shù)據(jù)。

          function 必需。規(guī)定當事件發(fā)生時運行的函數(shù)。

          實例:


          1 //直接給所有img標簽綁定click事件

          2 $("img").click(function(){

          3 alert('你點擊了圖片');

          4 })

          5

          6 //使用bind方法綁定click事件

          7 $("img").bind("click",function(){

          8 alert('你點擊了圖片');

          9 })


          Html 的img標簽添加點擊事件


          1 package com.topnews;

          2

          3 import java.util.ArrayList;

          4

          5 import android.annotation.SuppressLint;

          6 import android.app.Activity;

          7 import android.app.Fragment;

          8 import android.content.Context;

          9 import android.content.Intent;

          10 import android.graphics.Bitmap;

          11 import android.os.AsyncTask;

          12 import android.os.Bundle;

          13 import android.text.TextUtils;

          14 import android.util.Log;

          15 import android.view.View;

          16 import android.view.ViewGroup.LayoutParams;

          17 import android.webkit.WebChromeClient;

          18 import android.webkit.WebSettings;

          19 import android.webkit.WebView;

          20 import android.webkit.WebViewClient;

          21 import android.webkit.WebSettings.LayoutAlgorithm;

          22 import android.widget.Button;

          23 import android.widget.FrameLayout;

          24 import android.widget.ProgressBar;

          25 import android.widget.TextView;

          26

          27 import com.topnews.base.BaseActivity;

          28 import com.topnews.bean.NewsEntity;

          29 import com.topnews.service.NewsDetailsService;

          30 import com.topnews.tool.BaseTools;

          31 import com.topnews.tool.DataTools;

          32 import com.topnews.tool.DateTools;

          33

          34 @SuppressLint("JavascriptInterface")

          35 public class DetailsActivity extends BaseActivity {

          36 private TextView title;

          37 private ProgressBar progressBar;

          38 private FrameLayout customview_layout;

          39 private String news_url;

          40 private String news_title;

          41 private String news_source;

          42 private String news_date;

          43 private NewsEntity news;

          44 private TextView action_comment_count;

          45 WebView webView;

          46

          47 @Override

          48 protected void onCreate(Bundle savedInstanceState) {

          49 // TODO Auto-generated method stub

          50 super.onCreate(savedInstanceState);

          51 setContentView(R.layout.details);

          52 setNeedBackGesture(true);// 設(shè)置需要手勢監(jiān)聽

          53 getData();

          54 initView();

          55 initWebView();

          56 }

          57

          58 /* 獲取傳遞過來的數(shù)據(jù) */

          59 private void getData() {

          60 news = (NewsEntity) getIntent().getSerializableExtra("news");

          61 news_url = news.getSource_url();

          62 news_title = news.getTitle();

          63 news_source = news.getSource();

          64 news_date = 65DateTools.getNewsDetailsDate(String.valueOf(news.getPublishTime()));

          66 }

          67

          68 private void initWebView() {

          69 webView = (WebView) findViewById(R.id.wb_details);

          70 LayoutParams params = new 71LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

          72 if (!TextUtils.isEmpty(news_url)) {

          73 WebSettings settings = webView.getSettings();

          74 settings.setJavaScriptEnabled(true);// 設(shè)置可以運行JS腳本

          75 // settings.setTextZoom(120);//Sets the text zoom of the page in

          76 // percent. The default is 100.

          77 settings.setLayoutAlgorithm(LayoutAlgorithm.SINGLE_COLUMN);

          78 // settings.setUseWideViewPort(true); //打開頁面時, 自適應(yīng)屏幕

          79 // settings.setLoadWithOverviewMode(true);//打開頁面時, 自適應(yīng)屏幕

          80 settings.setSupportZoom(false);// 用于設(shè)置webview放大

          81 settings.setBuiltInZoomControls(false);

          82 webView.setBackgroundResource(R.color.transparent);

          83 // 添加js交互接口類,并起別名 imagelistner

          84 webView.addJavascriptInterface(new 85JavascriptInterface(getApplicationContext()), "imagelistner");

          86 webView.setWebChromeClient(new MyWebChromeClient());

          87 webView.setWebViewClient(new MyWebViewClient());

          88 Log.i("info", "news_url:" + news_url);

          89 Log.i("info", "news_title:" + news_title);

          90 Log.i("info", "news_source:" + news_source);

          91 Log.i("info", "news_date:" + news_date);

          92 new MyAsnycTask().execute(news_url, news_title, news_source + " " + 93news_date);

          94 }

          95 }

          96

          97 private void initView() {

          98 title = (TextView) findViewById(R.id.title);

          99 progressBar = (ProgressBar) findViewById(R.id.ss_htmlprogessbar);

          100 customview_layout = (FrameLayout) 101findViewById(R.id.customview_layout);

          102 // 底部欄目

          103 action_comment_count = (TextView) 104findViewById(R.id.action_comment_count);

          105

          106 progressBar.setVisibility(View.VISIBLE);

          107 title.setTextSize(13);

          108 title.setVisibility(View.VISIBLE);

          109 title.setText(news_url);

          110 action_comment_count.setText(String.valueOf(news.getCommentNum()));

          111 }

          112

          113 @Override

          114 public void onBackPressed() {

          115 super.onBackPressed();

          116 overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right);

          117 }

          118

          119 private class MyAsnycTask extends AsyncTask<string, string,="" string=""> {

          120

          121 @Override

          122 protected String doInBackground(String... urls) {

          123 String data = NewsDetailsService.getNewsDetails(urls[0], urls[1], urls[2]);

          124 Log.i("info", "MyAsnycTask.data:" + data);

          125 return data;

          126 }

          127

          128 @Override

          129 protected void onPostExecute(String data) {

          130 webView.loadDataWithBaseURL(null, data, "text/html", "utf-8", null);

          131 }

          132 }

          133

          134 // 注入js函數(shù)監(jiān)聽

          135 private void addImageClickListner() {

          136 // 這段js函數(shù)的功能就是,遍歷所有的img幾點,并添加onclick函數(shù),在還是執(zhí)137 行的時候調(diào)用本地接口傳遞url過去

          138 webView.loadUrl("javascript:(function(){" + "var objs = 139document.getElementsByTagName(\"img\");" + "var imgurl=''; "

          140 + "for(var i=0;i<objs.length;i++) "="" +="" "{"="" 141"imgurl+="objs[i].src+',';"" objs[i].onclick="function()" {="" 142window.imagelistner.openimage(imgurl);="" }="" "}"="" "})()");="" js通信接口="" 143public="" class="" javascriptinterface="" private="" context="" context;="" 144javascriptinterface(context="" context)="" this.context="context;" void="" 145openimage(string="" img)="" string[]="" imgs="img.split(",");" 146arraylist<string=""> imgsUrl = new ArrayList<string>();

          147 for (String s : imgs) {

          148 imgsUrl.add(s);

          149 Log.i("圖片的URL>>>>>>>>>>>>>>>>>>>>>>>", s);

          150 }

          151 Intent intent = new Intent();

          152 intent.putStringArrayListExtra("infos", imgsUrl);

          153 intent.setClass(context, ImageShowActivity.class);

          154 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

          155 context.startActivity(intent);

          156 }

          157 }

          158

          159 // 監(jiān)聽

          160 private class MyWebViewClient extends WebViewClient {

          161 @Override

          162 public boolean shouldOverrideUrlLoading(WebView view, String url) {

          163 return super.shouldOverrideUrlLoading(view, url);

          164 }

          165

          166 @Override

          167 public void onPageFinished(WebView view, String url) {

          168 view.getSettings().setJavaScriptEnabled(true);

          169 super.onPageFinished(view, url);

          170 // html加載完成之后,添加監(jiān)聽圖片的點擊js函數(shù)

          171 addImageClickListner();

          172 progressBar.setVisibility(View.GONE);

          173 webView.setVisibility(View.VISIBLE);

          174 }

          175

          176 @Override

          177 public void onPageStarted(WebView view, String url, Bitmap favicon) {

          178 view.getSettings().setJavaScriptEnabled(true);

          179 super.onPageStarted(view, url, favicon);

          180 }

          181

          182 @Override

          183 public void onReceivedError(WebView view, int errorCode, String description, 184String failingUrl)

          185 {

          progressBar.setVisibility(View.GONE);

          super.onReceivedError(view, errorCode, description, failingUrl);

          }

          }

          private class MyWebChromeClient extends WebChromeClient {

          @Override

          public void onProgressChanged(WebView view, int newProgress) {

          // TODO Auto-generated method stub

          if (newProgress != 100) {

          progressBar.setProgress(newProgress);

          }

          super.onProgressChanged(view, newProgress);

          }

          }

          }</string></objs.length;i++)></string,>


          // NewsDetailsService.java


          1 package com.topnews.service;

          2

          3 import java.io.IOException;

          4 import org.jsoup.Jsoup;

          5 import org.jsoup.nodes.Document;

          6 import org.jsoup.nodes.Element;

          7

          8 import android.text.TextUtils;

          9

          10 public class NewsDetailsService {

          11 public static String getNewsDetails(String url, String news_title,

          12 String news_date) {

          13 Document document = null;

          14 String data = "" +

          15 "<center><h2 style="'font-size:16px;'">" + news_title + "</h2></center>";

          16 data = data + "<p align="'left'" style="'margin-left:10px'">"

          17 + "<span style="'font-size:10px;'">"

          18 + news_date

          19 + "</span>"

          20 + "</p>";

          21 data = data + "<hr size="'1'">";

          22 try {

          23 document = Jsoup.connect(url).timeout(9000).get();

          24 Element element = null;

          25 if (TextUtils.isEmpty(url)) {

          26 data = "";

          27 element = document.getElementById("memberArea");

          28 } else {

          29 element = document.getElementById("artibody");

          30 }

          31 if (element != null) {

          32 data = data + element.toString();

          33 }

          34 data = data + "";

          35 } catch (IOException e) {

          36 e.printStackTrace();

          37 }

          38 return data;

          39 }

          40 }

          以上就是html中如何給圖片添加點擊事件的詳解的詳細內(nèi)容,

          家好,很高興又見面了,我是"高級前端?進階?",由我?guī)е蠹乙黄痍P(guān)注前端前沿、深入前端底層技術(shù),大家一起進步,也歡迎大家關(guān)注、點贊、收藏、轉(zhuǎn)發(fā),您的支持是我不斷創(chuàng)作的動力。

          今天遇到一個非常有意思的庫,即 Handsfree.js,特意分享給大家,看看是否可以結(jié)合自己的日常研發(fā)、業(yè)務(wù)支持等做一點小小的創(chuàng)新。話不多說,直接開始。

          1.什么是 Handsfree

          Build handsfree User Experiences and add face, hand, and pose tracking to your projects in a snap ?

          Handsfree 是一個通過計算機視覺集成手勢,面部表情和各種姿勢識別的前端庫。其核心 AI 技術(shù)用到了 tensorflow,可在瀏覽器上觸發(fā)交互事件,比如:滾動網(wǎng)頁,檢測人臉并展示相關(guān)表情,控制桌面游戲等等,開發(fā)者還可以通過 Handsfree 與 websocket 的結(jié)合控制任意與電腦連接的設(shè)備。

          官方提供的以下模型可以實時組合和重新配置:

          • MediaPipe Hands (2D):每只手 21 個 2D 手部標志;同時追蹤最多 4 只手;支持捏合狀態(tài)、手指指針和手勢
          • TensorFlow Handpose:支持 21 個 3D 手部標記;一次只能追蹤 1 只手;支持額外的助手和插件
          • MediaPipe FaceMesh:支持 468 個 2D 人臉特征點;一次最多追蹤 4 張面孔; 支持更多助手和插件
          • MediaPipe Pose:支持具有 33 個 2D 姿勢地標的全身模式;具有 25 個 2D 上身姿勢標志的上半身模式
          • TensorFlow Handpose:支持 6DOF 頭部姿勢;11 種面部變形和 16 種輔助狀態(tài);附帶基于 “Face Pointer” 的插件

          2.如何使用 Handsfree

          開發(fā)者可以選擇從 CDN 加載資源然后使用,比如下面的例子:

          <head>
            <!-- 加載 Handsfree.js 的官方包,包含 css 和 js -->
            <link rel="stylesheet" href="https://unpkg.com/handsfree@8.5.1/build/lib/assets/handsfree.css" />
            <script src="https://unpkg.com/handsfree@8.5.1/build/lib/handsfree.js"></script>
          </head>
          
          <body>
            <!-- 實例化Handsfree同時啟用  -->
            <script>
              const handsfree = new Handsfree({hands: true})
              handsfree.enablePlugins('browser')
              handsfree.start()
            </script>
          </body>

          當然,也可以選擇從 NPM 下載然后使用:

          // 啟用 Mediapipe 的 “手” 模型
          const handsfree = new Handsfree({hands: true})
          // 啟動標簽為 "browser" 的插件
          handsfree.enablePlugins('browser')
          // 開始追蹤
          handsfree.start()

          但是,值得一提的是某些模型體積可能超過 10Mb+,加載可能需要先對較長的時間。因此,如果要自行托管模型并離線使用,可以將模型從 npm 包下載到項目的公共文件夾中,比如 PUBLIC:

          // 在 WINDOWS 平臺上
          xcopy /e node_modules\handsfree\build\lib PUBLIC
          // 在其他平臺上
          cp -r node_modules/handsfree/build/lib/* PUBLIC

          然后按照如下方式引用:

          import Handsfree from 'handsfree'
          const handsfree = new Handsfree({
            hands: true,
            // Set this to your where you moved the models into
            assetsPath: '/PUBLIC/assets',
          })
          handsfree.enablePlugins('browser')
          handsfree.start()

          Handsfree.js 還允許開發(fā)者通過 handsfree.use(pluginName,callback) 創(chuàng)建各種插件,比如:

          // A plugin that console logs your data on every frame
          handsfree.use('consoleLogger', (data) => {
            console.log(data.weboji.rotation, data.pose.data.faceLandmarks)
          })

          以上代碼將創(chuàng)建一個新插件,然后可以使用 handsfree.plugin.consoleLogger 訪問該插件,并將在每一幀上運行。回調(diào)存儲在 handsfree.plugin.consoleLogger.onFrame 中,可以使用以下命令切換插件可用狀態(tài):

          handsfree.plugin.consoleLogger.enable()
          handsfree.plugin.consoleLogger.disable()

          更多關(guān)于 Handsfree.js 的用法和原理可以參考文末資料,本文不再過多展開。

          參考資料

          https://handsfreejs.netlify.app/#models

          https://ko-fi.com/handsfreejs


          主站蜘蛛池模板: 国模吧无码一区二区三区| 99久久无码一区人妻a黑| 天堂资源中文最新版在线一区| 无码人妻一区二区三区一| 久久国产精品一区| 日本韩国一区二区三区| 五十路熟女人妻一区二区 | 国产乱码精品一区二区三区香蕉 | 午夜影院一区二区| 无码少妇一区二区三区浪潮AV| 亚洲AV无码一区二区三区在线观看 | 亚洲国产美女福利直播秀一区二区| 福利一区在线视频| 婷婷亚洲综合一区二区| 免费无码A片一区二三区| 国产精品夜色一区二区三区| 精品人妻AV一区二区三区| 蜜桃视频一区二区三区在线观看| 一区二区三区在线观看视频| 天堂不卡一区二区视频在线观看 | 波多野结衣高清一区二区三区| 视频精品一区二区三区| 一区二区三区在线看| 亚洲视频在线一区二区| 国产激情一区二区三区成人91| 亚洲国产专区一区| 国产精品一区二区久久不卡| 亚洲一区二区三区在线观看精品中文| 视频一区精品自拍| 99精品一区二区免费视频| 韩国美女vip福利一区| 人妻在线无码一区二区三区| 国产主播一区二区三区在线观看| 国产一区二区三区播放| 日韩制服国产精品一区| 人妻无码第一区二区三区 | 大香伊蕉日本一区二区| 国产福利电影一区二区三区久久老子无码午夜伦不 | 日韩免费无码视频一区二区三区| 精品一区二区视频在线观看| 精品福利一区3d动漫|