沉 發(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)新。話不多說,直接開始。
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è)備。
官方提供的以下模型可以實時組合和重新配置:
開發(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
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。