iPhone 13 Pro上canvas绘制摄像头画面亮度偏亮如何解决
问题背景
需要调用手机摄像头采集画面,绘制到canvas元素后做后续保存操作。
异常表现:仅iPhone 13 Pro设备上canvas绘制输出的图像亮度偏高于正常水平,其余iOS设备、安卓设备均无该问题,同设备下<video>元素直接播放摄像头流的画面亮度正常。
核心实现代码如下:
let video = document.createElement('video'); let canvas = document.getElementById('canvas'); // 注意:原代码此处存在笔误,不应从未定义的container变量获取上下文 let ctx = container.getContext('2d'); navigator.mediaDevices.getUserMedia({ audio: false, video: { width: 1920, height: 1020, facingMode: 'environment' }}).then(stream => { window.stream = stream; video.srcObject = stream; const track = stream.getVideoTracks()[0]; video.setAttribute('playsinline', 'true'); video.play(); requestAnimationFrame(() => { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); }); })
故障原因
iPhone 13 Pro及后续搭载支持HDR拍摄摄像头的iOS设备,摄像头默认输出HDR格式视频帧。系统原生的<video>组件自带HDR到SDR的色调映射能力,所以直接播放时亮度显示正常;但部分iOS版本的2D Canvas上下文没有内置该色调映射逻辑,直接将HDR帧的高亮度值按SDR色域解析,就会出现画面整体过亮、高光发白的异常。
另外原代码存在两个逻辑缺陷:一是没有等视频加载完成第一帧就开始绘制,可能拿到未处理完成的异常帧;二是上下文获取对象写错,正常运行时会直接抛出JS错误。
修复方案
- 从源头上关闭视频轨的HDR输出
获取视频轨道后,强制约束输出非HDR格式帧,不支持该配置的设备会自动忽略约束,不会影响原有逻辑:const track = stream.getVideoTracks()[0]; try { await track.applyConstraints({ advanced: [{ hdrMode: 'none' }] }); } catch (err) {} - 修正上下文获取笔误,等待视频加载完成后再启动绘制循环
不要在调用play()后直接执行单次绘制,要等视频loadeddata事件触发,确认第一帧已经可渲染后再启动循环绘制,同时可以显式指定Canvas色彩空间为sRGB,避免不同设备的色彩解析偏差:// 修正笔误,从canvas元素获取2D上下文 let ctx = canvas.getContext('2d', { colorSpace: 'srgb' }); video.setAttribute('playsinline', 'true'); await video.play(); video.addEventListener('loadeddata', () => { const draw = () => { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); requestAnimationFrame(draw); } draw(); });
内容的提问来源于stack exchange,提问作者Alexandr Titovskiy
相关产品推荐
相关产品推荐

