前端实现图片OCR: 选中、复制图片里文本
为什么要复制图片文字?
有一个比较好用的产品,可能部分人使用过,TAPD ,一款敏捷项目管理工具,类似于禅道之类的在线应用,今天重点不是说项目管理工具, 今天主要是说说TAPD中有一项比较好用的功能,放大一张图片,可以直接复制图片中的文字,这个功能对于产品经理给出的需求,开发人员拿到里面的文案就非常好用。
现在企业微信、微信中已经具备这个功能了,但是对于Web来说,支持这个功能是更加复杂的,因为在Web中OCR天生就比较弱,node单线程,本身解释型语言性能也不算高。
既然TAPD已经有了,今天就来尝试动手写功能这个功能,所以有了此篇文章,最后我的demo我会放到github上,大家可以随意fork、测试。
运行图
本地OCR
本地使用Tessract.js识别文字,可以看到我们双击其中一处文字,上面输入框会显示我们选中的文字,可以用于测试对比识别效果。
也可以打开文字显示,看看整体的识别、位置还原程度
腾讯云OCR
通过腾讯云OCR服务识别效果是明显好于本地Tessract.js的,双击其中一处文字,查看上面文本框
同样也可以打开文字显示,看看整体的识别、位置还原程度
实现思路
实现的思路很简单,分为两个部分
- 图片识别文字
- 前端在图片上对应位置放置文字
对于本地ocr来说,只需要使用tesseract.js识别图片即可,对于腾讯云来说,需要使用腾讯云的OCR服务,然后把识别结果返回给前端,前端再把识别结果展示出来。
本地ocr不论是从识别速度、准确率都是不如氪金腾讯云OCR的,还是那句话:加钱,世界触手可得!
为了更好的体验,我把tessract.js运行在服务端中,没有直接在react前端项目使用这个包,所以不论是本地、还是腾讯ocr都是通过我的node服务端来调用的。 他们的前端完全一样,有兴趣可以在最后的github地址中查看。
本地ocr
本地实现的逻辑是,拿到图片后,先做灰度化和二值化处理,然后交给tesseract.js识别文字,最后把识别结果返回给前端。
腾讯云ocr
逻辑就很简单了,直接http服务传入图片,拿到返回的识别结果。
最终返回前端的结果
格式如下:
export interface ConvertedItem {
// 文本
text: string;
// 左上角坐标
start: [number, number];
// 右下角坐标
end: [number, number];
}
const result:ConvertedItem[] = [{...},{...}];
示例
{
"data": [
{
"text": "民 刁查看器 加 控制台 品 调试器 人 网络 《】样式编辑器 《Am 性能 » 乓 … X",
"start": [
22,
23
],
"end": [
1272,
55
]
},
{
"text": "W 。 过滤URL 1 十 Q © 禁用缓存 ”不节流? ”洽",
"start": [
26,
81
],
"end": [
1274,
113
]
},
...
{
"text": "炎 <b>ayaeaice< azas% doateivic/0>123123 23:20:27.627",
"start": [
24,
850
],
"end": [
1019,
875
]
}
]
}
前端拿到这些数据就可以把文字显示到对应的位置了。
核心代码实现
前端核心代码
前端就是依赖上面的数据结构,在把文字显示到对应的位置上:
<div className="image-container">
<img id="ocr-image" src={selectedImage!} alt="请先选择图片" />
{ocrResults.map((result, index) => {
const { text, start: leftTop, end: rightBottom } = result;
const height = rightBottom[1] - leftTop[1];
return (
<div key={index} className="ocr-text" style={{
color:`rgb(199, 10, 10${showOCRTxt?',1':',0'})`,
left: `${leftTop[0]}px`,
top: `${leftTop[1]}px`,
fontSize: `${height*0.7}px`,
width: `${rightBottom[0] - leftTop[0]}px`,
height: `${rightBottom[1] - leftTop[1]}px`
}}>
{text}
</div>
);
})}
</div>
服务端核心代码
本地Tessract.js实现OCR
本地OCR最好将图片先预处理一下,二值化和灰度化,让图片更便于识别。
async function multiThreadOCR(imagePath: string, numThreads: number): Promise<ConvertedItem[] | undefined> {
const scheduler = createScheduler();
const workers: Worker[] = [];
try {
for (let i = 0; i < numThreads; i++) {
const worker = await createWorker("chi_sim+eng", OEM.DEFAULT, {
// logger: (m) => console.log(m)
});
worker.setParameters({
preserve_interword_spaces: '1'
});
scheduler.addWorker(worker);
workers.push(worker);
}
const res = await scheduler.addJob('recognize', imagePath, {}, {
hocr: true,
blocks: true,
layoutBlocks: true,
});
const { data: { lines } } = res;
const result: ConvertedItem[] = [];
lines.forEach((line: OcrLine) => {
const { text, bbox } = line;
const { x0, y0, x1, y1 } = bbox;
result.push({
text: text.replace('\n', ''),
start: [x0, y0],
end: [x1, y1]
});
});
return result;
} catch (error) {
console.error('识别过程中出现错误:', error);
} finally {
for (const worker of workers) {
await worker.terminate();
}
}
}
app.post('/ocr', async (req: express.Request, res: express.Response) => {
// 二值化和灰度化
const grayscaleResult = await binarizeImage(await grayscaleImage(req.body.image));
if (grayscaleResult) {
multiThreadOCR(grayscaleResult, req.body.threadNum).then((data) => {
if (data) {
res.json({
data
});
} else {
res.status(500).send('识别过程中出现错误');
}
}).catch(err => {
console.error(err);
res.status(500).send('识别过程中出现错误');
});
} else {
res.status(500).send('图像预处理出错');
}
});
腾讯云OCR
腾讯云每个账号每个月都会给一些免费的OCR额度,如果用量不大的话,也是不需要自己花什么钱的。
const OcrClient = tencentcloud.ocr.v20181119.Client;
const clientConfig: ClientConfig = {
credential: {
secretId: process.env.secretId,
secretKey: process.env.secretKey,
},
region: "",
profile: {
httpProfile: {
endpoint: "ocr.tencentcloudapi.com",
},
},
};
app.post('/tencentocr', async (req: express.Request, res: express.Response) => {
const client = new OcrClient(clientConfig);
const params = {
"ImageBase64": req.body.image
};
client.GeneralAccurateOCR(params).then(
(data) => {
res.json({
// 把腾讯云返回的数据结构转换成和我们本地OCR一样的结构
data: convertData(data)
});
},
(err) => {
console.error("error", err);
res.status(500).send('识别过程中出现错误');
}
);
});
存在的问题
不管是本地识别还是腾讯云识别,因为不是个性化场景打磨的识别算法,所以可定制化程度几乎没有,这也就无可避免的出现识别效果可能不是很友好,存在如下比较麻烦的问题:
- 本地OCR文字识别率有些许问题,用的tesseract.js
- 文字在图片上为的位置还原有问题,主要是识别文字字号及中间空白的宽度困难
Demo可以优化的点
- 建立一个标准,对图片从大小、分辨率来划分界限,小图片可以使用本地OCR速度很快1秒多出结果,大一些的走腾讯云
- 优化文字在图片上的还原,尽可能保持重叠。
- 本地ocr可以研究tessract的words数据,本demo用的是lines数据,相对来说words数据粒度更细。也可以看看hocr的输出,是否还原效果会更好。
- 对于图片组件的封装,完全复刻Tapd的图片缩放、拖拽,并保持OCR数据。
项目地址
本文所用Demo项目源码地址:https://github.com/gtjyj/img-ocr-text-copy
欢迎大家查看源码,fork修改。