一、背景與難點
背景
目前得物ERP主要鑑別流程,是通過鑑別師鑑別提需到倉庫,倉庫庫工去進行商品補圖拍照,現有正品庫59%的人力投入在線下商品借取/歸還業務的操作端,目前,線下借取的方式會佔用商品資源,同時在使用用途上,每借出10件會出現1次拍照留檔,因此會有大量的線上閲圖量在日常鑑別和學習中發生;正品庫可通過圖庫搭建,提升圖庫質量,大大節約線下用工和物流成本支出。
但目前庫內存量10~20W件,待進行拍照同步到正品庫中,且目前仍不斷有新品入庫,現有的補圖流程效率約每天30件,難以滿足快速正品庫建立的需要, 主要有以下問題:
※ 補圖圖片上傳途徑繁瑣
倉端接收到補圖任務後,需使用ERP網頁端完成圖片拍攝&上傳操作,流程繁瑣,操作冗餘。
※ 留檔圖拍攝上傳質量壓縮
新品圖片&補圖圖片上傳ERP後,圖片質量壓縮,部分留檔圖因不清晰需重新拍攝,浪費作業人力。
※ 鑑別借還操作途徑單一
鑑別借用&歸還只能於PC端操作,不利於鑑別在庫內現場進行借用&歸還。
※ 正品流轉效率問題
在圖庫建立前有很多鑑別是需要借用到實物的,借用之後的登記、歸還等流程會大大影響流傳效率,同時存在異地倉庫借閲的情況,成本和週期更高。
優化前後整體方案對比
綜合來説,其實相當於整體的操作都需要在手持設備上完成(包括上傳、拍攝、通知等),這減少了過程操作繁多而導致的效率問題和圖片質量問題。
難點
在Web端上,去實現一個自定義的相機拍攝能力是相對簡單的,實現一個獲取視頻流轉化為圖片的能力也不復雜的。我們的初版應用的拍攝標準是1280x1280的圖片,但鑑別師希望有更高的分辨率,能夠得到原相機一模一樣的拍攝結果,所以必須需要提高分辨率,按照手機原相機的分辨率去加工處理圖片。以倉庫的 iPhoneX 為例:若需分辨率達到超高清範疇的4032 * 3024,庫工需要連續拍攝幾十次甚至上百次的各個模板位的圖片,才能完成一件正品的存檔工作。
綜合難點
※ 分辨率激增帶來的內存壓力
- 內存佔用暴增,單個從6.4M左右躍升到48.8M,增長7.6倍。
- 超高清分辨率需要更多的GPU內存和計算資源。
- 高分辨率與流暢體驗難以兼顧。
※ PWA內存分配限制
- 多層內存限制:拿iPhoneX為例,從3GB系統內存到300~500MB的實際可用內存,層層削減。若除去一些基礎的開銷(比如js引擎、WebKit開銷等開銷)後則更少,更容易達到系統限制的內存紅線,進而產生卡頓、失敗、被強制回收,降頻等情況。
- Webkit嚴格限制,瀏覽器對單個標籤頁內存使用有硬性上限。
※ 視頻流與圖像處理的資源競爭
- 視頻流和圖像處理同時佔用大量內存。
- GPU資源競爭,視頻解碼和Canvas繪製爭奪GPU資源。
※ 移動設備性能差異化
- 硬件碎片化:不同設備內存和性能差異巨大。
- 兼容性問題:需要為不同性能的設備提供不同策略,保障任務的進行。
※ 瀏覽器內存管理的不可控性
- 內存分配不可預測:系統會根據整機的內存壓力動態調整分配。自身web應用無法參與調控。
- GC時機不可控:垃圾回收可能在關鍵時刻觸發,影響作業流程。
- 進程終止風險:極端情況下瀏覽器自己會終止頁面,reload。
二、實現方案
整體技術實現
我們整體的技術實現基於 WebRTC 和 HTML5 Canvas 以及Web worker。
※ WebRTC
navigator.mediaDevices.getUserMedia 是 WebRTC API 的一部分,用於訪問用户設備的攝像頭和麥克風。它可以請求用户授權以獲取視頻或音頻流,並將實時媒體流綁定到 <video> 標籤上。
※ HTML5 的 video
用於顯示攝像頭捕捉到的實時視頻流。
※ Canvas
通過 canvas 元素,可以從 <video> 標籤的當前幀中捕獲圖像(拍照),並將其轉換為圖片格式(如 PNG 或 JPEG)。
※ WebWorker
通過允許在後台線程中運行腳本,避免阻塞主線程(UI 線程),從而解決複雜計算導致的頁面卡頓問題。
整體架構
整體方案簡要
- 在pwa頁面中開啓攝像頭
- 獲取視頻流: CameraStreamManager管理相機流,提供video元素
- 等待幀穩定
- 通過視頻流,創建ImageBitmap
- Worker處理: 將ImageBitmap傳遞給Worker進行處理
- 策略選擇,根據設備情況做策略選擇
- Worker中使用chunked、chunkedConvert等策略分塊處理大圖像
- 生成結果: 返回ObjectUrl(內存中的文件或二進制數據)
- 更新UI: 更新預覽和上傳隊列
- 資源回收
- 結束或下一步
其中的實現細節內更多偏向於資源的精細化管理、回收釋放、重試機制、容錯機制等。
最核心的準則是:性能優先,穩定保底。
產品使用流程
操作流程裏的核心是針對此前在電腦和手機中反覆切換拍攝、錄入、上傳等複雜的操作,轉變為在手持設備中一站式完成補圖、拍攝、上傳和通知等。
操作時序
三、性能優化
性能優化思維導圖
為什麼需要性能優化
- 頁面卡頓
- 低端機型無法順暢拍照
- 圖片轉化慢,手機熱..
- 高頻出現圖像轉化失敗
- 突破內存峯值,系統回收內存降頻等,程序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 和 vedioif ((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策略流程示意
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策略流程示意
// 分塊轉化 最終返回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相機應用內存佔用
優化後的效果
※ 內存優化結果
- 單張圖片處理峯值減少33% - 從123.2MB降至82.2MB。
- 單張圖片持久佔用減少61% - 從76.7MB降至30.2MB。
- PWA應用整體內存優化16-26% - 根據圖片數量不同。
- 內存壓力等級顯著降低,如從3-4張開始有明顯警示壓力,到操作快速秒級拍攝速率時才出現(實際操作過程中大概10-15秒一張,因需要擺放和根據模版與提醒進行拍攝)。
※ 用户體驗
- 最終在高清圖片的繪製作業流程中,由原來的3張圖告警到一次性可以拍攝50張圖的情況,大大降低了失敗風險。提升了作業的流暢度。
- 用户體驗改善,消除UI阻塞,響應時間減半。
四、業務結果
通過幾輪的策略優化,整個pwa應用已可以相對順暢、高效的繪製原相機標準的正品圖,已完全達到鑑別師高清圖的要求,同時不會有操作流的中斷。
- 目前日均的拍攝件數提升 330%,達成預期目標。
- 將每件的人力投入成本降低 41.18%。
- 目前通過PWA項目快速搭建了圖庫項目,Q2拍照數據佔比72.5%,預期後面比例會逐步升高,圖庫流轉效率提高到了20%,超出業務預期。
五、規劃和展望
在技術的實現上,許多時候要去做用空間換時間或用時間換空間的策略方案,本質上還是根據我們當前的業務場景和訴求,追求當下收益。有些時候可能不止侷限在實現上,需要從實際需求出發,不應該只停留在工具的層面,而深入到業務裏剖析挖掘其潛在的業務價值,做更深遠的思考,從工具思維轉向價值發現與傳遞的方向上。
未來我們還會思考:
- 前置對設備的綜合能力評估,更精細化的拆分低、中、高端設備和適配策略,收集更多的實際處理時間和內存峯值、CPU 性能指標等,用於不斷優化策略選擇算法。
- 根據類目做區分(比如鞋服、奢品),這些在鑑別的時候圖片質量有不同的品質要求的分類。後續可能會進行更加具有定製化屬性的方案,針對鑑別打標,針對當前業務中圖片拍攝重試場景下的AI圖像識別,針對重複拍攝場景做優化,進一步提高效率。
- 針對目前 10 到 15 秒的拍攝時間,能進一步壓縮問題,思考更加智能的拍攝能力。根據設備的真實情況,或基於色温分析的光線評估,提高圖像質量和降低重複率。基於正品特徵進行構圖優化,在設備上做實時拍攝指導,不只以單一模板和示例進行人工檢查,而是進一步標準化,降低人力參與度。
- 針對於商研側業務和前置拍照流程,將拍照H5的方案也納入採賣商品入庫流程,同時支持鑑別師對於圖庫的驗收,加快圖庫的驗收入庫效率,縮短庫內的拍照數據積壓週期。
往期回顧
1.匯金資損防控體系建設及實踐 | 得物技術
2.一致性框架:供應鏈分佈式事務問題解決方案|得物技術
3.Redis 是單線程模型?|得物技術
4.得物社區活動:組件化的演進與實踐
5.從CPU冒煙到絲滑體驗:算法SRE性能優化實戰全揭秘|得物技術
文 / 維克
關注得物技術,每週更新技術乾貨
要是覺得文章對你有幫助的話,歡迎評論轉發點贊~
未經得物技術許可嚴禁轉載,否則依法追究法律責任。