深度学习印章检测(自动生成数据集+yolov5)

目录

1 概述

1.1 简介

1.2 演示

2 软件安装

3 数据集

3.1 生成随机字符

3.2 生成印章图片

3.3 生成word文件

3.4 Word转PDF文件

3.5 PDF转图像

4 labelme标记

5 yolov5训练


1 概述

本文将从代码层面的角度来剖析印章数据集如何自动生成,以及如何进行训练与测试,如果希望获取直接运行的项目,请参考:

如果大家希望替换数据集,只需要替换掉本文的图片即可,其他训练和预处理操作均一致

1.1 简介

由于未找到印章目标检测的公开数据集及印章数据的敏感性,因为本文的印章数据均为测试使用的自动生成,本任务首先基于随机中文字符生成包含印章的word文档,然后将word转换为pdf,将pdf转换为图片,并使用labelme对图片进行标注,最后使用yolov5对印章进行检测

注意:由于是随机中文字符生成,因此文档内容都是乱编的,本实验的主要目的在于检测出印章的位置,因此文档的内容对此没有影响,所以本文使用随机字符生成数据,另外,如果希望本模型获得更好的泛化性能,需要为其添加自己希望检测的自定义数据集

1.2 演示

这是我们最后希望达到的检测效果:

本实验的主要目的在于检测出印章的位置,因此文档的内容对此没有影响,所以本文使用随机字符生成数据

2 软件安装

3 数据集

本文首先利用Pillow库生成印章图片,并使用docx将随机字符和印章写入word文件,最后分别使用comtypes和pdf2image将word转换为pdf和png。

3.1 生成随机字符

根据自定义的中文字符随机组合来生成公司名称、标题、子标题、内容以及落款日期

  • 这个子函数用于生成指定长度的随机文本。函数接收一个字符列表、最小长度和最大长度作为参数,然后在字符列表中随机选择字符,生成指定长度的字符串
def generate_random_text(char_list, min_length, max_length):
    """
    随机生成对应的内容
    :param char_list:
    :param min_length:
    :param max_length:
    """
    length = random.randint(min_length, max_length)
    return ''.join(random.choice(char_list) for _ in range(length))
  • 这个子函数用于生成随机的段落列表。首先,随机生成段落的数量(3到7段),然后为每个段落生成3到5句随机句子,并用句号连接每个句子,最后返回段落列表。
def generate_content():
    """
    生成随机段落列表
    """
    paragraphs = []
    num_paragraphs = random.randint(3, 7)  # 生成 3 到 7 段落
    for _ in range(num_paragraphs):
        num_sentences = random.randint(3, 5)  # 每段包含 3 到 5 句
        sentences = [generate_random_text(chinese_char, 8, 15) for _ in range(num_sentences)]
        paragraph = '。'.join(sentences) + '。'  # 用句号分隔句子并在末尾加句号
        paragraphs.append(paragraph)
    return paragraphs  # 用换行分隔段落
  • 定义一个包含常用汉字的列表,用于生成随机文本时选择字符
chinese_char = ['的', '一', '是', '在', '不', '了', '有', '和', '人', '这', '中', '大', '为', '上', '个', '国',
                '我', '以', '要', '他', '时', '来', '用', '们', '生', '到', '作', '地', '于', '出', '就', '分',
                '对', '成', '会', '可', '主', '发', '年', '动', '同', '工', '也', '能', '下', '过', '子', '说',
                '产', '种', '面', '而', '方', '后', '多', '定', '行', '学', '法', '所', '民', '得', '经', '十',
                '三', '之', '进', '着', '等', '部', '度', '家', '电', '力', '里', '如', '水', '化', '高', '自',
                '二', '理', '起', '小', '物', '现', '实', '加', '量', '都', '两', '体', '制', '机', '当', '使',
                '点', '从', '业', '本', '去', '把', '性', '好', '应', '开', '它', '合', '还', '因', '由', '其',
                '些', '然', '前', '外', '天', '政', '四', '日', '那', '社', '义', '事', '平', '形', '相', '全',
                '表', '间', '样', '与', '关', '各', '重新']
  • 定义了落款日期的范围,分别为1990年1月1日和2020年12月31日
start_date = datetime(1990, 1, 1)
end_date = datetime(2020, 12, 31)
  • 在主函数中,首先调用 generate_random_text() 生成公司名称、标题和子标题。然后调用 generate_content() 生成内容段落。最后生成落款日期并格式化输出
name = generate_random_text(chinese_char, 2, 8) + "有限公司"
title = generate_random_text(chinese_char, 5, 10)
subtitle = generate_random_text(chinese_char, 10, 20)
content = generate_content()
inscribe = name + "\n" + random_datetime(start_date, end_date).strftime('%Y年%m月%d日')
  • 完整代码:
def generate_info_random_by_char():
    """
    随机生成公司名称, 标题, 子标题, 内容, 落款日期
    """

    def generate_random_text(char_list, min_length, max_length):
        """
        随机生成对应的内容
        :param char_list:
        :param min_length:
        :param max_length:
        """
        length = random.randint(min_length, max_length)
        return ''.join(random.choice(char_list) for _ in range(length))

    def generate_content():
        """
        生成随机段落列表
        """
        paragraphs = []
        num_paragraphs = random.randint(3, 7)  # 生成 3 到 7 段落
        for _ in range(num_paragraphs):
            num_sentences = random.randint(3, 5)  # 每段包含 3 到 5 句
            sentences = [generate_random_text(chinese_char, 8, 15) for _ in range(num_sentences)]
            paragraph = '。'.join(sentences) + '。'  # 用句号分隔句子并在末尾加句号
            paragraphs.append(paragraph)
        return paragraphs  # 用换行分隔段落

chinese_char = ['的', '一', '是', '在', '不', '了', '有', '和', '人', '这', '中', '大', '为', '上', '个', '国',
                '我', '以', '要', '他', '时', '来', '用', '们', '生', '到', '作', '地', '于', '出', '就', '分',
                '对', '成', '会', '可', '主', '发', '年', '动', '同', '工', '也', '能', '下', '过', '子', '说',
                '产', '种', '面', '而', '方', '后', '多', '定', '行', '学', '法', '所', '民', '得', '经', '十',
                '三', '之', '进', '着', '等', '部', '度', '家', '电', '力', '里', '如', '水', '化', '高', '自',
                '二', '理', '起', '小', '物', '现', '实', '加', '量', '都', '两', '体', '制', '机', '当', '使',
                '点', '从', '业', '本', '去', '把', '性', '好', '应', '开', '它', '合', '还', '因', '由', '其',
                '些', '然', '前', '外', '天', '政', '四', '日', '那', '社', '义', '事', '平', '形', '相', '全',
                '表', '间', '样', '与', '关', '各', '重新']

    start_date = datetime(1990, 1, 1)
    end_date = datetime(2020, 12, 31)
    name = generate_random_text(chinese_char, 2, 8) + "有限公司"
    title = generate_random_text(chinese_char, 5, 10)
    subtitle = generate_random_text(chinese_char, 10, 20)
    content = generate_content()
    inscribe = name + "\n" + random_datetime(start_date, end_date).strftime('%Y年%m月%d日')

    return name, title, subtitle, content, inscribe

3.2 生成印章图片

  • 确定参数,即可以修改的变量:
:param save_stamp_path: 将要保存的印章路径
:param center_text: 印章中间的文字
:param circle_text: 印章周围的问题
:param width: 印章的宽度
:param height: 印章的高度
:param background_color: 印章的背景颜色
:param border_color: 印章的外边框颜色
:param fill_color: 印章的填充颜色
:param font_size: 设置印章内部文字大小
:parma angle_gap: 字体之间的间距
  • 首先建立空白画面
# 创建一个白色背景的图片
image = Image.new('RGB', (width, height), background_color)
draw = ImageDraw.Draw(image)
  • 绘制印章的圆形外边框
# 绘制圆形印章的外边框
draw.ellipse((10, 10, width - 10, height - 10), outline=border_color, width=5)
  • 设置印章内部的字体大小
# 设置印章内部文字的字体和大小
font = ImageFont.truetype(font_file, font_size)
  • 在印章的中间添加文字
# 在印章中间添加文字
text_bbox = draw.textbbox((0, 0), center_text, font=font)
text_width, text_height = text_bbox[2] - text_bbox[0], text_bbox[3] - text_bbox[1]
text_x = (width - text_width) / 2
text_y = (height - text_height) / 2
draw.text((text_x, text_y), center_text, fill=fill_color, font=font)
  • 在印章圆圈周围绘制弯曲的文字:
# 绘制弯曲的文字
circle_radius = 110
circle_center = (width // 2, height // 2)
start_angle = - 90 - (len(circle_text) - 1) / 2 * angle_gap
for i, char in enumerate(circle_text):
    angle = np.radians(start_angle + i * angle_gap)
    x = circle_center[0] + circle_radius * np.cos(angle)
    y = circle_center[1] + circle_radius * np.sin(angle)
    char_image = Image.new('RGBA', (font_size * 2, font_size * 2), (0, 0, 0, 0))
    image.paste(char_image, (int(x - font_size), int(y - font_size)), char_image)
  • 至此,将上述代码组装起来,为其设置印章中的内容,可以得到:

3.3 生成word文件

本部分主要用于生成包含标题、子标题、内容、落款和印章的Word文档

  • 本部分所需要的参数:
def generate_report_with_stamp(stamp_path, title, sub_title, content, inscribe, save_word_path):
    """
    生成word文档, 其中落款和印章在一起
    :param stamp_path: 印章的png路径
    :param title: 文档的标题
    :param sub_title: 文档的子标题
    :param content: 文档的内容
    :param inscribe: 文档的落款
    :param save_word_path: 文档保存的路径
    :return:
    """
  • 新建一个空白文档
doc = Document()
  • 添加一个段落并设置标题的格式,包括字体大小、加粗、颜色和字体
# 设置标题
title_paragraph = doc.add_paragraph()
title_run = title_paragraph.add_run(title)
title_run.font.size = Pt(24)
title_run.bold = True
title_run.font.color.rgb = RGBColor(255, 0, 0)
title_run.font.name = '宋体'
title_run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
title_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
  • 设置子标题的格式,包括字体大小和字体
# 设置子标题
sub_title_paragraph = doc.add_paragraph()
sub_title_run = sub_title_paragraph.add_run(sub_title)
sub_title_run.font.size = Pt(16)
sub_title_run.font.name = '宋体'
sub_title_run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
sub_title_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
  • 通过遍历内容列表,逐段落添加内容,并设置每个段落的格式,包括首行缩进、行间距和段后间距
# 设置内容
for tmp_content in content:
    content_paragraph = doc.add_paragraph(tmp_content)
    content_paragraph.paragraph_format.first_line_indent = Pt(28)
    content_paragraph.paragraph_format.line_spacing = Pt(20)
    content_paragraph.paragraph_format.space_after = Pt(20)
    content_run = content_paragraph.runs[0]
    content_run.font.size = Pt(12)
    content_run.font.name = '宋体'
    content_run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
  • 添加落款并设置其格式,使其右对齐
# 添加落款
inscribe_paragraph = doc.add_paragraph()
inscribe_run = inscribe_paragraph.add_run(inscribe)
inscribe_run.font.size = Pt(12)
inscribe_run.font.name = '宋体'
inscribe_run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体')
inscribe_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
  • 计算印章在文档中的大致位置,并将其插入到文档中。我们假设每行大约有33个字,并估算印章所在的行数
# 添加印章图片
line_number = sum([len(_) // 33 + 1 for _ in content]) + 5  # 根据字数估计印章的大致位置
p = doc.paragraphs[0]
add_float_picture(p, stamp_path, width=Cm(3.0), pos_x=Cm(16), pos_y=Cm(line_number))
  • 将文档保存到指定路径
# 保存文档
doc.save(save_word_path)
  • 结果如下图所示:

3.4 Word转PDF文件

本部分主要使用了comtypes库与Microsoft Word进行交互

  • 使用comtypes.client.CreateObject创建一个Word应用程序对象,并将其设置为不可见
# 初始化Word应用程序
word = comtypes.client.CreateObject('Word.Application')
word.Visible = False
  • 使用word.Documents.Open方法打开指定路径的Word文档
# 打开Word文档
doc = word.Documents.Open(docx_path)
  • 在进行转换操作之前,我们需要确保输出文件夹存在。如果不存在,则创建该文件夹
# 确保输出文件夹存在
if not os.path.exists(output_folder):
    os.makedirs(output_folder)
  • 遍历文档中的每一页,并将其导出为PDF
for i in range(1, doc.ComputeStatistics(2) + 1):  # 2 means wdStatisticPages
    # 将页面导出为PNG图片
    output_path = os.path.join(output_folder, os.path.basename(docx_path).replace('.docx', '.pdf'))
    doc.ExportAsFixedFormat(
        OutputFileName=output_path,
        ExportFormat=17,  # 17 means wdExportFormatPNG
        Item=0,  # 0 means wdExportDocumentContent
        CreateBookmarks=0,  # 0 means wdExportCreateNoBookmarks
        DocStructureTags=True
    )
  • 结果如下图所示:

3.5 PDF转图像

  • 直接使用下述代码即可将PDF转换为图像:
def convert_pdf_to_png(pdf_path, output_image_path):
    """
    Converts pdf file to png file
    :param pdf_path: The pdf file
    :param output_image_path: img output path
    :return:
    """
    pages = convert_from_path(pdf_path, dpi=300)
    for i, page in enumerate(pages):
        page.save(output_image_path, 'PNG')
  • 结果如下图所示:

4 labelme标记

labelme标记印章只有一个类别,所以我们将其命名为Stamp,另外需要打开后使用打开目录打开含有图片的文件夹,还需要在文件->更改输出路径中更改输出路径,也就是json保存的位置

使用教程参考这篇文章的第三章:http://t.csdnimg.cn/nCKzM

5 yolov5训练

  • 将json转换为yolo格式的txt,可以直接使用下述函数:
def convert_dataset(json_base_dir, output_label_dir):
    for name in os.listdir(json_base_dir):
        json_path = os.path.join(json_base_dir, name)
        with open(json_path, 'r') as f:
            data = json.load(f)
            imageHeight = data['imageHeight']
            imageWidth = data['imageWidth']
            yolo_annotations = []

            for shape in data['shapes']:
                points = shape['points']
                x_min = points[0][0]
                y_min = points[0][1]
                x_max = points[1][0]
                y_max = points[1][1]
                # 计算中心点、宽和高
                x_center = (x_min + x_max) / 2.0
                y_center = (y_min + y_max) / 2.0
                width = x_max - x_min
                height = y_max - y_min

                # 归一化
                x_center /= imageWidth
                y_center /= imageHeight
                width /= imageWidth
                height /= imageHeight

                # YOLO格式: class x_center y_center width height
                yolo_annotation = f"0 {x_center} {y_center} {width} {height}\n"
                yolo_annotations.append(yolo_annotation)

            # 保存到txt文件
            txt_path = os.path.join(output_label_dir, name.replace('.json', '.txt'))
            with open(txt_path, 'w') as f:
                f.writelines(yolo_annotations)
  • 下载yolov5项目并解压:https://github.com/ultralytics/yolov5

  • 修改其中的配置文件data/coco.yaml,把下面的内容粘贴到该配置文件中,注意修改自己数据集的实际路径:

path: ./dataset/stamp # dataset root dir
train: train/images # train images (relative to 'path') 118287 images
val: val/images # val images (relative to 'path') 5000 images

# Classes
names:
  0: stamp
conda create -n Stamp python=3.9.19
conda activate Stamp
conda install pytorch==1.11.0 torchvision==0.12.0 torchaudio==0.11.0 cudatoolkit=11.3 -c pytorch
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
  • 使用下述命令开启训练:

python train.py --data stamp.yaml --weights yolov5s.pt --img 640 --batch-size 4 --epochs 30

训练完成后会得到:

  • 利用训练好的模型进行检测:

python detect.py --weights runs/train/exp/weights/best.pt --source dataset/Stamp/images/6.png

得到的结果为:

YOLOv5是一种基于深度学习的目标检测算法,可以快速准确地检测图像中的不同目标。OpenCV是一个流行的计算机视觉库,提供了丰富的图像处理和分析功能。Java是一种常用的编程语言,可以用于开发各种应用程序。 如果想在Java上使用YOLOv5来进行印章识别,首先需要安装配置OpenCV和YOLOv5的相关环境。可以使用JavaCV库来方便地在Java中调用OpenCV的功能,同时也可以使用TensorFlow Java API来加载和使用YOLOv5的模型。 准备工作完成后,我们可以通过以下步骤进行印章识别: 1. 加载和初始化YOLOv5的模型。这包括加载模型文件、配置文件和权重文件,并根据需要进行参数设置。 2. 使用OpenCV读取待识别的图像,并进行预处理。预处理可以包括图像的缩放、归一化等操作,以便符合模型的输入要求。 3. 将预处理后的图像输入到YOLOv5模型中进行目标检测。模型将输出图像中检测到的印章的位置、类别和置信度等信息。 4. 根据模型的输出结果,可以根据置信度进行筛选和过滤,以得到最可信的印章检测结果。 5. 可以使用OpenCV的绘图功能,在原始图像上标注出检测到的印章位置和类别等信息,以便进行可视化展示或进一步的后续处理。 通过以上步骤,我们就可以实现在Java环境下使用YOLOv5和OpenCV进行印章识别。当然,具体实现的细节还需要根据实际情况进行调整和完善,比如模型的训练和优化等。此外,还需要考虑算法的性能和效果等因素,以便得到更好的印章识别结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值