Vue3 + Tesseract.js 实现图片文字识别(OCR)功能
大约 4 分钟
Vue3 + Tesseract.js 实现图片文字识别(OCR)功能
背景
最近在开发一个项目时,需要实现图片文字识别功能。图片中主要包含中文和数字,我想到使用 Tesseract.js 这个开源的 OCR 库来实现。这篇文章记录了从功能实现到性能优化的完整过程。
一、技术选型
Tesseract.js 是一个基于 Tesseract OCR 引擎的 JavaScript 库,可以在浏览器端直接运行 OCR 识别,无需后端服务。它的优势包括:
- 纯前端实现,无需服务器
- 支持 100+ 种语言
- 支持图片预处理提升识别准确率
- API 简单易用
二、初步实现功能
1、安装依赖
npm install tesseract.js2、基础代码实现
首先写了一个简单的 OCR 识别功能:
<script setup>
import { onMounted } from 'vue';
import Tesseract from 'tesseract.js';
onMounted(() => {
document.getElementById('upload').addEventListener('change', async (e) => {
const file = e.target.files[0];
const result = await Tesseract.recognize(
file,
'eng', // 语言包
{ logger: (m) => console.log(m) }
);
document.getElementById('result').innerHTML = result.data.text;
});
});
</script>
<template>
<input type="file" id="upload" accept="image/*" />
<div id="result"></div>
</template>3、遇到的问题与优化
问题 1:语言包不匹配
由于图片中主要是中文和数字,使用 'eng' 语言包识别效果很差。需要改为中文语言包:
// 改为中文简体 + 英文
'chi_sim+eng';问题 2:图片预处理
为了提升识别准确率,添加了图片预处理功能(调整尺寸 + 灰度化):
async function preprocessAndRecognize(imageFile) {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = async () => {
// 调整图像尺寸(保持宽高比)
const maxWidth = 800;
const scale = img.width > maxWidth ? maxWidth / img.width : 1;
canvas.width = img.width * scale;
canvas.height = img.height * scale;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 转换为灰度图
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = data[i + 1] = data[i + 2] = avg;
}
ctx.putImageData(imageData, 0, 0);
// 识别处理...
};
img.src = URL.createObjectURL(imageFile);
});
}三、发现性能瓶颈
功能实现后,在测试时发现了一个严重的问题:首次识别的加载时间非常长。
打开浏览器开发者工具,发现 Tesseract.js 在首次识别时会从 CDN 下载语言包:
https://cdn.jsdelivr.net/npm/@tesseract.js-data/chi_sim/4.0.0_best_int/chi_sim.traineddata.gz这个文件大小约 1.64MB,在网络不好的情况下,下载时间可能长达几十秒,用户体验极差。
四、解决方案:本地语言包
1、下载语言包文件
将需要的语言包下载到本地项目的 public 目录下:
# 创建存放目录
mkdir public/tesseract-lang
# 下载中文语言包
"https://cdn.jsdelivr.net/npm/@tesseract.js-data/chi_sim/4.0.0_best_int/chi_sim.traineddata.gz"
# 下载英文语言包
"https://cdn.jsdelivr.net/npm/@tesseract.js-data/eng/4.0.0_best_int/eng.traineddata.gz"2、配置本地路径
在代码中配置 Tesseract.js 使用本地语言包:
// 配置本地语言包路径
const langPath = `${window.location.origin}/tesseract-lang`;
// 在 recognize 方法中使用
const result = await Tesseract.recognize(
canvas.toDataURL('image/png'),
'chi_sim+eng',
{
logger: (m) => {
progress.value = `${m.status}: ${Math.round(m.progress * 100)}%`;
},
},
{
langPath: langPath, // 使用本地语言包路径
}
);3、项目结构
public/
tesseract-lang/
├── chi_sim.traineddata.gz (1.64 MB)
└── eng.traineddata.gz (2.82 MB)
src/
components/
└── HelloWorld.vue五、完整代码
最终的完整组件代码:
<script setup>
import { ref } from 'vue';
import Tesseract from 'tesseract.js';
const recognizedText = ref('');
const isLoading = ref(false);
const progress = ref('');
// 配置本地语言包路径
const langPath = `${window.location.origin}/tesseract-lang`;
async function preprocessAndRecognize(imageFile) {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = async () => {
try {
// 调整图像尺寸
const maxWidth = 800;
const scale = img.width > maxWidth ? maxWidth / img.width : 1;
canvas.width = img.width * scale;
canvas.height = img.height * scale;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 灰度化处理
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = data[i + 1] = data[i + 2] = avg;
}
ctx.putImageData(imageData, 0, 0);
// OCR 识别(使用本地语言包)
const result = await Tesseract.recognize(
canvas.toDataURL('image/png'),
'chi_sim+eng',
{
logger: (m) => {
progress.value = `${m.status}: ${Math.round(m.progress * 100)}%`;
},
},
{
langPath: langPath,
}
);
resolve(result.data.text);
} catch (error) {
reject(error);
}
};
img.onerror = (error) => reject(error);
img.src = URL.createObjectURL(imageFile);
});
}
async function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
if (!file.type.startsWith('image/')) {
alert('请上传图片文件');
return;
}
isLoading.value = true;
progress.value = '开始识别...';
recognizedText.value = '';
try {
const text = await preprocessAndRecognize(file);
recognizedText.value = text;
progress.value = '识别完成';
} catch (error) {
console.error('OCR识别失败:', error);
progress.value = '识别失败';
alert('图片识别失败,请重试');
} finally {
isLoading.value = false;
}
}
</script>
<template>
<section class="ocr-container">
<h2>图片文字识别 (OCR)</h2>
<div class="upload-section">
<input
type="file"
id="upload"
accept="image/*"
@change="handleFileUpload"
:disabled="isLoading" />
<label for="upload" class="upload-btn" :class="{ disabled: isLoading }">
{{ isLoading ? '识别中...' : '选择图片' }}
</label>
</div>
<div v-if="progress" class="progress">
{{ progress }}
</div>
<div v-if="recognizedText" class="result-section">
<h3>识别结果:</h3>
<pre class="result-text">{{ recognizedText }}</pre>
</div>
</section>
</template>
<style scoped>
.ocr-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.upload-section {
margin: 20px 0;
}
#upload {
display: none;
}
.upload-btn {
display: inline-block;
padding: 12px 24px;
background-color: #4caf50;
color: white;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.upload-btn:hover {
background-color: #45a049;
}
.upload-btn.disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.progress {
margin: 10px 0;
color: #666;
font-size: 14px;
}
.result-section {
margin-top: 20px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 4px;
}
.result-text {
white-space: pre-wrap;
word-wrap: break-word;
background-color: white;
padding: 15px;
border-radius: 4px;
border: 1px solid #ddd;
min-height: 100px;
font-size: 14px;
line-height: 1.6;
}
</style>总结
- 图片预处理的重要性 - 灰度化和尺寸调整可以显著提升识别准确率
- 性能优化的思路 - 对于大型依赖文件,考虑本地化加载可以大幅改善用户体验
- 语言包配置 - 根据实际需求选择合适的语言包组合
如果你的项目也需要 OCR 功能,希望这篇文章对你有所帮助!