深度學習的JavaScript基礎:從瀏覽器中提取數據

最近在讀一本《基於瀏覽器的深度學習》,書比較薄,但是涉及的內容很多,因此在讀的過程中不得不再查閱一些資料,以加深理解。我目前從事的本職工作就是瀏覽器研發,對於前端技術並不陌生。但是從前段時間開發微信小程序識狗君的過程來看,對JavaScript還是掌握得太少,特別是對一些前端框架以及一些比較新的JavaScript語法和編程模型,瞭解的不夠。在修改tfjs-core源碼時,就體會到這種痛苦。好吧,既然無法避開,那就正面剛吧。

在python語言中,通過文件、攝像頭獲取數據,並不是什麼難事。但對於瀏覽器來說,出於安全的考慮,並不能直接訪問本地文件,至於訪問攝像頭、麥克風這樣的硬件設備,只是從HTML5纔開始得到支持。本文就如果獲取數據展開討論,看看在瀏覽器中提取數據有哪些方法。

加載圖像數據

圖像分類、對象目標檢測等是機器學習方面的重要應用,這離不開圖像數據。爲了將圖像作爲機器學習算法的輸入,必須事先提取圖像的像素值。

從圖像中提取像素值

熟悉HTML的朋友肯定知道,要在瀏覽器中顯示一幅圖像,通常通過HTML img標籤:

<img src="images/cat.jpg" id="img_cat"></img>

現在我們可以使用全局DOM API document.getElementById(‘img_cat’)訪問圖像元素。問題是這樣獲得的HTMLImageElement類型,並沒有相關的API來提取像素值。此外還需要注意的是,這裏用到的DOM API只在瀏覽器中可用,在Node.js這樣沒有DOM的JavaScript運行時中不可用。

慶幸的是,從HTML 5開始,現代瀏覽器提供了Canvas API,可以用編程的方式將像素繪製到屏幕上,也有相應的API提取像素值。

爲了從Canvas元素中提取數據,我們首先需要創建畫布上下文,在此上下文中,我們可以將圖像內容繪製到畫布上,然後訪問並返回畫布像素數據。

function loadRgbDataFromImage(img) {
    // 創建canvas元素
    const canvas = document.createElement('canvas');

    // 將canvas尺寸設置爲圖像大小
    canvas.width = img.width;
    canvas.height = img.height;

    // 創建2D渲染上下文
    const ctx = canvas.getContext('2d');

    // 將圖像渲染到canvas上下文的坐上角座標(0, 0)
    ctx.drawImage(img, 0, 0, img.width, img.height);

    // 提取RGB數據
    const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);

    // 將數據轉換爲int32數組
    return new Int32Array(imgData.data);
}

在上面的代碼中,ctx.getImagedata函數返回ImageData類型的數據,這是一個包含width, height和data屬性的對象。data屬性值的存儲格式爲類型化數組Uint8ClampedArray。

需要注意的是,圖像是異步加載的,因此我們只有在瀏覽器完全加載了圖像才能提取像素值,這可以在onload事件中完成。

const img = document.getElementById('img_cat');
img.onload = () => {
    const data = loadRgbdataFromImage(img);
    ...
}

加載遠程資源

圖像數據不僅可以是本服務器上的圖片,還可以是其它遠程服務器上的資源,以URL的形式提供。

<img src="https://<other_server>/cat.jpg" crossOrigin="anonymous" id="img_cat"></img>

在加載其它遠程服務器上的資源時,需要了解跨域資源共享(Cross-Origin Resource Sharing, CORS)的概念。出於安全的考慮,瀏覽器會自動阻止對當前連接之外的不同域、協議或端口的cross-site請求。而CORS策略允許瀏覽器通過設置附加的HTTP頭來執行對資源的跨域HTTP請求。比如上面代碼中,使用crossOrigin屬性,並將其設置爲anonymouse,顯式地允許該元素加載cross-site資源。

我們也可以通過JavaScript,以編程方式完成上述代碼的功能。需要注意加載圖像資源是異步行爲,我們返回Promise,而不是已經加載的資源。

function loadImage(url) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.crossOrigin = "anonymous";
        img.src = url;
        img.onload = () => resolv(img);
        img.onerror = reject;
    });
}

加載二進制塊

經過訓練的模型,模型權重、參數等數據,通常以二進制塊的形式保存,所以在瀏覽器中使用機器學習模型,一定會面臨二進制塊的加載問題。好在JavaScript是一種非常通用的語言,內置了對類型化數組和數組緩衝區的支持,這使得在瀏覽器中使用二進制數據非常方便。

相比文本表示格式(如csv或JSON),二進制數據文件更小,加載速度更快(不需要解析),這使得在JavaScript中加載較大規模的模型權重成爲可能。

假如我們有一個二進制塊rand.bin,可以創建一個函數來獲取二進制塊作爲數組緩衝區。

async function loadBinaryDataFromUrl(url) {
    const req = new Request(url);
    const res = await fetch(req);
    if (!res.ok) {
        throw Error(res.statusText);
    }

    return res.arrayBuffer();
}

訪問外設數據

隨着移動終端的普及,以前很多需要電腦上完成的工作,都可以在移動終端上完成,而移動終端豐富且使用方便的外設(相機、麥克風、重力感應器等)提供了多種玩法。早期的瀏覽器訪問設備的能力幾乎沒有,但從HTML5開始,增加了硬件訪問能力,提供了Device API,藉助於Device API,通過JS和HTML頁面訪問終端的應該成爲可能。

從網絡攝像頭獲取圖像

瀏覽器的MediaDevices API允許用戶訪問視頻和音頻設備,例如相機、麥克風和揚聲器。它是更通用的WebRTC API的一部分。

我們可以使用MediaDevices::getUserMedia()函數啓動視頻流,該函數將返回包含MediaStream對象的promise。

navigator.mediaDevices.getUserMedia({ video: true, audio: false})
    .then((stream) => { ... });

爲了從MediaStream中提取數據,需要將流附加到HTML video元素。我們可以通過代碼創建一個這樣的元素,並將流提供給播放器。

const player = document.createElement('video');

navigator.mediaDevices.getUserMedia({ video: true, audio: false})
    .then((stream) => { player.srcObject = stream; });

最後,我們可以從video元素中提取內容,將圖像渲染到畫布,然後提取畫布中的像素。

const data = loadRgbDataFromImage(player, width, height);

是的,這個地方沒有看錯,player也可以傳遞給loadRgbDataFromImage。查看drawImage函數的原型,對於img參數的說明爲:

img:Specifies the image, canvas, or video element to use

也就是說這裏傳遞image、canvas或video都是可以的。

還有一種更高端用法,就是從WebGL中的video元素訪問,而無須使用畫布,有興趣的可以查閱相關資料。

用麥克風錄音

訪問麥克風同樣通過MediaDevices API,處理數據則通過WebAudio API,這是一個非常靈活的基於圖的音頻處理API。

首先使用MediaDevices::getUserMedia()函數檢索音頻流。

navigator.mediaDevices.getUserMedia( { audio: true, video: false })
    .then(onStream);

接下來,設置一個非常簡單的音頻圖,包括輸入、簡單處理器和默認輸出。我們還需定義處理器的屬性,包括輸入和輸出通道的數量以及音頻塊的緩衝區大小。

const audioContext = new AudioContext();

const bufferSize = 4096;
const numInputChannels = 1;
const numOutputChannels = 3;

function onStream(stream) {
    const source = audioContex.createMediaStreamSource(stream);
    const processor = audioContext.createScriptProcessor(
        bufferSize, numInputChannels, numOutputChannels);
    source.connect(processor);
    processor.connect(audioContext.destination);
    processor.onaudioprocess = onProcess;
}

然後,在onProcess中執行處理。

function onProcess(e) {
    const data = e.inputBuffer.getChannelData(0);
    ...
}

AudioBuffer.getChannelData()函數返回Float32Array(bufferSize)的數據,還可以通過duration、sampleRate和numberOfChannels屬性獲得其它音頻信息。

現在我們可以使用這些數據進一步處理或直接送給模型。

小結

本文探討如何在瀏覽器中獲取數據的幾種方法,包括圖像數據、音頻數據,現代瀏覽器具備原來越豐富的設備訪問能力,配合移動終端方便易用的外設,必將產生越來越多的有趣的機器學習應用。

發佈了180 篇原創文章 · 獲贊 447 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章