2023年3月

基于PaddleOCR的多视角集装箱箱号检测识别

一、项目介绍

集装箱号是指装运出口货物集装箱的箱号,填写托运单时必填此项。标准箱号构成基本概念:采用ISO6346(1995)标准

标准集装箱箱号由11位编码组成,如:
CBHU 123456 7
,包括三个部分:

  1. 第一部分由4位英文字母组成。前三位代码主要说明箱主、经营人,第四位代码说明集装箱的类型。列如CBHU 开头的标准集装箱是表明箱主和经营人为中远集运
  2. 第二部分由6位数字组成。是箱体注册码,用于一个集装箱箱体持有的唯一标识
  3. 第三部分为校验码由前4位字母和6位数字经过校验规则运算得到,用于识别在校验时是否发生错误。即第11位编号

本教程基于
PaddleOCR
进行集装箱箱号检测识别任务,使用少量数据分别训练检测、识别模型,最后将他们串联在一起实现集装箱箱号检测识别的任务

效果展示:

二、环境准备

首先点击左侧套件选择PaddleOCR 进行下载。

三、数据集介绍

本教程所使用的
集装箱箱号数据集
,该数据包含3003张分辨率为1920×1080的集装箱图像

1、PaddleOCR检测模型训练标注规则如下,中间用"\t"分隔:

" 图像文件名                    json.dumps编码的图像标注信息"
ch4_test_images/img_61.jpg    [{"transcription": "MASA", "points": [[310, 104], [416, 141], [418, 216], [312, 179]]}, {...}]

其中json.dumps编码前的图像标注信息是包含多个字典的list,字典中的 points 表示文本框的四个点的坐标(x, y),从左上角的点开始顺时针排列。 transcription 表示当前文本框的文字,当其内容为“###”时,表示该文本框无效,在训练时会跳过。

2、PaddleOCR识别模型训练标注规则如下,中间用"\t"分隔:

" 图像文件名                 图像标注信息 "

train_data/rec/train/word_001.jpg   简单可依赖
train_data/rec/train/word_002.jpg   用科技让复杂的世界更简单

四、数据整理

4.1 检测模型所需数据准备

将数据集3000张图片按2:1划分成训练集和验证集,运行以下代码

from tqdm import tqdm
finename = "all_label.txt"
f = open(finename)
lines = f.readlines() 
t = open('det_train_label.txt','w')
v = open('det_eval_label.txt','w')
count = 0
for line in tqdm(lines):
    if count < 2000:
        t.writelines(line)
        count += 1
    else:
        v.writelines(line)
f.close()
t.close()
v.close()

4.2 识别模型所需数据准备

我们根据检测部分的注释,裁剪数据集尽可能只包含文字部分图片作为识别的数据,运行以下代码

from PIL import Image
import json
from tqdm import tqdm
import os
import numpy as np
import cv2
import math

from PIL import Image, ImageDraw

class Rotate(object):

    def __init__(self, image: Image.Image, coordinate):
        self.image = image.convert('RGB')
        self.coordinate = coordinate
        self.xy = [tuple(self.coordinate[k]) for k in ['left_top', 'right_top', 'right_bottom', 'left_bottom']]
        self._mask = None
        self.image.putalpha(self.mask)

    @property
    def mask(self):
        if not self._mask:
            mask = Image.new('L', self.image.size, 0)
            draw = ImageDraw.Draw(mask, 'L')
            draw.polygon(self.xy, fill=255)
            self._mask = mask
        return self._mask

    def run(self):
        image = self.rotation_angle()
        box = image.getbbox()
        return image.crop(box)

    def rotation_angle(self):
        x1, y1 = self.xy[0]
        x2, y2 = self.xy[1]
        angle = self.angle([x1, y1, x2, y2], [0, 0, 10, 0]) * -1
        return self.image.rotate(angle, expand=True)

    def angle(self, v1, v2):
        dx1 = v1[2] - v1[0]
        dy1 = v1[3] - v1[1]
        dx2 = v2[2] - v2[0]
        dy2 = v2[3] - v2[1]
        angle1 = math.atan2(dy1, dx1)
        angle1 = int(angle1 * 180 / math.pi)
        angle2 = math.atan2(dy2, dx2)
        angle2 = int(angle2 * 180 / math.pi)
        if angle1 * angle2 >= 0:
            included_angle = abs(angle1 - angle2)
        else:
            included_angle = abs(angle1) + abs(angle2)
            if included_angle > 180:
                included_angle = 360 - included_angle
        return included_angle



def image_cut_save(path, bbox, save_path):
    """
    :param path: 图片路径
    :param left: 区块左上角位置的像素点离图片左边界的距离
    :param upper:区块左上角位置的像素点离图片上边界的距离
    :param right:区块右下角位置的像素点离图片左边界的距离
    :param lower:区块右下角位置的像素点离图片上边界的距离
    """
    img_width  = 1920
    img_height = 1080
    img = Image.open(path)
    coordinate = {'left_top': bbox[0], 'right_top': bbox[1], 'right_bottom': bbox[2], 'left_bottom': bbox[3]}
    rotate = Rotate(img, coordinate)
    
    left, upper = bbox[0]
    right, lower = bbox[2]
    if lower-upper > right-left:
        rotate.run().convert('RGB').transpose(Image.ROTATE_90).save(save_path)
    else:
        rotate.run().convert('RGB').save(save_path)
    return True

#读取检测标注制作识别数据集
files = ["det_train_label.txt","det_eval_label.txt"]
filetypes =["train","eval"]
for index,filename in enumerate(files):
    f = open(filename)
    l = open('rec_'+filetypes[index]+'_label.txt','w')
    if index == 0:
        data_dir = "RecTrainData"
    else:
        data_dir = "RecEvalData"
    if not os.path.exists(data_dir):
        os.mkdir(data_dir)
    lines = f.readlines() 
    for line in tqdm(lines):
        image_name = line.split("\t")[0].split("/")[-1]
        annos = json.loads(line.split("\t")[-1])
        img_path = os.path.join("/home/aistudio/input0/images",image_name)
        for i,anno in enumerate(annos):
            data_path = os.path.join(data_dir,str(i)+"_"+image_name)
            if image_cut_save(img_path,anno["points"],data_path):
                l.writelines(str(i)+"_"+image_name+"\t"+anno["transcription"]+"\n")
    l.close()
    f.close()

五、实验

由于数据集比较少,为了模型更好和更快的收敛,这里选用 PaddleOCR 中的 PP-OCRv3 模型进行检测和识别。PP-OCRv3在PP-OCRv2的基础上,中文场景端到端Hmean指标相比于PP-OCRv2提升5%, 英文数字模型端到端效果提升11%。详细优化细节请参考PP-OCRv3技术报告。

5.1 检测模型

5.1.1 检测模型配置

PaddleOCR提供了许多检测模型,在路径
PaddleOCR-2.6.0/configs/det
下可找到模型及其配置文件。如我们选用模型
ch_PP-OCRv3_det_student.yml
,其配置文件路径在:
PaddleOCR-2.6.0/configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml
。使用前需对其进行必要的设置,如训练参数、数据集路径等。将部分关键配置展示如下:

#关键训练参数
use_gpu: true #是否使用显卡
epoch_num: 1200 #训练epoch个数
save_model_dir: ./output/ch_PP-OCR_V3_det/ #模型保存路径
save_epoch_step: 200 #每训练200epoch,保存一次模型
eval_batch_step: [0, 100] #训练每迭代100次,进行一次验证
pretrained_model: ./PaddleOCR-release
2.5/pretrain_models/ch_PP-OCR_V3_det/best_accuracy.pdparams #预训练模型路径
#训练集路径设置
Train:
  dataset:
    name: SimpleDataSet
    data_dir: /input0/images #图片文件夹路径
    label_file_list:
      - ./det_train_label.txt #标签路径

文件直接放在更目录里,自行替换即可 /home/aistudio/ch_PP-OCRv3_det_student.yml

5.1.2 模型微调

在notebook中运行如下命令对模型进行微调,其中 -c 传入的为配置好的模型文件路径

%run /home/aistudio/PaddleOCR-2.6.0/tools/train.py \
    -c /home/aistudio/PaddleOCR-2.6.0/configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml

使用默认超参数,模型
ch_PP-OCRv3_det_student
在训练集上训练100个epoch后,模型在验证集上的hmean达到:90.96%,此后再无明显增长

[2023/03/21 15:57:09] ppocr INFO: best metric, hmean: 0.909551282051282, precision: 0.8977836411609498,
recall: 0.921611681990265, fps: 20.347745459258228, best_epoch: 100

5.2 识别模型

5.2.1 识别模型配置

PaddleOCR也提供了许多识别模型,在路径
PaddleOCR-2.6.0/configs/rec
下可找到模型及其配置文件。如我们选用模型ch_PP-OCRv3_rec_distillation,其配置文件路径在:
PaddleOCR-2.6.0/configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml
。使用前需对其进行必要的设置,如训练参数、数据集路径等。将部分关键配置展示如下:

#关键训练参数
use_gpu: true #是否使用显卡
epoch_num: 1200 #训练epoch个数
save_model_dir: ./output/rec_ppocr_v3_distillation #模型保存路径
save_epoch_step: 200 #每训练200epoch,保存一次模型
eval_batch_step: [0, 100] #训练每迭代100次,进行一次验证
pretrained_model: ./PaddleOCR-release-2.5/pretrain_models/PPOCRv3/best_accuracy.pdparams #预训练模型路径
#训练集路径设置
Train:
  dataset:
    name: SimpleDataSet
    data_dir: ./RecTrainData/ #图片文件夹路径
    label_file_list:
      - ./rec_train_label.txt #标签路径
      
      Eval:
  dataset:
    name: SimpleDataSet
    data_dir: ./RecEvalData/
    label_file_list:
    - ./rec_eval_label.txt

文件直接放在更目录里,自行替换即可 /home/aistudio/ch_PP-OCRv3_rec_distillation.yml

5.2.2 模型微调

在notebook中运行如下命令对模型进行微调,其中 -c 传入的为配置好的模型文件路径

%run /home/aistudio/PaddleOCR-2.6.0/tools/train.py \
    -c /home/aistudio/PaddleOCR-2.6.0/configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml

使用默认超参数,模型
ch_PP-OCRv3_rec_distillation
在训练集上训练50个epoch后,模型在验证集上的精度达到:91.11%,此后再无明显增长

[2023/03/21 20:04:28] ppocr INFO: best metric, acc: 0.9110600272522444, norm_edit_dis: 0.9427426548965615,
Teacher_acc: 0.9040291998159589, Teacher_norm_edit_dis: 0.9405629345025616, fps: 246.029195787707, best_epoch: 50

六、结果展示

6.1 检测模型推理

在notebook中运行如下命令使用微调过的模型检测测试图片中的文字,其中:

  • Global.infer_img
    为图片路径或图片文件夹路径,
  • Global.pretrained_model
    为微调过的模型,
  • Global.save_res_path
    为推理结果保存路径
%run /home/aistudio/PaddleOCR-2.6.0/tools/infer_det.py \
    -c /home/aistudio/PaddleOCR-2.6.0/configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml \
    -o Global.infer_img="/home/aistudio/input0/images" Global.pretrained_model="./output/ch_PP-OCR_V3_det/best_accuracy" Global.save_res_path="./output/det_infer_res/predicts.txt"

6.2 识别模型推理

在notebook中运行如下命令使用微调过的模型检测测试图片中的文字,其中:

  • Global.infer_img
    为图片路径或图片文件夹路径,
  • Global.pretrained_model
    为微调过的模型,
  • Global.save_res_path
    为推理结果保存路径
%run /home/aistudio/PaddleOCR-2.6.0/tools/infer_rec.py \
    -c /home/aistudio/PaddleOCR-2.6.0/configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml \
    -o Global.infer_img="./RecEvalData/" Global.pretrained_model="./output/rec_ppocr_v3_distillation/best_accuracy" Global.save_res_path="./output/rec_infer_res/predicts.txt"
    

部分结果展示:

./RecEvalData/0_1-122720001-OCR-AS-B01.jpg	{"Student": {"label": "EITU1786393", "score": 0.9737951766666623682}, "Teacher": {"label": "EITU1786393", "score": 0.9882291666666404663}}
./RecEvalData/0_1-122720001-OCR-LB-C02.jpg	{"Student": {"label": "EITU1786393", "score": 0.9709678888320923}, "Teacher": {"label": "EITU1786393", "score": 0.9925146698951721}}
./RecEvalData/0_1-122720001-OCR-RF-D01.jpg	{"Student": {"label": "EITU1786393", "score": 0.9985160231590271}, "Teacher": {"label": "EITU1786393", "score": 0.9967824816703796}}
./RecEvalData/0_1-122728001-OCR-RF-D01.jpg	{"Student": {"label": "DFSU4119250", "score": 0.9663339257240295}, "Teacher": {"label": "DFSU4119250", "score": 0.9600133299827576}}
./RecEvalData/0_1-122740001-OCR-AH-A01.jpg	{"Student": {"label": "MRKU4306585", "score": 0.9916775226593018}, "Teacher": {"label": "MRKU4306585", "score": 0.9929805994033813}}
./RecEvalData/0_1-122749001-OCR-AH-A01.jpg	{"Student": {"label": "FCGU4996010", "score": 0.9195910096168518}, "Teacher": {"label": "FCGU4996010", "score": 0.9424482583999634}}
./RecEvalData/0_1-122830001-OCR-AS-B01.jpg	{"Student": {"label": "MEDU4024195", "score": 0.9861812591552734}, "Teacher": {"label": "MEDU4024195", "score": 0.9718942642211914}}
./RecEvalData/0_1-122843001-OCR-RF-D01.jpg	{"Student": {"label": "TGU864295", "score": 0.9045045375823975}, "Teacher": {"label": "TGU864395", "score": 0.8963061571121216}}

6.3 检测识别模型串联推理

6.3.1 模型转换

在串联推理前首先需要将训练保存的模型转换成推理模型,分别执行如下检测命令即可。其中,
-c
传入要转换模型的配置文件路径,
-o Global.pretrained_model
为要被转换的模型文件,
Global.save_inference_dir
为转换得到推理模型的储存路径

# 检测模型转换
%run /home/aistudio/PaddleOCR-2.6.0/tools/export_model.py \
-c /home/aistudio/PaddleOCR-2.6.0/configs/det/ch_PP-OCRv3/ch_PP-OCRv3_det_student.yml  \
-o Global.pretrained_model="./output/ch_PP-OCR_V3_det/best_accuracy" Global.save_inference_dir="./output/det_inference/"


# 识别模型转换
%run /home/aistudio/PaddleOCR-2.6.0/tools/export_model.py \
-c /home/aistudio/PaddleOCR-2.6.0/configs/rec/PP-OCRv3/ch_PP-OCRv3_rec_distillation.yml \
-o Global.pretrained_model="./output/rec_ppocr_v3_distillation/best_accuracy" Global.save_inference_dir="./output/rec_inference/"

6.3.2 模型串联推理

转换完毕后,PaddleOCR提供了检测和识别模型的串联工具,可以将训练好的任一检测模型和任一识别模型串联成两阶段的文本识别系统。输入图像经过文本检测、检测框矫正、文本识别、得分过滤四个主要阶段输出文本位置和识别结果。执行代码如下,其中
image_dir
为单张图像或者图像集合的路径,
det_model_dir
为检测inference模型的路径,
rec_model_dir
为识别inference模型的路径。
可视化识别结果默认保存到 ./inference_results 文件夹里面

%run /home/aistudio/PaddleOCR-2.6.0/tools/infer/predict_system.py \
--image_dir="OCRTest" \
--det_model_dir="./output/det_inference/" \
--rec_model_dir="./output/rec_inference/Student/"

结果展示:

1-122700001-OCR-LF-C01.jpg	[{"transcription": "TTEMU3108252", "points": [[1226, 133], [1322, 133], [1322, 883], [1226, 883]]}, {"transcription": "22G1", "points": [[1417, 214], [1479, 216], [1471, 463], [1409, 461]]}]
1-122720001-OCR-AH-A01.jpg	[{"transcription": "ITU1786393", "points": [[225, 206], [918, 215], [917, 318], [224, 309]]}]
1-122720001-OCR-AS-B01.jpg	[{"transcription": "EITU1786393", "points": [[919, 283], [1389, 296], [1387, 372], [917, 359]]}, {"transcription": "45G1", "points": [[1104, 399], [1288, 399], [1288, 486], [1104, 486]]}]
1-122720001-OCR-LB-C02.jpg	[{"transcription": "TU1", "points": [[226, 10], [515, 6], [516, 97], [227, 102]]}, {"transcription": "45G1", "points": [[489, 114], [784, 104], [787, 204], [492, 213]]}]
1-122720001-OCR-RF-D01.jpg	[{"transcription": "EITU1786393", "points": [[216, 38], [941, 27], [942, 125], [217, 135]]}, {"transcription": "45G1", "points": [[452, 137], [719, 133], [720, 218], [453, 223]]}]

以1-122720001-OCR-AS-B01.jpg测试样例进行展示:

多视角识别结果为:
EITU1786393

七.总结

本项目做了基于PaddleOCR的多视角集装箱箱号检测识别,使用少量数据分别训练检测、识别模型,最后将他们串联在一起实现集装箱箱号检测识别的任务。其中集装箱号是指装运出口货物集装箱的箱号,填写托运单时必填此项。标准箱号构成基本概念:采用ISO6346(1995)标准。

从结果上看,基于PaddleOCR的多视角集装箱箱号检测识别取得了不错的效果,但也存在一些改进地方。

  • 数据集样本量和丰富度不够
  • 训练不充分,因为算力和时间限制,本人就简单训练了100 epochs左右停止了。
  • 模型参数调优(目前默认参数)
    等等

源项目链接:
https://aistudio.baidu.com/aistudio/projectdetail/5766320?contributionType=1

欢迎关注fork 三连

模块化的基本概念

什么是模块化?

模块化是解决一个复杂问题时,自顶向下逐层把系统划分为若干个模块的过程,编程中,就是遵守一定规则,把一个
大文件
拆成
独立并相互依赖
的多个小模块。

模块化规范

使用什么样的语法格式引用模块和向外暴露成员

CommonJS规范

Node.js 遵循了
CommonJS
的模块化规范。其中:

  • 每个模块内部,
    module
    变量代表当前模块
  • 导入其它模块使用
    require()
    方法
  • 模块对外共享成员使用
    module.exports
    对象

ES6 模块化规范

是浏览器端与服务器端
通用
的模块化开发规范。它的出现极大的降低了前端开发者的模块化学习成本,开发者不需再额外学习 AMD、CMD 或 Commonjs 等模块化规范。
ES6 模块化规范中定义:

  • 每个js文件都是一个独立的模块
  • 导入其它模块成员使用
    import
    关键字
  • 向外共享模块成员使用
    export
    关键字

在Node.js中设置ES6模块化规范:

  1. 安装
    v14.15.1
    或更高版本的node.js

  2. package.js
    的根节点添加
    "type:""module"
    节点

Node.js中的模块化

Node.js中模块的分类

  • 内置模块:官方提供的fs、path、http等
  • 自定义模块:用户创建的每个.js文件,都是自定义模块
  • 第三方模块:由第三方开发的,需要下载的模块

加载模块

文章以
CommonJS
规范为例

//内置模块
const http = require('http')
//自定义模块
const custom = require('./custom.js')//后缀名可以省略
//第三方模块
const moment = require('moment')

注意
:当使用
require
加载时,会执行被加载模块的代码

模块作用域

在自定义模块中定义的变量和方法等成员,只能在当前模块被访问,
防止全局变量污染的问题

//username.js
const username='张三'

function sayHello(){
    console.log("大家好,我是"+username)
}

//test.js
const custom=require('./username')
console.log(custom)//输出:{} 空对象

向外共享成员

module对象

每个.js自定义模块中都有一个
module
对象,它里面
存储了和当前模块有关的信息

module.exports对象

在自定义模块中,可以使用它将模块内的成员共享出去,当使用
require()
方法导入时,得到的就是
module.exports
所指的对象

//username.js
module.exports.username = '张三'
module.exports.sayHello = function () {
    console.log("大家好,我是" + username)
}

注意:

  • module.exports
    默认指向空对象
  • 使用
    require()
    方法导入的结果,以
    module.exports
    指向的对象为准

exports对象

为了简化,Node提供了exports对象。默认情况下,
exports

module.exports
指向同一个对象。

exports.username= '张三'
module.exports={
    gender:'男',
    age:22
}
//{gender:'男',age:22}
module.exports.username= '张三'
exports={
    gender:'男',
    age:22
}
//{username:'张三'}
  • 使用
    require()
    方法导入的结果,永远以
    module.exports
    指向的对象为准
  • 防止混乱,不要在同一个模块中同时使用
    exports

    module.exports

npm和包

在Node.js中
第三方模块
叫做

,来自于第三方个人或团队,免费且开源。

包基于内置模块封装出来,提供了更高级、更方便的API,极大的提高了开发效率。

包的下载


  • https://www.npmis.com/
    网站上搜索自己所需要的包
  • 从https://registry.npmis.org/ 服务器上下载自己需要的包

使用npm包管理工具(Node Package Manager)

npm install 包的完整名称
npm i 包的完整名称 -g//全局安装
npm install moment@2.22.2

npm uninstall 包的完整名称

node_modules
文件夹用来存放所有已安装到项目中的包

package-lock.json
配置文件用来记录
node_modules
目录下每一个包的下载信息

包的版本号:“点分十进制”

第一位数字:大版本

第二位数字:功能版本

第三位数字:Bug修复版本

(只要前面的版本号增长,后面归零)

包管理配置文件

npm规定,项目根目录中,必须提供一个
package.json
的包管理配置文件

npm init -y//快速新建package.json

dependencies节点

专门记录使用npm insatll命令安装了哪些包

npm install
//执行该命令,npm会读取package.json中的dependencies节点,一次性下载所有的包

devDependencies节点

如果某些包只在开发中用到,建议安装在devDependencies节点

npm i 包的完整名称 -D
npm i 包的完整名称 --save-dev

包下载慢的问题

切换npm的下包镜像源

npm config get registry //查看当前镜像源
npm config set registry=xxxxxxxxxx//切换镜像源

nrm工具

npm i nrm -g
nrm ls//查看所有镜像源
nrm use taobao//切换镜像源

包的分类

项目包和全局包、开发依赖包和核心依赖包

规范的包结构

  1. 包必须以单独的目录出现
  2. 包的顶级目录必须包含package.json这个包配置文件
  3. package.json
    必须包含
    name

    version

    main
    这三个属性,分别代表包的名字、版本号、包的入口

一、概述

iniparser是针对INI文件的解析器。ini文件则是一些系统或者软件的配置文件。iniparser库的API可以对ini文件(配置文件)进行解析、设置、删除等操作。

常见的 ini 读写开源库有:minIni、inifile、iniparser

二、使用

下载

Github:
https://github.com/ndevilla/iniparser

方式一

  1. 编译
    下载后进入文件根目录,使用
    make
    命令编译,编译完成后会生成 libiniparser.a 和 libiniparser.so.1 文件

  2. 测试
    iniparser 提供了测试程序,进入 example 目录,使用
    make
    命令编译,完成后会生成 iniexample 执行文件

  3. 测试结果

  4. 注意事项
    使用链接文件时,可以参考 example 目录下的 Makefile 文件

方式二

此方法使用比较简单,直接将 src 目录下的文件拷贝到工程中即可,使用方式和自己编写的 .c 和 .h 文件一样

三、API函数

iniparser.h

/* 获取dictionary对象的section个数 */
int iniparser_getnsec(dictionary *d);

/* 获取dictionary对象的第n个section的名字 */
char * iniparser_getsecname(dictionary *d, int  n);

/* 保存dictionary对象到file */
void iniparser_dump_ini(dictionary * d, FILE * f);  

/* 保存dictionary对象一个section到file */
void iniparser_dumpsection_ini(dictionary * d, char * s, FILE * f); 

/* 打印 ini 文件内容 */
void iniparser_dump(dictionary * d, FILE * f);  

/* 获取dictionary对象某个section下的key个数 */
int iniparser_getsecnkeys(dictionary * d, char * s); 

/* 获取dictionary对象某个section下所有的key */
char ** iniparser_getseckeys(dictionary * d, char * s); 

/* 返回dictionary对象的section:key对应的字串值 */
char * iniparser_getstring(dictionary * d, const char * key, char * def);   

/* 返回idictionary对象的section:key对应的整形值 */
int iniparser_getint(dictionary * d, const char * key, int notfound); 

/* 返回dictionary对象的section:key对应的双浮点值 */
double iniparser_getdouble(dictionary * d, const char * key, double notfound);  

/* 返回dictionary对象的section:key对应的布尔值 */
int iniparser_getboolean(dictionary * d, const char * key, int notfound); 

/* 设置dictionary对象的某个section:key的值 */
int iniparser_set(dictionary * ini, const char * entry, const char * val); 

/* 删除dictionary对象中某个section:key */
void iniparser_unset(dictionary * ini, const char * entry); 

/* 判断dictionary对象中是否存在某个section:key */
int iniparser_find_entry(dictionary * ini, const char * entry) ; 

/* 解析dictionary对象并返回(分配内存)dictionary对象 */
dictionary * iniparser_load(const char * ininame); 

/* 释放dictionary对象(内存) */
void iniparser_freedict(dictionary * d);

dictionary.h

/* 计算关键词的hash值  
unsigned dictionary_hash(const char * key); 

/* 创建dictionary对象 */
dictionary * dictionary_new(int size);  

/* 删除dictionary对象 */
void dictionary_del(dictionary * vd); 

/* 获取dictionary对象的key值 */
char * dictionary_get(dictionary * d, const char * key, char * def); 

/* 设置dictionary对象的key值 */
int dictionary_set(dictionary * vd, const char * key, const char * val); 

/* 删除dictionary对象的key值 */
void dictionary_unset(dictionary * d, const char * key); 

/* 保存dictionary对象 */
void dictionary_dump(dictionary * d, FILE * out);

四、演示

  1. test.ini 文件

    #
    # 测试文件
    #
    
    [Node]
    Test                           = 1234
    
  2. test.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    #include "iniparser.h"
    
    
    #define FILE_INI "test.ini"
    
    /**
     * @brief 读取 ini 文件的配置信息
     * 
     * @param read_buf 读取缓冲去
     * @param return 返回操作结果
     */
    int get_ini_info(int *read_buf)
    {
        dictionary  *ini;
    
        ini = iniparser_load(FILE_INI);
        if (ini==NULL) {
            fprintf(stderr, "cannot parse file: %s\n", FILE_INI);
            return -1;
        }
    
        /* 打印文件内容 */
        // iniparser_dump(ini, stderr);
    
        /* 读取压力等级的判断信息 */
        *read_buf = iniparser_getint(ini, "node:test", -1);
    
        iniparser_freedict(ini);
        return 0;
    }
    
    /**
     * @brief 写入 ini 文件的配置信息
     * 
     * @param write_buf 写入缓冲区
     * @param return 返回操作结果
     */
    int set_ini_info(const char *write_buf)
    {
        dictionary  *ini;
        FILE *fp = NULL;
    
        ini = iniparser_load(FILE_INI);
        if (ini==NULL) {
            fprintf(stderr, "cannot parse file: %s\n", FILE_INI);
            return -1;
        }
    
        /* 写入压力等级的判断信息 */
        iniparser_set(ini, "node:test", write_buf);
    
        /* 将信息保存到文件中 */
        fp = fopen(FILE_INI, "w");
        if( fp == NULL ) {
            fprintf(stderr, "stone:fopen error!\n");
            return -1;
        }
        iniparser_dump_ini(ini, fp);
    
        fclose(fp);
        iniparser_freedict(ini);
        return 0;
    }
    
    int main (int argc, char **argv)
    {
    	int num = 0;
    	set_ini_info("1234");
    	get_ini_info(&num);
    	printf("date is: %d \n", num);
    }
    
    
  3. 文件目录

  4. 编译

gcc test.c dictionary.c iniparser.c -o test
  1. 测试效果

参考链接

minIni:
https://github.com/compuphase/minIni/tree/master/dev

inifile:
https://github.com/Winnerhust/inifile2

iniparser:
https://github.com/ndevilla/iniparser

Iniparser库详解:
https://blog.csdn.net/weixin_46245859/article/details/125860628

Iniparser库详解:
https://blog.csdn.net/weixin_46245859/article/details/125860628

背景:

全局异常捕获是我们开发当中不可获取的一个环节,顾名思义就是全局的捕获异常,并响应出去,今天就带着各位童鞋们在.Net 6.0当中如何做到全局异常捕获.

思路:

我们可以通过请求管道达到我们全局的一个场景,所以我们第一步就是在请求管道中自定义一个全局异常捕获的中间件,并通过try catch来实现,是不是特别简单,好话不多说下面就带着各位步入正题!!!

实现:

1.在根目录中创建Middlewares文件夹,并创建ExceptionHandlingMiddleware类(自定义中间件)

2.创建一个捕获异常后需要将错误信息响应出去的容器,哈哈哈大白话来说就是一个类,我这就暂命名为ActionTResult

3.实现ExceptionHandlingMiddleware中代码逻辑,不卖关子,直接上代码!!!

1   /// <summary>
2     ///全局异常捕获中间件3     /// </summary>
4     public classExceptionHandlingMiddleware5 {6         private readonly RequestDelegate _next;  //用来处理上下文请求
7         private readonly ILogger<ExceptionHandlingMiddleware>_logger;8 
9         public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware>logger)10 {11             _next =next;12             _logger =logger;13 }14 
15         /// <summary>
16         ///执行中间件17         /// </summary>
18         /// <param name="httpContext"></param>
19         /// <returns></returns>
20         public asyncTask InvokeAsync(HttpContext httpContext)21 {22             try
23 {24                 await _next(httpContext); //要么在中间件中处理,要么被传递到下一个中间件中去
25 }26             catch(Exception ex)27 {28                 await HandleExceptionAsync(httpContext, ex); //捕获异常了 在HandleExceptionAsync中处理
29 }30 }31 
32         /// <summary>
33         ///异步处理异常34         /// </summary>
35         /// <param name="context"></param>
36         /// <param name="exception"></param>
37         /// <returns></returns>
38         private asyncTask HandleExceptionAsync(HttpContext context, Exception exception)39 {40             context.Response.ContentType = "application/json";  //返回json 类型
41             var response =context.Response;42             var errorResponse = newActionTResult43 {44                 Succes =SuccessTypeEnum.Error45             };  //自定义的异常错误信息类型
46             switch(exception)47 {48                 caseApplicationException ex:49                     if (ex.Message.Contains("Invalid token"))50 {51                         response.StatusCode = (int)HttpStatusCode.Forbidden;52                         errorResponse.ErrorMsg =ex.Message;53                         break;54 }55                     response.StatusCode = (int)HttpStatusCode.BadRequest;56                     errorResponse.ErrorMsg =ex.Message;57                     break;58                 caseKeyNotFoundException ex:59                     response.StatusCode = (int)HttpStatusCode.NotFound;60                     errorResponse.ErrorMsg =ex.Message;61                     break;62                 default:63                     response.StatusCode = (int)HttpStatusCode.InternalServerError;64                     errorResponse.ErrorMsg = "Internal Server errors. Check Logs!";65                     break;66 }67 _logger.LogError(exception.Message);68             var result =JsonSerializer.Serialize(errorResponse);69             awaitcontext.Response.WriteAsync(result);70 }71     }

4.在请求管道中直接使用即可

1 app.UseMiddleware<ExceptionHandlingMiddleware>();

效果:

到这里其实我们已经实现了一个全局异常捕获了,我们一起来看看效果吧!

在这里本章节就结束了,希望对在坐的各位童鞋有所帮助,当然如果有发现不足或者不对的地方也欢迎探讨,及时指正!!!