博客 / 詳情

返回

以圖搜圖架構優化:使用客户端模型提取圖像特徵

序言

以圖搜圖系統指的是從圖像內容提取特徵向量,然後使用向量數據庫進行向量數據的插入、刪除、相似性檢索等操作,進而提供根據圖像內容搜索出具有相似內容的其它圖像的功能。

系統架構

典型的搜圖系統整體架構時序圖如下:

圖像上傳過程:

  1. 客户端上傳圖像到服務端。
  2. 服務端存儲圖像至對象存儲、插入結構化數據至關係型數據庫、發送消息至 MQ 消息隊列。
  3. 服務端對客户端請求返回響應。
  4. 圖像搜索服務接受 MQ 的消息,下載圖像內容,使用特定模型提取圖像特徵向量,然後將特徵向量插入到向量數據庫。

這裏使用 MQ 的主要原因有:

  • 異步快速響應,因為提取圖像特徵比較耗時,如果是同步的過程則會對客户端體驗不友好。
  • 解耦服務、服務異構,提取圖像特徵屬於計算機視覺領域,編程語言生態基本是 Python ,而後端服務則常見於 Java、Golang、Node.js 等,這在架構上就要求服務異構和解耦。
  • 削峯填谷,由於用户上傳圖像具有波峯波谷的天然特性,使用 MQ 可以使下游圖像計算保持平穩。

圖像搜索過程:

  1. 客户端上傳圖像到服務端。
  2. 服務端發起調用並將圖像傳遞到圖像搜索服務,圖像搜索服務提取圖像特徵向量,然後查詢向量數據庫進行相似性搜索,最後返回向量搜索結果。
  3. 服務端根據向量搜索結果查詢結構化數據,整合數據,最後響應。

我們可以看到以上系統中,比較耗時的有兩部分:

  1. 圖像傳遞鏈路長:客户端 -> 服務端 -> 對象存儲 -> 圖像搜索服務。
  2. 圖像特徵計算比較耗時、且比較消耗服務器資源。

使用客户端模型優化架構

為了進一步優化系統架構,我們可以嘗試使用客户端模型進行圖像特徵提取。

圖像上傳過程:

  1. 客户端向服務端請求對象存儲的直傳地址,然後客户端直接將圖像內容傳遞到對象存儲(需要對象存儲支持直傳操作)。
  2. 客户端進行本地計算,提取圖像特徵向量,然後傳遞特徵向量和結構化數據給服務端。
  3. 服務端對結構化數據和向量數據分別插入到不同的數據庫,完成響應。

圖像搜索過程:

  1. 客户端進行本地計算,提取圖像特徵向量,然後傳遞特徵向量和結構化數據給服務端。
  2. 服務端分別進行向量檢索和結構化數據查詢,整合數據,完成響應。

優化後的架構:

  • 圖像傳遞鏈路短,只有客户端 -> 對象存儲。
  • 圖像特徵計算卸載到了客户端完成,服務器不需要再消耗計算資源。
  • 減少了 MQ 和圖像搜索服務這兩個構件,架構更加簡單、複雜度降低。

客户端模型的可行性和約束

客户端相比於服務端具有硬件資源有限、且不可擴展的特點,因此這就要求客户端使用的模型要更小、計算消耗更少。

我們根據上圖中的模型對比可以看到 mobilenet 這種模型更符合我們的需求(模型的名字就能看出來)。

示例

以下給出一個前端使用 mobilenet 完成圖像特徵提取的示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet"></script>
</head>
<body>
    <input type="file" id="imageInput">
    <button onclick="extractFeatures()">Extract Features</button>
    <pre id="result"></pre>

    <script>
        let model;

        async function loadModel() {
            if (!model) {
                // 加載模型時 mobilenet 會去 storage.googleapis.com 下載
                model = await mobilenet.load({version: 2, alpha: 1.0});
            }
            return model;
        }

        function preprocessImage(image) {
            const tensor = tf.browser.fromPixels(image)
                .resizeNearestNeighbor([224, 224])
                .toFloat()
                .expandDims();
            return tensor.div(255.0);
        }

        async function extractFeatures() {
            const input = document.getElementById('imageInput');
            if (input.files.length === 0) {
                alert('Please select an image file first.');
                return;
            }

            const model = await loadModel();

            const timeStart = Date.now();

            const file = input.files[0];
            const reader = new FileReader();
            reader.onload = async function (e) {
                const image = new Image();
                image.src = e.target.result;
                image.onload = async function () {
                    const processedImage = preprocessImage(image);
                    const features = model.infer(processedImage, false); // 去掉最後的全連接層
                    const featuresArray = await features.array();
                    document.getElementById('result').textContent = JSON.stringify(featuresArray, null, 2);

                    console.log(`Extract feature spend: ${Date.now() - timeStart} ms`);;
                }
            }
            reader.readAsDataURL(file);
        }
    </script>
</body>
</html>

然後在我的筆記本電腦簡單測試的結果:

從上圖可以看到,在我的客户端處理一張圖像可以在一秒內完成,當然實際耗時取決於硬件資源和圖像大小。

最後,如果你對此類主題感興趣,可以閲讀我的其它相關文章。


參考資料:

  • https://keras.io/api/applications/
  • https://www.tensorflow.org/js/models
  • https://github.com/tensorflow/tfjs-models/tree/master/mobilenet
user avatar FatTiger4399 頭像 ticktank 頭像 prepared 頭像 chazhoudeqingchun 頭像 13917911249 頭像 pudongping 頭像 jianhuan 頭像 mylxsw 頭像 dadegongjian 頭像
9 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.