精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

正品庫拍照PWA應用的實現與性能優化

開發 架構
在技術的實現上,許多時候要去做用空間換時間或用時間換空間的策略方案,本質上還是根據我們當前的業務場景和訴求,追求當下收益。有些時候可能不止局限在實現上,需要從實際需求出發,不應該只停留在工具的層面,而深入到業務里剖析挖掘其潛在的業務價值,做更深遠的思考,從工具思維轉向價值發現與傳遞的方向上。

一、背景與難點

    1. 背景

    2. 難點

二、實現方案

    1. 整體技術實現

    2. 整體架構

    3. 產品使用流程

    4. 操作時序

三、性能優化

    1. 為什么需要性能優化

    2. 做了哪些優化

    3. 方案選擇與實現

    4. 優化后對比

四、業務結果

五、規劃和展望

一、背景與難點

背景

目前得物ERP主要鑒別流程,是通過鑒別師鑒別提需到倉庫,倉庫庫工去進行商品補圖拍照,現有正品庫59%的人力投入在線下商品借取/歸還業務的操作端,目前,線下借取的方式會占用商品資源,同時在使用用途上,每借出10件會出現1次拍照留檔,因此會有大量的線上閱圖量在日常鑒別和學習中發生;正品庫可通過圖庫搭建,提升圖庫質量,大大節約線下用工和物流成本支出。

但目前庫內存量10~20W件,待進行拍照同步到正品庫中,且目前仍不斷有新品入庫,現有的補圖流程效率約每天30件,難以滿足快速正品庫建立的需要, 主要有以下問題:

補圖圖片上傳途徑繁瑣

倉端接收到補圖任務后,需使用ERP網頁端完成圖片拍攝&上傳操作,流程繁瑣,操作冗余。

留檔圖拍攝上傳質量壓縮

新品圖片&補圖圖片上傳ERP后,圖片質量壓縮,部分留檔圖因不清晰需重新拍攝,浪費作業人力。

鑒別借還操作途徑單一

鑒別借用&歸還只能于PC端操作,不利于鑒別在庫內現場進行借用&歸還。

正品流轉效率問題

在圖庫建立前有很多鑒別是需要借用到實物的,借用之后的登記、歸還等流程會大大影響流傳效率,同時存在異地倉庫借閱的情況,成本和周期更高。

優化前后整體方案對比

圖片圖片

綜合來說,其實相當于整體的操作都需要在手持設備上完成(包括上傳、拍攝、通知等),這減少了過程操作繁多而導致的效率問題和圖片質量問題。

演示流程如下:

難點

在Web端上,去實現一個自定義的相機拍攝能力是相對簡單的,實現一個獲取視頻流轉化為圖片的能力也不復雜的。我們的初版應用的拍攝標準是1280x1280的圖片,但鑒別師希望有更高的分辨率,能夠得到原相機一模一樣的拍攝結果,所以必須需要提高分辨率,按照手機原相機的分辨率去加工處理圖片。以倉庫的 iPhoneX 為例:若需分辨率達到超高清范疇的4032 * 3024,庫工需要連續拍攝幾十次甚至上百次的各個模板位的圖片,才能完成一件正品的存檔工作。

綜合難點

分辨率激增帶來的內存壓力
  1. 內存占用暴增,單個從6.4M左右躍升到48.8M,增長7.6倍。
  2. 超高清分辨率需要更多的GPU內存和計算資源。
  3. 高分辨率與流暢體驗難以兼顧。

PWA內存分配限制

  1. 多層內存限制:拿iPhoneX為例,從3GB系統內存到300~500MB的實際可用內存,層層削減。若除去一些基礎的開銷(比如js引擎、WebKit開銷等開銷)后則更少,更容易達到系統限制的內存紅線,進而產生卡頓、失敗、被強制回收,降頻等情況。
  2. Webkit嚴格限制,瀏覽器對單個標簽頁內存使用有硬性上限。

視頻流與圖像處理的資源競爭

  1. 視頻流和圖像處理同時占用大量內存。
  2. GPU資源競爭,視頻解碼和Canvas繪制爭奪GPU資源。

移動設備性能差異化

  1. 硬件碎片化:不同設備內存和性能差異巨大。
  2. 兼容性問題:需要為不同性能的設備提供不同策略,保障任務的進行。

瀏覽器內存管理的不可控性

  1. 內存分配不可預測:系統會根據整機的內存壓力動態調整分配。自身web應用無法參與調控。
  2. GC時機不可控:垃圾回收可能在關鍵時刻觸發,影響作業流程。
  3. 進程終止風險:極端情況下瀏覽器自己會終止頁面,reload。

二、實現方案

整體技術實現

我們整體的技術實現基于 WebRTC 和 HTML5 Canvas 以及Web worker。

WebRTC

navigator.mediaDevices.getUserMedia 是 WebRTC API 的一部分,用于訪問用戶設備的攝像頭和麥克風。它可以請求用戶授權以獲取視頻或音頻流,并將實時媒體流綁定到 <video> 標簽上。

HTML5 的 video

用于顯示攝像頭捕捉到的實時視頻流。

Canvas

通過 canvas 元素,可以從 <video> 標簽的當前幀中捕獲圖像(拍照),并將其轉換為圖片格式(如 PNG 或 JPEG)。

WebWorker

通過允許在后臺線程中運行腳本,避免阻塞主線程(UI 線程),從而解決復雜計算導致的頁面卡頓問題。

整體架構

圖片

整體方案簡要

  1. 在pwa頁面中開啟攝像頭
  2. 獲取視頻流: CameraStreamManager管理相機流,提供video元素
  3. 等待幀穩定
  4. 通過視頻流,創建ImageBitmap
  5. Worker處理: 將ImageBitmap傳遞給Worker進行處理
  6. 策略選擇,根據設備情況做策略選擇
  7. Worker中使用chunked、chunkedConvert等策略分塊處理大圖像
  8. 生成結果: 返回ObjectUrl(內存中的文件或二進制數據)
  9. 更新UI: 更新預覽和上傳隊列
  10. 資源回收
  11. 結束或下一步

其中的實現細節內更多偏向于資源的精細化管理、回收釋放、重試機制、容錯機制等。

最核心的準則是:性能優先,穩定保底。

產品使用流程

圖片圖片

操作流程里的核心是針對此前在電腦和手機中反復切換拍攝、錄入、上傳等復雜的操作,轉變為在手持設備中一站式完成補圖、拍攝、上傳和通知等。

操作時序

圖片圖片

三、性能優化

性能優化思維導圖性能優化思維導圖

為什么需要性能優化

  • 頁面卡頓
  • 低端機型無法順暢拍照
  • 圖片轉化慢,手機熱..
  • 高頻出現圖像轉化失敗
  • 突破內存峰值,系統回收內存降頻等,程序reload
  • ...

首先看下此前的策略中的性能表現,首先我們用的的是超高分辨率的約束配置條件:

const videoConstraints = useRef({
    video: {
      facingMode: 'environment',
      width: {
        min: 1280,
        ideal: 4032,
        max: 4032
      },
      height: {
        min: 720,
        ideal: 3024,
        max: 3024
      },
      frameRate: {
        ideal: 30, // 適當降低可以降低視頻緩沖區的內存占用,我們先按照這樣的場景來看。
        min: 15
      },
      advanced: [
        { focusMode: "continuous" },
      ]
    } as MediaTrackConstraints,
});

如果單獨拍攝一張圖內存,粗略計算為如下(主要以iPhoneX的情況做解析):

// 視頻流約束
const iphoneXStreamConfig = {
  width: 4032,
  height: 3024,
  frameRate: 24,
  format: 'RGBA' // 4字節/像素
};


// 單幀內存計算
const frameMemoryCalculation = {
  // 單幀大小
  pixelCount: 4032 * 3024,                    // = 12,192,768 像素
  bytesPerFrame: 4032 * 3024 * 4,             // = 48,771,072 字節
  mbPerFrame: (4032 * 3024 * 4) / (1024 * 1024), // ≈ 46.51 MB
};


// 實際運行時內存占用
const runtimeMemoryUsage = {
  // 視頻流緩沖區 (至少3-4幀)
  streamBuffer: {
    frameCount: 4,
    totalBytes: 48771072 * 4,        // ≈ 186.04 MB
    description: '視頻流緩沖區(4幀)'
  },
 
  // 處理管道內存
  processingPipeline: {
    captureBuffer: 46.51,            // 一幀的大小
    processingBuffer: 46.51,         // 處理緩沖
    encoderBuffer: 46.51 * 0.5,      // 編碼緩沖(約半幀)
    totalMB: 46.51 * 2.5,           // ≈ 116.28 MB
    description: '視頻處理管道內存'
  },
  
  // 總體內存
  total: {
    peakMemoryMB: 186.04 + 116.28,  // ≈ 302.32 MB
    stableMemoryMB: 186.04 + 93.02, // ≈ 279.06 MB
    description: '預估總內存占用'
  }
};

單張圖的內存占用

圖片圖片

按照上文的視頻約束條件,單幀大小:約 46.51MB,實際單張內存需要76.7M左右(15 + 15 + 46.5 + 0.2 「objectURL引用」),三五張圖大概就會達到內存限制紅線,這樣的內存占用對移動設備來說太大了,實際上,在項目上線初期,業務使用也反饋:拍照幾張手機發熱嚴重,頁面經常卡死。

PWA相機應用內存占用情況

圖片圖片

在移動端中,特別是ios,內存限制是動態的,依賴多個因素,如:設備物理內存總量,設備當前可用內存,后臺的軟件運行情況。上文可以看出至少有300M是固定支出的,還需增加一些WebRtc視頻幀緩沖累積的占用、瀏覽器內存緩存解碼幀的堆積。

在iPhone的WeKit的內核瀏覽器下,官方內存限制雖是1.5G,實際上可能在是800-1200M左右,在實際的測試場景下,甚至還要低很多。

拍攝過程內存變化

圖片圖片

秒數是為了更直觀的觀察區分內存數據的變化。

有些并不能立即回收canvas對象,需要等之前的二進制blob文件被回收后才可進行,這無疑是在慢慢增加內存的壓力。

內存壓力趨勢分析

基于上文的單獨內存占用和相機應用的內存占用(按照1.5G的分配),可以粗略分析出:

圖片圖片

圖片圖片

這些大部分都是官方的數據計算和累積,在實際操作中,如果操作過快,差不多會在第三、四張時開始出現問題了。因為變量比較多,比如充電或發熱情況;而連續作業時候的情況又各不同,但是整體規律是差不多的。上文分析的是5張開始危險,實際情況則是第三張就已經出現問題了。

不僅如此,在拍攝作業流程中,還有CPU的熱節流風險,如內存85%使用率超過30秒,cpu會降頻至70%或更低的性能。

這其中的主要消耗是:視頻流處理(35-45%) + Canvas處理(25-35%)  及4032×3024這類大分辨率導致的計算密集型操作。

做了哪些優化

  • canvas主線程繪制更改為離屏渲染繪制
  • 視頻流管理、前置設備參數預熱
  • 分辨率管理
  • 引入Webworker線程單獨繪制
  • 優化設備檢測策略
  • 異步上傳管理
  • 產品兜底,頁面reload,緩存歷史數據
  • 內存分配模型

方案選擇與實現

實現原相機拍攝的最初的一版,是通過把canvas內容轉為base64后,同步上傳圖片,最初通過一些低端機的測試情況來看,最主要的問題是圖片比較大,生成的base64的code自然也比較大,在數據體積上會增大33%左右。 因為是移動設備,這么大的圖片上傳的速度又相對緩慢,導致操作的過程需要等待和加載。

在這樣的場景下為什么要異步上傳呢,如果拍攝的快些,頁面會變得很卡頓。由于大量的字符串涌入到頁面中,再加上cavans轉化這么大的image到base64 code又會比較消耗內存,所以整體有丟幀卡頓的表現。進而考慮替換為blobUrl。

toDataURL 和 toBlob對比

圖片圖片

如上所示,我們最終選擇了性能更好的canvas to Blob并使用二進制的形式。

  • 更快的回顯
  • 更快的轉化
  • 更小的內存占用

在運用了 Blob 后, 通過埋點等操作,頁面渲染和流暢度雖然有所緩解,但會在比較高頻的情況下出現圖片轉化失敗,而且也是間隔性的,如上文所示,我們根據渲染和一些實際案例分析過后,發現問題還是存在于內存峰值和CPU資源。

canvas.convertToBlob失敗主要是因為內存的限制問題,特別是在處理大圖像時。編碼同一圖像可能在資源充足時成功,資源緊張時失敗,這也就解釋了為什么是間隔性的出現轉化失敗。

因為有大量的繪制需在主線程完成,但由于JS的單線程問題,嚴重影響了頁面的操作和后續的渲染, 使得庫工的作業流程被迫等待。因此,我們引入了WebWorker以及OffscreenCanvas,開啟新線程專一用來做繪制。當然Webworker中的內存的管理也是比較復雜的,同樣會占據大量內存,也有數據通信成本,但是相較于用戶體驗,我們不得不做一定程度的平衡和取舍。

Web Worker + OffscreenCanvas 架構

圖片圖片

  • 主線程不阻塞:圖像處理在Worker中進行,UI保持響應
  • 更好的性能:OffscreenCanvas在獨立線程中渲染
  • 內存隔離:Worker獨立內存空間,避免主線程內存壓力

好處就是可以多張并發,降低內存泄漏風險,劣勢是開發復雜度增加,調試困難, 數據傳輸開銷(ImageBitmap需要轉移所有權)。

相機資源的動態管理與釋放

我們知道每個機器的分辨率與他們對WebRtc相關能力的支持是不同的。比如iPhoneX 的最大分辨率支持是:4032 * 3024,其他的機器則會不同,所以固定的分辨率配置是行不通的,需要在進入相機后檢查設備支持情況等。以及視頻通道的保留操作和暫時性暫停,也對操作流程產生著很大積極影響。在繼續服用的場景下僅暫停數據傳輸,保持活躍連接,在下一張拍攝的時候復用連接,而非重新進行初始化、連接和檢查等操作。

圖片圖片

ImageBitmap 直接創建策略

在繪制中,如果 imageData 是普通的 Image 或 Canvas,每次 drawImage 都可能涉及格式轉換和內存拷貝,無疑增大了內存支出。引入 ImageBitmap,因其是專門為高性能圖像作處理設計,數據存儲在 GPU 內存中,最重要的是:它支持內存的復制轉義,可以交到Webworker中去處理,可以在主線程和 Worker 之間零拷貝傳輸,在worker中直接使用,無需解碼。

直接從視頻流創建ImageBitmap,跳過Canvas中間步驟。

...
let imageBitmap: ImageBitmap | null = null;
// 判斷是否為視頻元素,如果是則嘗試直接創建ImageBitmap
// 支持img 和 vedio
if ((source instanceof HTMLVideoElement || source instanceof HTMLImageElement) && supportsImageBitmap) {
  try {
    console.log('嘗試直接從視頻元素創建ImageBitmap');
    // 直接從視頻元素創建ImageBitmap,跳過Canvas中間步驟
    if (source instanceof HTMLVideoElement) {
      imageBitmap = await createImageBitmap(
        source,
        0, 0, sourceWidth, sourceHeight
      );
    } else {
      // 支持img
      imageBitmap = await createImageBitmap(source);
    }
    console.log('直接創建ImageBitmap成功!!');
  } catch (directError) {
    console.warn('這直接從視頻創建ImageBitmap失敗,回退到Canvas:', directError);
    // 失敗后將通過下面的Canvas方式創建
    imageBitmap = null;
  }
 }
 ...

createImageBitmap 實際上是:

  • 創建一個位圖引用
  • 可能直接使用視頻解碼器的輸出緩沖區
  • 在支持的平臺上,直接使用GPU內存中的紋理
  • 最重要的是:不涉及實際的像素繪制操作、高效的跨線程傳輸(支持通過結構化克隆算法高效傳輸避免了序列化/反序列化開銷,能高效傳送到Worker)
綜合表現
  • 性能最優: 避免Canvas繪制的中間步驟。
  • 內存效率: 直接從視頻幀創建位圖,占用更低。
  • 硬件加速: 可利用GPU加速。

Worker中的圖像處理策略

在web端,主線程和Worker間的數據傳輸有三種方式,結構化克隆和Transferable對象,ShareArrayBuffer(共享內存訪問,支持度有問題),整體上使用Transferable對象的形式,可降低內存消耗。接下來,我們簡單介紹這里用到的兩種執行策略。

chunked策略(chunked processing分塊處理)

主要源于內存控制,避免圖像過大導致的內存溢出。將大圖像分割成多個小塊,使用一個小的臨時畫布逐塊處理后繪制到最終畫布,通過"分而治之"的策略顯著降低內存峰值使用,避免大圖像處理時的內存溢出問題。

劣勢是處理時間增加,算法復雜度高。

chunked策略流程示意chunked策略流程示意

class ChunkedProcessStrategy extends ImageProcessStrategy {
  readonly name = 'chunked';
  
  protected async doProcess(imageData: ImageBitmap, options: ProcessOptions): Promise<Blob> {
    const { width, height, quality } = options;
    const optimalChunkSize = ResourceManager.calculateOptimalChunkSize(width, height);
    
    const chunkConfig: ChunkConfig = {
      size: optimalChunkSize,
      cols: Math.ceil(width / optimalChunkSize),
      rows: Math.ceil(height / optimalChunkSize),
    };
    
    const { canvas: finalCanvas, ctx: finalCtx } = ResourceManager.createCanvas(width, height);
    const { canvas: tempCanvas, ctx: tempCtx } = ResourceManager.createCanvas(optimalChunkSize, optimalChunkSize);
   
    try {
      for (let row = 0; row < chunkConfig.rows; row++) {
        for (let col = 0; col < chunkConfig.cols; col++) {
          await this.processChunk(
            imageData,
            tempCanvas,
            tempCtx,
            finalCtx,
            row,
            col,
            chunkConfig,
            width,
            height
          );
       
          await new Promise(resolve => setTimeout(resolve, 0));
        }
      }
      
      return await finalCanvas.convertToBlob({
        type: 'image/jpeg',
        quality,
      });
    } finally {
      ResourceManager.releaseResources(tempCanvas, tempCtx);
      ResourceManager.releaseResources(finalCanvas, finalCtx);
    }
  }
  
  private async processChunk(
    imageData: ImageBitmap,
    tempCanvas: OffscreenCanvas,
    tempCtx: OffscreenCanvasRenderingContext2D,
    finalCtx: OffscreenCanvasRenderingContext2D,
    row: number,
    col: number,
    chunkConfig: ChunkConfig,
    width: number,
    height: number
  ): Promise<void> {
    const x = col * chunkConfig.size;
    const y = row * chunkConfig.size;
    const chunkWidth = Math.min(chunkConfig.size, width - x);
    const chunkHeight = Math.min(chunkConfig.size, height - y);
   
    tempCtx.clearRect(0, 0, chunkConfig.size, chunkConfig.size);
   
    tempCtx.drawImage(
      imageData,
      x, y, chunkWidth, chunkHeight,
      0, 0, chunkWidth, chunkHeight
    );
    
    finalCtx.drawImage(
      tempCanvas,
      0, 0, chunkWidth, chunkHeight,
      x, y, chunkWidth, chunkHeight
    );
  }
}
  ...

主要針對中等性能的機型,適用于直接轉化可能失敗的情形。

chunkedConvert策略(分塊處理轉化)

將大圖像分塊后,每塊獨立轉換為壓縮的Blob存儲,最后再將所有Blob重新解碼,同時合并到最終畫布,通過"分塊壓縮存儲 + 最終合并"的策略實現極致的內存控制,但代價是處理時間翻倍,屬于時間換內存的策略。

chunkedConvert策略流程示意chunkedConvert策略流程示意

// 分塊轉化 最終返回
class ChunkedProcessStrategy extends ImageProcessStrategy {
  readonly name = 'chunked';
 
  protected async doProcess(imageData: ImageBitmap, options: ProcessOptions): Promise<Blob> {
    const { width, height, quality } = options;
    const optimalChunkSize = ResourceManager.calculateOptimalChunkSize(width, height);
   
    const chunkConfig: ChunkConfig = {
      size: optimalChunkSize,
      cols: Math.ceil(width / optimalChunkSize),
      rows: Math.ceil(height / optimalChunkSize),
    };
   
    const { canvas: finalCanvas, ctx: finalCtx } = ResourceManager.createCanvas(width, height);
    const { canvas: tempCanvas, ctx: tempCtx } = ResourceManager.createCanvas(optimalChunkSize, optimalChunkSize);
   
    try {
      for (let row = 0; row < chunkConfig.rows; row++) {
        for (let col = 0; col < chunkConfig.cols; col++) {
          await this.processChunk(
            imageData,
            tempCanvas,
            tempCtx,
            finalCtx,
            row,
            col,
            chunkConfig,
            width,
            height
          );
         
          // 給GC機會
          await new Promise(resolve => setTimeout(resolve, 0));
        }
      }
      
      return await finalCanvas.convertToBlob({
        type: 'image/jpeg',
        quality,
      });
    } finally {
      ResourceManager.releaseResources(tempCanvas, tempCtx);
      ResourceManager.releaseResources(finalCanvas, finalCtx);
    }
  }
  
  private async processChunk(
    imageData: ImageBitmap,
    tempCanvas: OffscreenCanvas,
    tempCtx: OffscreenCanvasRenderingContext2D,
    finalCtx: OffscreenCanvasRenderingContext2D,
    row: number,
    col: number,
    chunkConfig: ChunkConfig,
    width: number,
    height: number
  ): Promise<void> {
    const x = col * chunkConfig.size;
    const y = row * chunkConfig.size;
    const chunkWidth = Math.min(chunkConfig.size, width - x);
    const chunkHeight = Math.min(chunkConfig.size, height - y);
   
    tempCtx.clearRect(0, 0, chunkConfig.size, chunkConfig.size);
  
    tempCtx.drawImage(
      imageData,
      x, y, chunkWidth, chunkHeight,
      0, 0, chunkWidth, chunkHeight
    );
    
    finalCtx.drawImage(
      tempCanvas,
      0, 0, chunkWidth, chunkHeight,
      x, y, chunkWidth, chunkHeight
    );
  }
}


...
...


class ChunkedConvertStrategy extends ImageProcessStrategy {
  readonly name = 'chunkedConvert';
 
  protected async doProcess(imageData: ImageBitmap, options: ProcessOptions): Promise<Blob> {
    const { width, height, quality } = options;
    const config = WorkerConfig.getInstance();
   
    const chunks: Array<{
      blob: Blob;
      x: number;
      y: number;
      width: number;
      height: number;
    }> = [];
   
    // 分塊處理
    for (let y = 0; y < height; y += config.chunkSize) {
      for (let x = 0; x < width; x += config.chunkSize) {
        const chunkWidth = Math.min(config.chunkSize, width - x);
        const chunkHeight = Math.min(config.chunkSize, height - y);
       
        const chunk = await this.processSingleChunk(
          imageData, x, y, chunkWidth, chunkHeight, quality
        );
      
        chunks.push({ ...chunk, x, y, width: chunkWidth, height: chunkHeight });
        
        await new Promise(resolve => setTimeout(resolve, 0));
      }
    }
    
    // 合并塊
    return chunks.length === 1 ? chunks[0].blob : await this.mergeChunks(chunks, width, height, quality);
  }
  
  private async processSingleChunk(
    imageData: ImageBitmap,
    x: number,
    y: number,
    width: number,
    height: number,
    quality: number
  ): Promise<{ blob: Blob }> {
    const { canvas, ctx } = ResourceManager.createCanvas(width, height);
   
    try {
      ctx.drawImage(imageData, x, y, width, height, 0, 0, width, height);
      const blob = await canvas.convertToBlob({
        type: 'image/jpeg',
        quality,
      });
      return { blob };
    } finally {
      ResourceManager.releaseResources(canvas, ctx);
    }
  }
  
  private async mergeChunks(
    chunks: Array<{ blob: Blob; x: number; y: number; width: number; height: number }>,
    width: number,
    height: number,
    quality: number
  ): Promise<Blob> {
    const { canvas: finalCanvas, ctx: finalCtx } = ResourceManager.createCanvas(width, height);
   
    try {
      for (const chunk of chunks) {
        const imgBitmap = await createImageBitmap(chunk.blob);
      
        try {
          finalCtx.drawImage(
            imgBitmap,
            0, 0, chunk.width, chunk.height,
            chunk.x, chunk.y, chunk.width, chunk.height
          );
        } finally {
          imgBitmap.close();
        }
      
        await new Promise(resolve => setTimeout(resolve, 0));
      }
    
      return await finalCanvas.convertToBlob({
        type: 'image/jpeg',
        quality,
      });
    } finally {
      ResourceManager.releaseResources(finalCanvas, finalCtx);
    }
  }
}

會有更小的峰值,適配與更低端的機型和極大圖像。不會內存溢出,但是也會降低轉化效率。在可用與效率方面,選擇了可用。

其中整體方案里還有一些其他的策略,如Direct直接轉化、邊轉化邊繪制等,會根據不同的機型進行選擇。目前,重點保障低端機型,因為中高端機器在使用過程中沒有性能上的卡點。

優化后對比

首先,我們明確了這幾個主要策略:

  • Web Worker架構 - 主線程內存壓力分散
  • ImageBitmap直接傳輸 - 減少內存拷貝
  • 繪制分塊處理 - 降低內存峰值
  • 資源管理優化 - Canvas復用和及時釋放

最重要策略:增加很多管理器和優化方式降低內存的峰值,即那一瞬間的值。

同時,將可以在后臺做轉化和運算的操作,投入到web worker中去做,降低主線程的內存壓力。

優化后單圖內存占用情況

圖片圖片

優化后PWA相機應用內存占用

圖片

優化后的效果

內存優化結果

圖片圖片

  1. 單張圖片處理峰值減少33% - 從123.2MB降至82.2MB。
  2. 單張圖片持久占用減少61% - 從76.7MB降至30.2MB。
  3. PWA應用整體內存優化16-26% - 根據圖片數量不同。
  4. 內存壓力等級顯著降低,如從3-4張開始有明顯警示壓力,到操作快速秒級拍攝速率時才出現(實際操作過程中大概10-15秒一張,因需要擺放和根據模版與提醒進行拍攝)。
用戶體驗
  • 最終在高清圖片的繪制作業流程中,由原來的3張圖告警到一次性可以拍攝50張圖的情況,大大降低了失敗風險。提升了作業的流暢度。
  • 用戶體驗改善,消除UI阻塞,響應時間減半。

四、業務結果

通過幾輪的策略優化,整個pwa應用已可以相對順暢、高效的繪制原相機標準的正品圖,已完全達到鑒別師高清圖的要求,同時不會有操作流的中斷。

  • 目前日均的拍攝件數提升 330%,達成預期目標。
  • 將每件的人力投入成本降低 41.18%。
  • 目前通過PWA項目快速搭建了圖庫項目,Q2拍照數據占比72.5%,預期后面比例會逐步升高,圖庫流轉效率提高到了20%,超出業務預期。

圖片圖片

五、規劃和展望

在技術的實現上,許多時候要去做用空間換時間或用時間換空間的策略方案,本質上還是根據我們當前的業務場景和訴求,追求當下收益。有些時候可能不止局限在實現上,需要從實際需求出發,不應該只停留在工具的層面,而深入到業務里剖析挖掘其潛在的業務價值,做更深遠的思考,從工具思維轉向價值發現與傳遞的方向上。

未來我們還會思考:

  1. 前置對設備的綜合能力評估,更精細化的拆分低、中、高端設備和適配策略,收集更多的實際處理時間和內存峰值、CPU 性能指標等,用于不斷優化策略選擇算法。
  2. 根據類目做區分(比如鞋服、奢品),這些在鑒別的時候圖片質量有不同的品質要求的分類。后續可能會進行更加具有定制化屬性的方案,針對鑒別打標,針對當前業務中圖片拍攝重試場景下的AI圖像識別,針對重復拍攝場景做優化,進一步提高效率。
  3. 針對目前 10 到 15 秒的拍攝時間,能進一步壓縮問題,思考更加智能的拍攝能力。根據設備的真實情況,或基于色溫分析的光線評估,提高圖像質量和降低重復率。基于正品特征進行構圖優化,在設備上做實時拍攝指導,不只以單一模板和示例進行人工檢查,而是進一步標準化,降低人力參與度。
  4. 針對于商研側業務和前置拍照流程,將拍照H5的方案也納入采賣商品入庫流程,同時支持鑒別師對于圖庫的驗收,加快圖庫的驗收入庫效率,縮短庫內的拍照數據積壓周期。
責任編輯:武曉燕 來源: 得物技術
相關推薦

2020-03-30 14:00:21

Flutter前端代碼

2010-03-05 10:01:23

2023-09-08 15:37:29

軟件開發性能

2012-05-18 15:10:22

HTML5

2009-11-18 09:00:17

數據庫優化應用程序性能

2010-11-15 16:20:33

Oracle系統優化

2013-09-17 10:32:08

Android性能優化數據庫

2023-08-29 15:10:04

持續性能優化開發

2024-10-09 23:32:50

2012-07-06 09:51:34

2021-01-29 14:24:33

桌面瀏覽器Firefox

2018-05-16 07:41:29

圖片代碼資源

2025-02-25 12:00:00

Java線程開發

2022-10-28 13:41:51

字節SDK監控

2010-05-24 14:59:29

Hadoop集群

2021-02-02 13:45:31

Vue代碼前端

2009-03-17 09:28:22

接口VCMySQL

2022-01-09 16:45:36

前端性能優化編程

2011-08-02 09:04:02

移動應用開發

2012-10-09 09:43:50

WLAN優化無線局域網WLAN
點贊
收藏

51CTO技術棧公眾號

国产欧美日韩另类视频免费观看 | 麻豆91在线播放免费| 亚洲香蕉成人av网站在线观看| 色999五月色| 国产av一区二区三区| 亚洲午夜极品| 国产一区二区三区在线观看视频| 玩弄中年熟妇正在播放| 成人影院免费观看| 成人免费视频视频| 国产精品亚洲自拍| 天堂网av手机版| 一区二区在线| 国产亚洲日本欧美韩国| 一二三区视频在线观看| 小明成人免费视频一区| 亚洲成人av一区二区三区| 亚洲午夜精品久久久中文影院av| 亚洲成熟少妇视频在线观看| 艳女tv在线观看国产一区| 亚洲乱码av中文一区二区| 伊人色在线视频| 日韩免费小视频| 亚洲国产一区二区在线播放| dy888午夜| eeuss影院在线播放| 91免费观看在线| 成人自拍视频网站| 国产一区二区在线视频聊天| 麻豆成人精品| 97在线观看视频国产| 九九视频免费观看| 国产精品久久久久久久久久10秀| 51午夜精品国产| 成年人黄色片视频| 草草在线观看| 黄网动漫久久久| 激情五月五月婷婷| 免费在线观看av片| 中文字幕不卡的av| 青青草原亚洲| 黄色小视频在线观看| 99精品视频在线观看| 超碰在线观看97| www.久久伊人| 国产精品综合二区| 91精品免费| www.久久久久久久久久| 国产99精品国产| 91嫩草视频在线观看| 国产ts人妖调教重口男| 国产尤物一区二区在线 | 国产ktv在线视频| 亚洲永久精品国产| 免费视频爱爱太爽了| 成人在线免费看黄| 亚洲三级在线播放| 免费观看国产视频在线| 一色桃子av在线| 亚洲美女在线一区| 国产在线xxxx| 免费在线小视频| 欧美日韩美女在线| 成年人黄色片视频| 欧美高清免费| 欧美一区二区网站| 亚洲欧洲国产视频| 琪琪久久久久日韩精品| 亚洲免费电影一区| 久久久久99精品成人| 欧美国产小视频| 色综合导航网站| 国产午夜免费视频| 欧美亚洲一区二区三区| 国产精品极品尤物在线观看 | 国产精品色网| 欧美在线视频观看免费网站| 在线观看免费高清视频| 国产精品一二三四| 久久久久久精| 欧美天天影院| 亚洲成人免费视| 动漫av免费观看| 国产精品久久免费视频 | 天堂社区在线视频| 四虎成人精品一区二区免费网站| 狠狠爱在线视频一区| 日韩在线第三页| 精品视频一二| 亚洲精品综合精品自拍| 欧日韩不卡视频| 激情综合电影网| 国产精品成人av性教育| a天堂在线观看视频| 91在线精品秘密一区二区| 不卡中文字幕在线| 午夜伦理福利在线| 欧美一区二区三区不卡| 在线免费观看日韩av| 欧美国产一级| 日本成人激情视频| 亚洲成人黄色片| 亚洲国产经典视频| 国产 日韩 亚洲 欧美| 电影亚洲一区| 亚洲精品中文字| 成人免费毛片东京热| 日韩高清一区在线| 国产精品久久久久久久久婷婷| 国产高清视频免费观看| 久久精品日韩一区二区三区| 在线观看18视频网站| 一呦二呦三呦精品国产| 精品国产乱码久久久久久闺蜜| 一级黄色大片免费看| 欧美色蜜桃97| 欧洲美女免费图片一区| www.好吊色| 综合久久久久久| 无码少妇一区二区三区芒果| 盗摄系列偷拍视频精品tp| 久久精品中文字幕| 中文字幕在线视频第一页| 91一区二区在线观看| av网站大全免费| 精品国产乱码一区二区三区| 中文字幕日韩精品有码视频| www.国产一区二区| 99精品在线观看视频| 人人妻人人澡人人爽欧美一区双 | 国产香蕉视频在线看| 老色鬼久久亚洲一区二区| www.久久爱.cn| 国产激情视频在线观看| 欧美色图在线观看| 一级特黄曰皮片视频| 亚洲午夜av| 国产91精品入口17c| 日本aa在线| 日韩午夜电影在线观看| 少妇被躁爽到高潮无码文| 蜜桃一区二区三区四区| 午夜视频久久久| 91tv亚洲精品香蕉国产一区| 国产一区二区日韩| 国产美女www爽爽爽| 国产欧美1区2区3区| 92看片淫黄大片一级| 国产成人av| 国产精品扒开腿做爽爽爽男男| 国产精品无码久久av| 一区二区中文视频| 污版视频在线观看| 欧美mv日韩| 91欧美精品成人综合在线观看| 四虎免费在线观看| 午夜精品久久久久久久久久| 亚洲精品乱码久久久久久蜜桃图片| 亚洲最大在线| 国产精品第二页| 自拍视频在线播放| 8x8x8国产精品| 青青草手机在线视频| 不卡视频在线观看| 漂亮人妻被中出中文字幕| 国产精品片aa在线观看| 国产精品av在线| 日本精品在线| 日韩精品中文字幕在线一区| 日产欧产va高清| 国产亚洲综合在线| 国产又粗又长又爽又黄的视频| 国产一区二区三区日韩精品| 国产美女被下药99| 尤物在线网址| 亚洲欧美在线一区二区| 一区二区视频在线免费观看| 亚洲欧美日韩国产手机在线| 欧美极品jizzhd欧美仙踪林| 久久激情网站| 国产奶头好大揉着好爽视频| 少妇精品在线| 国产成人久久久| 91麻豆免费在线视频| 日韩成人av在线播放| 久久久久久av无码免费看大片| 97超碰欧美中文字幕| 久久精品午夜福利| 一本一道久久a久久精品蜜桃| 国产美女精品视频| 欧美家庭影院| 在线日韩日本国产亚洲| 亚洲国产www| 色婷婷亚洲一区二区三区| 丁香花五月激情| 久久久久久免费| 国产高潮失禁喷水爽到抽搐| 青娱乐精品视频在线| 欧美一级免费播放| 先锋资源久久| 欧美另类视频在线| 在线精品自拍| 国产精品久久久久久久美男| 成年人视频免费在线播放| 在线电影av不卡网址| 四虎在线视频免费观看| 欧美精品欧美精品系列| 国产又大又粗又爽| 亚洲国产视频在线| 国产精品免费在线视频| 久久久亚洲精品石原莉奈| 极品白嫩的小少妇| 精品一区二区三区免费毛片爱| 中文字幕色一区二区| 最新亚洲精品| 国产区二精品视| 另类视频一区二区三区| 国产精品视频中文字幕91| 日韩深夜视频| 久久频这里精品99香蕉| 黄黄的网站在线观看| 亚洲性无码av在线| 天堂a√在线| 亚洲福利视频二区| www.av黄色| 欧美一区二区三区日韩| 亚洲无码精品在线播放| 91福利视频久久久久| 国产精品不卡av| 一区二区三区四区亚洲| 91久久国产综合| 国产精品久久久久国产精品日日| 国产xxxxhd| 麻豆一区二区在线| 97公开免费视频| 美女网站久久| 国产aaa一级片| 久久久精品日韩| 少妇高清精品毛片在线视频| 在线综合视频| 男女激情无遮挡| av成人毛片| 中文字幕无码精品亚洲35| 亚洲韩日在线| 欧美视频在线观看网站| 亚洲精品日本| 欧美性大战久久久久xxx| 亚洲久久在线| 116极品美女午夜一级| 久久久久99| 激情婷婷综合网| 美女在线视频一区| 在线观看免费av网址| 国产在线精品一区二区不卡了| 人妻少妇精品久久| 亚洲一区二区动漫| 干日本少妇首页| 日韩精品亚洲专区| 久草福利视频在线| 久久99精品国产.久久久久久| 老太脱裤子让老头玩xxxxx| 在线日韩中文| 精品久久久久久久免费人妻| 青青青爽久久午夜综合久久午夜| 日本一级黄视频| 欧美日韩天堂| 国产精品一区二区免费在线观看| 欧美激情视频一区二区三区免费| 欧美高清性xxxxhd| 成人女性视频| 日本a级片在线播放| 亚洲影视在线| 91高清国产视频| 粉嫩aⅴ一区二区三区四区 | 欧美在线视频第一页| 亚洲综合激情另类小说区| 在线观看免费国产视频| 色欧美乱欧美15图片| 91亚洲视频在线观看| 欧美成人精精品一区二区频| 亚洲色图21p| 色七七影院综合| 国产区美女在线| 国产精品高清免费在线观看| 亚洲国产高清在线观看| 欧美精品一区二区三区四区五区 | 亚洲爱爱爱爱爱| 伦理片一区二区三区| 久久最新资源网| 自拍在线观看| 91夜夜未满十八勿入爽爽影院| 福利一区二区| 国产亚洲欧美一区二区| 日韩av有码| 欧美精品自拍视频| 久热成人在线视频| 中文字幕人妻一区二区三区| 国产精品白丝在线| 天天干天天干天天| 日韩一级大片在线| 国产私拍精品| 久久久久久噜噜噜久久久精品| 日韩经典av| 国产精品亚洲激情| 国产丝袜一区| 手机看片日韩国产| 日本人妖一区二区| 玖玖爱在线精品视频| 亚洲欧美日韩中文播放| 波多野结衣在线观看视频| 亚洲成人免费在线视频| 国产成人无吗| 国产欧美日韩91| 免费黄色成人| 黄www在线观看| 国产999精品久久| 久久高清内射无套| 欧美日本国产一区| 成人高潮成人免费观看| 秋霞av国产精品一区| 大陆精大陆国产国语精品| 一区中文字幕在线观看| 日本va欧美va瓶| 国产精品20p| 狠狠躁夜夜躁人人爽天天天天97| 亚洲熟妇无码乱子av电影| 日韩免费看网站| 大片免费在线看视频| 国产日韩av在线| 成人羞羞视频在线看网址| 91av在线免费播放| 久久嫩草精品久久久精品| 久久不卡免费视频| 亚洲精品国产品国语在线| av中文字幕电影在线看| 国产二区不卡| 欧美色123| 久久久久亚洲AV成人网人人小说| 久久久三级国产网站| 日韩xxxxxxxxx| 亚洲国内精品视频| 懂色av一区| 国语精品免费视频| 国产欧美日本| xxxxx在线观看| 色视频一区二区| www.久久热.com| 国产裸体写真av一区二区| 午夜免费一区| 久久精品一二三四| 亚洲最新在线观看| 欧美一区二区黄片| 91精品91久久久久久| 国产成人三级| 亚洲免费av一区二区三区| 国产精品色眯眯| 国产精品久久久久久久久久久久久久久久| 精品久久免费看| 九九精品调教| 国模精品一区二区三区| 久久在线精品| 国产熟女一区二区| 欧美三级视频在线播放| 麻豆网站在线免费观看| 99在线影院| 国产农村妇女毛片精品久久莱园子 | 国产精品1区2区3区4区| 欧美久久久久久久久久| 2024最新电影在线免费观看| 国产日韩在线一区二区三区| 国产欧美丝祙| 成人欧美一区二区三区黑人一| 色嗨嗨av一区二区三区| 日本在线看片免费人成视1000| 欧美在线视频在线播放完整版免费观看| 一区二区三区无毛| 青草网在线观看| 97成人超碰视| 国产精品久久免费| 性色av一区二区三区红粉影视| 亚洲日本视频在线| 日韩av片在线看| 《视频一区视频二区| 成人久久精品人妻一区二区三区| 九九精品视频在线观看| 美女网站色精品尤物极品姐弟| 日本大片免费看| 久久久综合网站| 午夜精品无码一区二区三区| 庆余年2免费日韩剧观看大牛| 国产在线播放精品| av污在线观看| 偷窥国产亚洲免费视频| 18视频免费网址在线观看| 国产精品一区二区免费看| 久久久久久黄| 精品午夜福利在线观看| 原创国产精品91| 狼人精品一区二区三区在线| 亚洲va综合va国产va中文|