`Laravel`版`小丑路人社区`改版中,与`Hyperf版小丑路人社区`数据互动,此版本改版中……尚未彻底完结!

Q:

阿里云ocr识别笔记

阿里云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:

  1. 上传解析的图片需要有表格线,且单元格没有合并的情况
  2. 当表头说明文字叫法不同,但是都有一一对应的字段,可以通过$data数组进行配置
  3. 当上传第一张图片(有表头)解析成功后,会存储表头到此用户表的table_headers字段,后续没有表头只有数据也会同步使用此表头进行解析
  4. 数据格式化可针对行数据是否有name字段来判断是否需要此条数据,并二次加工数据
  5. 印章水印可在阿里云移除,当前配置可自动识别水印去除处理

图片示例

image.png

image.png

特殊图片处理

  • 当图片没有竖线,只有横线的情况,没有办法解析到每列数据,可以通过取出所有字符串数据,进行正则匹配方式一一拿到每行数据,然后进行处理
  • 定制化处理针对指定图片,不能随意解析,其他非规则图片可参考此方式
/**
     * 特殊识别处理
     * @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;
    }

图片示例

image.png

特别技巧:

  • 在前端上传图片后可以进行多张图片数据通过指定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;
其他
订阅

评论记录


评论/回复