-
Notifications
You must be signed in to change notification settings - Fork 1
Description
最近在做 OCR 识别相关的工作,图片都是通过前端上传,遇到了几个问题。
- 通过代码创建 input 标签来实现图片选择&拍照功能,在 iOS 手机上拍照确定后没有任何反应;
- 在 OCR 识别过程中出现不少识别失败的情况。
为了解决以上两个问题,做了相关的探究。
iOS 设备拍照确定后没有反应
先看下唤起图片上传的代码:
const launchHtmlCamera = () => {
const inputElement = document.createElement('input');
inputElement.id = 'upload-image';
inputElement.type = 'file';
inputElement.accept = 'image/*';
// 允许多选
inputElement.multiple = true;
inputElement.style.display = 'none';
// 如果仅支持拍照,添加 capture 属性
// if ('capture' in inputElement) {
// inputElement.capture = 'camera';
// }
inputElement.addEventListener('change', handleImageSelect);
inputElement.click();
};代码比较简单,就是通过 js 创建 input 标签,设置系列属性,然后模拟点击事件触发图片上传。但是在测试中发现,iOS 手机拍照后没有触发回调函数,也就是 handleImageSelect 一直没有执行。
在 stackoverflow 上发现了解决办法,但是并没有找到原因。要在 iOS 手机上正常使用,必须得满足两个条件:
- input 标签必须被插入到 DOM 中;
- 必须使用 addEventListener 监听 change 事件,而不能使用 onchange。
所以只需要在模拟点击事件代码之前加上一行代码:
document.body.appendChild(inputElement); // 如果不插入 iOS 中事件监听器不会被触发由于是采用的 react hooks 开发模式,还遇到另一个和闭包有关的问题。input 被插入到 DOM 后,在其上绑定了 handleImageSelect 函数,而这个函数里会设置新增的图片数据,因为在后续更新中访问这个变量一直是初次渲染时的值,导致添加一张图片后再次添加只发生替换而不新增图片。
为了解决这个办法,最佳做法是每次渲染时重新绑定事件回调函数或者是通过 useRef 保存对变量的引用。
第一种重新绑定事件回调函数,做法比较简单粗暴,就是在回调函数的最后将 input 标签从 DOM 中移除。
event.target.value = null;
const inputElement = document.getElementById('upload-image');
inputElement.removeEventListener('change', handleImageSelect);
document.body.removeChild(inputElement);第二种方式,就是监听变量,然后在其变化后使用 ref 保存,在事件回调函数里使用 ref 来访问最新的变量。
const currentImgList = useRef([]);
useEffect(() => {
currentImgList.current = [...imgList];
}, [imgList]);
const handleImageSelect = () => {
const list = [...currentImgList.current, ...res.filter(Boolean)];
setImgList(list);
}另外需要调整的就是在每次唤起图片上传函数时,需要判断 input 标签是否已经插入到 DOM 中。
let inputElement = document.getElementById('upload-image');
if (inputElement) {
inputElement.click();
return;
}iOS 拍照 OCR 无法识别
在使用 iOS 手机拍照的时候,图片预览是竖直方向,但是 OCR 系统反馈识别失败。这是因为 OCR 在识别的过程中,会把图片默认当作旋转角度为 0 的图片来识别,我们预览看到的图片,其实是系统帮我们旋转了角度的,这个可以通过 EXIF 信息能看到。
比如这张图片,我们在设备上看是正常的,但是系统帮我们逆时针旋转了 90 度,图片真实角度是顺时针旋转了 90 度的。而 OCR 系统识别过程中会按照区域进行识别,这就导致识别失败。比如说要求的图片右上角必须是一个二维码,但是由于真实的图片被旋转了,二维码出现在右下角了,OCR 框定的区域就找不到二维码了。
手机拍照图片上传时经常遇到图片旋转的问题,需要设置 EXIF 中的 Orientation 参数。Orientation 存储的是手机的拍摄方向,获取 Orientation 信息可以借助一个开源工具库 exif-js。关于图像处理 Orientation 的相关信息,可以从这篇文章详细阅读:笔记:使用 JavaScript 读取 JPEG 文件 EXIF 信息中的 Orientation 值。
比如我们要把真实的图片绘制出来,可以通过如下代码:
/**
* 根据文件扩展信息还原图片数据
* @param {*} file 文件
* @returns blob
*/
export const getExif = (file) => {
return new Promise((resolve) => {
// 创建一个Image对象
const img = new Image();
// 读取图片数据
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result;
// 图片加载完成后进行旋转处理
img.onload = function () {
// 获取方向信息
EXIF.getData(img, () => {
const orientation = EXIF.getTag(img, 'Orientation');
console.log('orientation', orientation);
// 创建一个Canvas
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// 根据方向信息旋转图片
switch (orientation) {
case 3:
canvas.width = img.height;
canvas.height = img.width;
context.rotate(Math.PI);
context.drawImage(img, -img.width, -img.height);
break;
case 6:
canvas.width = img.height;
canvas.height = img.width;
context.rotate(Math.PI / 2);
context.drawImage(img, 0, -img.height);
break;
case 8:
canvas.width = img.height;
canvas.height = img.width;
context.rotate(-Math.PI / 2);
context.drawImage(img, -img.width, 0);
break;
default:
canvas.width = img.width;
canvas.height = img.height;
context.drawImage(img, 0, 0);
}
// 将Canvas中的图像转为 blob
canvas.toBlob(resolve);
});
};
};
reader.readAsDataURL(file);
});
};当然,我们给 OCR 的图片,得把 Orientation 信息设置为 1,无论怎么翻转角度,我都当作是旋转角度为 0 来处理,这里通过 canvas 的方式重新绘制图片:
/**
* 将图片的 orientation 重置为 1
* @param {*} file 文件
* @returns blob
*/
export const formatOrientation = (file) => {
return new Promise((resolve) => {
// 创建一个Image对象
const img = new Image();
// 读取图片数据
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target.result;
// 图片加载完成后进行旋转处理
img.onload = function () {
// 创建一个Canvas
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// 设置Canvas的宽度和高度,确保它足够容纳整个图片
canvas.width = img.width;
canvas.height = img.height;
// 不再根据方向信息旋转图片,直接绘制原始图片
context.drawImage(img, 0, 0);
// 将Canvas中的图像转为 blob
canvas.toBlob(resolve, file?.type, 0.5);
};
};
reader.readAsDataURL(file);
});
};这样处理过后,OCR 识别失败的例子就减少了很多,如果 OCR 还不能识别,就需要 OCR 系统把图片翻转 180 度再进行识别。
