阿里云ocr识别笔记
安装扩展包
laravel使用
composer require godruoyi/laravel-ocr
原始方式
# >= 8.1
composer require "godruoyi/ocr:^3.0"
# >= 7.2
composer require "godruoyi/ocr:^2.1"
配置文件(laravel举例)
<?php
/*
* This file is part of the godruoyi/ocr.
*
* (c) Godruoyi <gmail@godruoyi.com>
*
* This source file is subject to the MIT license that is bundled.
*/
return [
/*
|--------------------------------------------------------------------------
| Default client
|--------------------------------------------------------------------------
|
| 指定一个默认的 client 名称,其值需要在下列的 drivers 数组中配置。
|
*/
'default' => env('OCR_DEFAULT_CLIENT', 'aliyun'),
/*
|--------------------------------------------------------------------------
| Client 配置
|--------------------------------------------------------------------------
|
| Client 配置信息,包括基本密钥等;注意目前 aliyun 暂只支持 appcode 方式。
|
*/
'drivers' => [
'aliyun' => [
'appcode' => env('OCR_CODE', ''),
'secret_id' => '',
'secret_key' => '',
],
'baidu' => [
'access_key' => '',
'secret_key' => '',
],
'tencent' => [
'secret_id' => '',
'secret_key' => '',
],
],
/*
|--------------------------------------------------------------------------
| Cache 配置
|--------------------------------------------------------------------------
|
| Baidu OCR 需要缓存来保存 AccessToken,默认我们使用 Symfony Cache 组件的 FilesystemAdapter
| 你可以在这里设置为使用 Laravel 的缓存组件。
|
*/
'laravel_cache' => true,
];
!> 其中在.env
中增加 OCR_CODE = xxxxxxxxxxxxxx
数据,这个key来源于阿里云,试用次数100次加1分钱500次机会,足够使用了。
表格识别
调用初始化
/**
* @param $filePath [图片地址]
* @throws BusinessException
*/
public function handle($filePath, $type = 1)
{
$options = [
'table' => true,
'noStamp' => true
];
$response = OCR::aliyun()->generalAdvanced($filePath, $options);
$res = $response->toArray();
if (isset($res['error_code'])) {
Log::channel('ocr')->error('识别图片:' . $filePath . ' 错误结果:', $res);
throw new BusinessException(CodeResponse::OCR_ERR);
}
if ($type == 15) {
$data = $res['content'];
Log::channel('ocr')->info('识别图片:' . $filePath . '识别结果:' . $data);
return $this->format15($data);
} else {
$data = $res['prism_tablesInfo'][0];
Log::channel('ocr')->info('识别图片:' . $filePath . ' 识别结果:', $data);
return $this->format($data);
}
}
通用解析表格数据
/**
* 通用识别处理
* @throws BusinessException
*/
public function format($data)
{
$table = [];
$res = [];
$items = $data['cellInfos'];
foreach ($items as $item) {
if ($item['xsc'] == $item['xec'] && $item['ysc'] == $item['yec']) {
$table[$item['ysc']][$item['xec']] = $item['word'];
}
}
$data = [
'sn' => ['序号', 'ID'],
'code' => ['编码', '商品编码', '零件编码', '零件号', '配件编码', '零件编号'],
'name' => ['零件名称', '名称', '零件中文名', '配件名称'],
'num' => ['出库数', '数量'],
'quality' => ['产地', '配件品质', '品质'],
'unit' => ['单位'],
'price' => ['售价', '单价'],
'total_price' => ['金额', '价格', '销售价', '配件价格'],
];
$init = 0;//表格数据(除了表头)第一条row索引
if (!isset($table[0])) {
throw new BusinessException(CodeResponse::OCR_ERR);
} else {
$arr = array_values($table[0]);
$intersection = array_intersect($arr, $data['name']);
$user = Auth::guard('supplier')->user();
if (!empty($intersection)) {
$header = $table[0];
// 遍历表格数据(除了表头)
$init = 1;
$keys = array_keys($table[1]);
$user->table_headers = json_encode($header);
$user->save();
} else {
//获取存储的表头
$header = json_decode($user->table_headers);
if (!$header) {
Log::channel('ocr')->error('表头缺失');
throw new BusinessException(CodeResponse::OCR_ERR);
}
// 遍历表格数据(除了表头)
$keys = array_keys($table[0]);
}
}
$initData = array_fill_keys(array_keys($data), ''); // 初始化空的行数据
for ($i = $init; $i < count($table); $i++) {
$row = $table[$i];
$rowData = [];
// 遍历当前行的每个格子
foreach ($keys as $key) {
if (isset($header[$key])) {
$cellData = $row[$key];
// 检查当前表头文字是否在 $row 数组中的值中
foreach ($data as $k => $v) {
if (in_array($header[$key], $v)) {
$rowData[$k] = $cellData;
break;
}
}
}
}
$rowData = array_merge($initData, $rowData);
if ($rowData['name']) {
//数据格式化
$rowData['sn'] = empty($rowData['sn']) ? count($res) + 1 : $rowData['sn'];
$rowData["code"] = str_replace(' ', '', $rowData["code"]);
$rowData["total_price"] = preg_replace("/[^0-9]+/", "", $rowData["total_price"]);
$res[] = $rowData;
}
}
return $res;
}
Tips:
- 上传解析的图片需要有表格线,且单元格没有合并的情况
- 当表头说明文字叫法不同,但是都有一一对应的字段,可以通过
$data
数组进行配置- 当上传第一张图片(有表头)解析成功后,会存储表头到此用户表的
table_headers
字段,后续没有表头只有数据也会同步使用此表头进行解析- 数据格式化可针对行数据是否有
name
字段来判断是否需要此条数据,并二次加工数据- 印章水印可在阿里云移除,当前配置可自动识别水印去除处理
图片示例
特殊图片处理
- 当图片没有竖线,只有横线的情况,没有办法解析到每列数据,可以通过取出所有字符串数据,进行正则匹配方式一一拿到每行数据,然后进行处理
- 定制化处理针对指定图片,不能随意解析,其他非规则图片可参考此方式
/**
* 特殊识别处理
* @Author sugar
* @date 2023/8/9
* @param $data
* @return array
*/
public function format15($data)
{
$res = [];
preg_match('/仓位.+?人民币/', $data, $matches);
// 获取中间的字符串
$data = trim($matches[0], '仓位 人民币');
// $pattern = '/ (\d{3}) (\S+) (.*?) (\S+) (\S+) (\S+) (\S+)/';
$pattern = '/\d{3}\s.+?(?=\d{3}\s|$)/';
preg_match_all($pattern, $data, $matches);
for ($i = 0; $i <= count($matches[0]) - 1; $i++) {
$m = $matches[0][$i];
$m_arr = explode(' ', $m);
$m_arr = array_values(array_filter($m_arr));
if (count($m_arr) == 7 || count($m_arr) == 8) {
$n['sn'] = $m_arr[0];
$n['num'] = $m_arr[count($m_arr) - 3];
$n['quality'] = $m_arr[count($m_arr) - 4];
$n['price'] = $m_arr[count($m_arr) - 2];
$n['total_price'] = $this->splitString($m_arr[count($m_arr) - 1]);
if (count($m_arr) == 8) {
$str = $m_arr[1] . $m_arr[2];
$n['code'] = substr($str, 0, 12);
$n['name'] = substr($str, 12);
} else {
$n['code'] = substr($m_arr[1], 0, 12);
$n['name'] = substr($m_arr[1], 12);
}
$res[] = $n;
}
}
return $res;
}
public function splitString($string)
{
// 使用正则表达式匹配小数点后两位的内容
preg_match("/\d+\.\d{2}/", $string, $matches);
if (count($matches) > 0) {
// 提取匹配到的内容作为前面的字符串
$result = $matches[0];
} else {
// 如果没有匹配到,则返回原始字符串
$result = $string;
}
return $result;
}
图片示例
特别技巧:
- 在前端上传图片后可以进行多张图片数据通过指定key进行合并数据
//更新表格数据
let oldArr = dataSource.value;
let appendArr = res.data
appendArr.forEach((item) => {
const index = oldArr.findIndex((oldItem) => oldItem.sn.toString() === item.sn.toString());
if (index !== -1) {
oldArr[index] = item;
} else {
oldArr.push(item);
}
});
dataSource.value = oldArr;
评论/回复