【机器学习】基于CNN-RNN模型的验证码图片识别

news2024/11/14 23:47:28

1. 引言

1.1. OCR技术研究的背景

1.1.1. OCR技术能够提升互联网体验

随着互联网应用的广泛普及,用户在日常操作中频繁遇到需要输入验证码的场景,无论是在登录、注册、支付还是其他敏感操作中,验证码都扮演着重要角色来确保安全性。然而,这种传统的手动输入方式不仅要求用户仔细辨认并逐一输入验证码中的字符,还常常因为字符的模糊、扭曲或难以辨认而引发输入错误,从而增加了用户的时间成本和操作难度,大大降低了用户体验。

为了改善这一状况,OCR验证码识别技术应运而生。这项技术通过利用先进的图像处理和计算机视觉算法,能够自动识别和解析验证码图像中的字符,并将其转换为可编辑的文本形式。用户只需将验证码图像输入到识别系统中,系统便能迅速而准确地完成识别,并将结果直接应用于相应的操作,无需用户手动输入。这不仅极大地减少了用户的操作时间,还降低了输入错误的可能性,显著提高了操作的便捷性和用户体验。

1.1.2. OCR技术能够保证系统安全

验证码作为一种重要的安全机制,在网络世界中起着至关重要的作用,其主要目的是为了防止恶意软件或自动化脚本的非法操作。无论是保护用户账户的安全,还是确保在线交易的顺利进行,验证码都发挥着不可替代的作用。然而,传统的手动输入验证码方式虽然在一定程度上提升了系统的安全性,但也可能因人为因素,如疲劳、粗心或误操作,而导致错误输入,从而给系统安全带来潜在风险。

为了克服这一挑战,OCR验证码识别技术应运而生。这项技术的引入不仅提升了用户体验,更在保障系统安全性方面展现出了巨大的潜力。通过采用先进的图像处理和机器学习算法,OCR验证码识别技术能够迅速、准确地识别并解析验证码图像中的字符,减少了人为因素导致的错误输入。同时,它还能够有效识别并抵御恶意软件的攻击,进一步提高了系统的安全防御能力。

在实际应用中,OCR验证码识别技术能够自动识别用户输入的验证码图像,并快速解析出其中的字符信息。这种自动化的处理方式不仅减少了用户的操作时间,还降低了因人为因素导致的错误率。此外,OCR验证码识别技术还能够对输入的验证码进行实时验证,确保用户输入的准确性,从而进一步增强了系统的安全性。

1.1.3. OCR技术具备良好的扩展性

随着技术的迅猛发展和网络安全要求的提高,验证码的形式和复杂度不断增加,以满足更加复杂多变的网络安全需求。这不仅包括传统的数字、字母组合验证码,还包括图形验证码、滑动验证码、拼图验证码等多种形式,甚至有的验证码还加入了动态效果和噪点干扰,以增大识别和破解的难度。

面对这种趋势,OCR验证码识别技术需要具备良好的适应性和可扩展性。首先,它必须能够应对各种类型和复杂度的验证码图像。无论是简单的数字字母组合,还是复杂的图形、滑动、拼图验证码,OCR技术都需要通过不断学习和优化算法,以准确识别和解析出其中的关键信息。这需要OCR技术具备强大的图像处理和识别能力,能够处理各种复杂的图像特征,如颜色、形状、纹理等。

其次,OCR验证码识别技术还需要具备可扩展性。随着技术的不断进步和新的验证码形式的出现,OCR技术需要能够迅速适应并集成新的识别算法和模型,以提供更加可靠和高效的解决方案。这要求OCR技术具备灵活的架构和可定制的功能,能够方便地添加新的算法和模型,并根据实际需求进行定制和优化。

为了实现良好的适应性和可扩展性,OCR验证码识别技术通常采用深度学习和计算机视觉技术作为核心。深度学习技术通过训练大量的数据样本,能够学习到验证码图像中的复杂特征和规律,并自动提取出关键信息。同时,深度学习模型具有强大的泛化能力,能够适应新的验证码形式和变化,确保识别的准确性和稳定性。

1.2. OCR模型简介

OCR模型,即光学字符识别(Optical Character Recognition)模型,是一种通过图像处理和模式识别技术,将图像中的文本信息转换为可编辑、可搜索的文本格式的技术。

1.2.1.OCR模型的工作原理

OCR模型的工作原理基于图象处理、模式识别和机器学习等技术:

  1. 图象预处理:对输入的图像进行预处理,包括图像的灰度化、二值化、去噪等操作。这些操作有助于简化后续处理,提高识别准确率。
  2. 文字定位:通过图像分析和边缘检测等算法,定位图像中的文字区域。文字定位是实现OCR识别的关键步骤之一。
  3. 字符分割:在文字定位的基础上,将文字区域进行字符分割,将每一个字符单独提取出来。字符分割的准确性直接影响后续的字符识别。
  4. 特征提取:对每一个字符进行特征提取,将字符的形状、纹理等特征转化为数值表示。常用的特征提取方法包括投影法、模板匹配法、形态学等。
  5. 字符识别:使用训练好的模型或算法对提取的字符特征进行识别。常用的字符识别方法包括模板匹配、统计模型、神经网络等。OCR可以使用单字符识别或者基于上下文的识别方法。
  6. 后处理:对识别结果进行后处理,包括错误校正、字典匹配、语法校验等。后处理可以提高识别结果的准确性和可信度。
  7. 输出结果:最后,OCR将识别的字符转化为可编辑、可搜索的文本输出。输出结果可以保存为文本文件、数据库记录等形式,方便后续的文本处理和分析。
1.2.2.OCR模型的优缺点

OCR模型具有以下优点:

  1. 实时性和准确性高:OCR模型可以直接从图像中提取文本,而无需复杂的预处理步骤。同时,由于其端到端的设计,使得模型可以同时处理多种语言和字体。
  2. 自动化程度高:OCR模型可以自动完成文本识别过程,减少人工干预,提高工作效率。

然而,OCR模型也存在一些不足之处:

  1. 对噪声和抖动较为敏感:如果输入图像中存在较多的噪声或抖动,可能会影响OCR模型的识别准确率。
  2. 对复杂排版处理效果不佳:对于复杂的排版结构,如表格、图文混排等,OCR模型的识别效果可能会受到一定影响。
1.2.3.OCR模型的应用场景

OCR模型在多个领域都有广泛的应用:

  1. 政府部门:OCR技术可以用于公安及交通部门的车牌、驾驶证、行驶证等证件识别,还有相关单位档案或笔录系统等,实现快速核查和比对,提高工作效率和准确度。
  2. 金融行业:OCR技术可以用于金融行业的远程开户、身份验证、银行卡识别、交易数据录入、文档管理等场景,提高工作效率和用户体验。

OCR模型是一种强大的文本识别技术,其工作原理基于图象处理、模式识别和机器学习等技术。通过不断的优化和改进,OCR模型在各个领域的应用前景将更加广阔。

1.3. OCR模型识别验证码方法

OCR模型识别验证码的步骤可以归纳如下:

  1. 图像获取
  • 打开需要登录或验证的网站,找到包含验证码的图像。
  • 将验证码图像截取或保存到本地,以便后续处理。
  1. 图像预处理
  • 对获取的验证码图像进行预处理,以消除噪声、改善图像质量。
  • 常见的预处理步骤包括灰度化、二值化、降噪等。
  1. 验证码定位
  • 使用图像分析技术,在预处理后的图像中定位验证码的具体位置。
  • 通过边缘检测、形态学操作等方法,将验证码从背景中分离出来。
  1. 字符分割
  • 将定位到的验证码图像进行字符分割,将每个字符单独提取出来。
  • 这通常涉及投影法、连通域分析等技术,以确保字符间的准确分离。
  1. 特征提取
  • 对每个分割出来的字符进行特征提取,将其形状、纹理等属性转化为数值表示。
  • 特征提取的方法取决于具体的OCR模型和算法,但通常包括HOG、SIFT、SURF等特征描述符。
  1. 字符识别
  • 使用训练好的OCR模型对提取的字符特征进行识别。
  • OCR模型可以是基于模板匹配、统计模型或深度学习的方法。
  • 深度学习模型(如卷积神经网络CNN)在复杂验证码识别中表现出色。
  1. 结果后处理
  • 对OCR模型输出的识别结果进行后处理,以提高准确性。
  • 后处理可能包括字典匹配、语法校验、去除非法字符等操作。
  1. 登录验证
  • 将识别出的验证码结果输入到相应的验证码输入框中。
  • 点击“登录”或“验证”按钮,等待系统验证结果。
  1. 结果反馈
  • 如果登录或验证成功,则表明OCR模型正确识别了验证码。
  • 如果失败,则可能需要重复上述步骤,或者调整OCR模型的参数和算法以提高识别准确率。

需要注意的是,OCR模型识别验证码的准确率和效率受到多种因素的影响,包括验证码的复杂度、图像质量、光照条件、字体类型等。因此,在实际应用中,需要根据具体情况对OCR模型进行选择和调整。

2.OCR模型识别验证码的过程

2.1 设置

# 导入 os 模块,用于设置环境变量  
import os  
  
# 设置 Keras 的后端为 TensorFlow  
os.environ["KERAS_BACKEND"] = "tensorflow"  
  
# 导入 NumPy 库,用于数值计算  
import numpy as np  
  
# 导入 Matplotlib 的 pyplot 模块,用于绘图  
import matplotlib.pyplot as plt  
  
# 导入 Pathlib 的 Path 类,用于处理文件路径  
from pathlib import Path  
  
# 导入 TensorFlow 库  
import tensorflow as tf  
  
# 通常情况下,我们不需要直接导入 keras 模块,因为当我们使用 TensorFlow 2.x 时,  
# TensorFlow 已经包含了 Keras API。但如果您想使用独立的 Keras 库,请确保已正确安装。  
# 但为了代码简洁性,这里我们直接使用 TensorFlow 中的 Keras API。  
  
# 导入 TensorFlow 中的 Keras 相关的 ops 和 layers 模块  
# 注意:在 TensorFlow 2.x 中,ops 模块的导入通常是不必要的,因为大部分操作都直接集成在 layers 和其他模块中。  
# 但如果您需要使用特定的底层操作,可以从 'tensorflow.python.ops' 导入。
from keras import ops  
from tensorflow.keras import layers  

2.2.下载数据集

curl -LO https://github.com/AakashKumarNain/CaptchaCracker/raw/master/captcha_images_v2.zip
7z x captcha_images_v2.zip -oC:\path\to\extract\ -y

2.3. 数据预处理

数据集包含了1040个验证码文件,这些文件均为PNG格式的图像。每个验证码图像都有一个对应的标签,这个标签实际上就是该图像的文件名(但不包括文件扩展名),它表示了验证码中显示的字符序列。

在训练模型之前,我们需要对标签进行预处理,以便模型能够理解并学习这些字符。具体来说,我们需要将标签中的每个字符映射到一个唯一的整数,从而形成一个整数序列。这是因为大多数机器学习模型,特别是深度学习模型,不能直接处理文本或字符数据,它们需要数值型输入。

为了实现这一映射,我们可以创建一个字典(或称为词汇表),其中键是字符,值是整数。这样,我们就可以将每个字符快速转换为对应的整数。同时,为了能够在预测阶段将模型输出的整数序列转换回原始的字符序列,我们还需要创建另一个字典,它的键是整数,值是字符。

在构建这两个字典时,我们还需要考虑一些额外的因素。例如,如果验证码中可能包含大小写字母、数字或特殊字符,我们需要确保这些字符在字典中都有对应的条目。此外,为了简化模型的学习过程,我们还可以考虑对字符进行编码,例如使用ASCII码或其他编码方案。

一旦我们构建了这两个字典,就可以开始处理数据集了。对于每个验证码图像,我们将其文件名(即标签)转换为整数序列,并将这个整数序列作为模型的目标输出。然后,我们可以使用这些处理过的数据来训练模型。

在预测阶段,模型将输出一个整数序列。我们使用之前创建的整数到字符的字典,将这个整数序列转换回原始的字符序列,从而得到预测的验证码。最后,我们可以将这个预测的验证码与实际的验证码进行比较,以评估模型的性能。

2.3.1 图片格栅标准化
from pathlib import Path
import os

# 设置数据目录的路径
data_dir = Path("./captcha_images_v2/")

# 获取目录下所有的图片文件,并按照字母顺序排序
images = sorted(list(map(str, list(data_dir.glob("*.png")))))

# 从图片路径中提取标签,即图片名称中'.png'之前的部分
labels = [img.split(os.path.sep)[-1].split(".png")[0] for img in images]

# 从所有标签中提取字符,并去重,然后排序
characters = set(char for label in labels for char in label)
characters = sorted(list(characters))

# 打印找到的图片数量
print("Number of images found: ", len(images))
# 打印找到的标签数量
print("Number of labels found: ", len(labels))
# 打印找到的唯一字符数量
print("Number of unique characters: ", len(characters))
# 打印存在的字符
print("Characters present: ", characters)

# 设置训练和验证时的批次大小
batch_size = 16

# 设置期望的图片尺寸
img_width = 200
img_height = 50

# 设置图片将要被卷积块下采样的因子
# 我们将使用两个卷积块,每个块都有一个池化层,每个池化层将特征下采样2倍
# 因此总的下采样因子是4。
downsample_factor = 4

# 设置数据集中任何验证码的最大长度
max_length = max([len(label) for label in labels])
2.3.2. 划分数据集
import tensorflow as tf
from tensorflow.keras.layers import StringLookup
import numpy as np

# 将字符映射为整数
# 使用StringLookup层来创建字符到数字的映射表
char_to_num = StringLookup(vocabulary=list(characters), mask_token=None)

# 将整数映射回原始字符
# 创建一个反向映射表,用于将数字转换回字符
num_to_char = StringLookup(
    vocabulary=char_to_num.get_vocabulary(), 
    mask_token=None, 
    invert=True
)

# 定义分割数据集的函数
def split_data(images, labels, train_size=0.9, shuffle=True):
    # 1. 获取数据集的总大小
    size = len(images)
    # 2. 创建索引数组,并根据需要进行打乱
    indices = tf.range(size)
    if shuffle:
        indices = tf.random.shuffle(indices)
    # 3. 计算训练样本的数量
    train_samples = int(size * train_size)
    # 4. 将数据分割为训练集和验证集
    x_train, y_train = images[indices[:train_samples]], labels[indices[:train_samples]]
    x_valid, y_valid = images[indices[train_samples:]], labels[indices[train_samples:]]
    return x_train, x_valid, y_train, y_valid

# 使用split_data函数分割数据集
x_train, x_valid, y_train, y_valid = split_data(np.array(images), np.array(labels))

# 定义编码单个样本的函数
def encode_single_sample(img_path, label):
    # 1. 读取图片文件
    img = tf.io.read_file(img_path)
    # 2. 解码图片并转换为灰度图
    img = tf.io.decode_png(img, channels=1)
    # 3. 将图片数据类型转换为float32,并归一化到[0, 1]区间
    img = tf.image.convert_image_dtype(img, tf.float32)
    # 4. 调整图片大小到指定的尺寸
    img = tf.image.resize(img, [img_height, img_width])
    # 5. 转置图片,使得时间维度对应图片的宽度
    img = tf.transpose(img, perm=[1, 0, 2])
    # 6. 将标签中的字符映射为数字
    label = char_to_num(tf.strings.unicode_split(label, input_encoding='UTF-8'))
    # 7. 返回字典,因为我们的模型期望两个输入
    return {"image": img, "label": label}

代码主要包含三个部分的功能:字符映射、数据集分割和样本编码。

  1. 字符映射
    - 代码首先创建了一个字符到数字的映射表,这使得模型能够将验证码中的字符转换为整数,以便进行数值运算。StringLookup层用于实现这一映射。
    - 接着,创建了一个反向映射表,将数字转换回原来的字符。这在模型训练完成后,将预测结果转换回可读字符时非常有用。

  2. 数据集分割
    - 定义了一个名为split_data的函数,它接受图片和标签列表,以及训练集所占的比例和是否打乱数据的标志。
    - 函数首先确定数据集的总大小,然后根据是否需要打乱数据来生成索引数组。
    - 计算训练集的大小,并根据这个大小将数据集分割为训练集和验证集。
    - 最后,函数返回分割后的训练集和验证集的图片和标签。

  3. 样本编码
    - 定义了一个名为encode_single_sample的函数,用于将单个样本(图片路径和标签)编码为模型可接受的格式。
    - 函数读取并解码图片文件,将其转换为灰度图,并归一化到[0, 1]区间。
    - 调整图片大小以符合模型输入要求,并进行转置,以确保时间维度与图片宽度对应。
    - 使用之前创建的字符到数字映射表,将标签中的字符转换为整数序列。
    - 返回一个字典,包含编码后的图片和标签,以供模型输入。

2.3.3. 创建数据集对象

通过创建数据集、编码样本、批处理和预取数据,为模型训练和验证提供了一个高效、可并行处理的数据流。这样做不仅优化了数据的读取和处理速度,还使得模型能够更好地利用硬件资源,加速训练和评估过程。

import tensorflow as tf

# 将训练数据转换为TensorFlow数据集
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))

# 使用map函数并行对每个样本进行编码,使用encode_single_sample函数
# num_parallel_calls设置为AUTOTUNE,以自动调整并行调用的数量
# 然后按照批次大小进行批处理,最后使用prefetch来提高训练时的数据读取效率
train_dataset = (
    train_dataset.map(encode_single_sample, num_parallel_calls=tf.data.AUTOTUNE)
    .batch(batch_size)
    .prefetch(buffer_size=tf.data.AUTOTUNE)
)

# 将验证数据转换为TensorFlow数据集
validation_dataset = tf.data.Dataset.from_tensor_slices((x_valid, y_valid))

# 对验证数据集执行与训练数据集相同的操作
validation_dataset = (
    validation_dataset.map(encode_single_sample, num_parallel_calls=tf.data.AUTOTUNE)
    .batch(batch_size)
    .prefetch(buffer_size=tf.data.AUTOTUNE)
)

# 中文注释:
# 1. 使用from_tensor_slices方法将训练数据和标签组合成一个数据集。
# 2. 使用map方法对数据集中的每个元素应用encode_single_sample函数进行编码。
#    num_parallel_calls参数设置为AUTOTUNE,允许TensorFlow自动选择最优的并行数量。
# 3. 使用batch方法将编码后的数据分成指定大小的批次。
# 4. 使用prefetch方法预取数据,以减少训练过程中的数据读取延迟。

代码的功能是将训练数据和验证数据转换成适合TensorFlow模型训练和评估的数据流,具体步骤和功能如下:

  1. 数据集创建

    • 使用tf.data.Dataset.from_tensor_slices函数,将训练集和验证集的图片路径和标签分别封装成tf.data.Dataset对象。这是TensorFlow中处理数据集的标准方式,它允许对数据进行高效的批处理和迭代。
  2. 数据编码

    • 利用map方法和之前定义的encode_single_sample函数,对数据集中的每个样本进行编码。这个过程包括读取图片文件、解码、归一化、调整大小、转置以及字符映射等步骤。num_parallel_calls=tf.data.AUTOTUNE参数允许TensorFlow自动调整用于并行处理的资源数量,以优化性能。
  3. 批处理

    • 通过batch方法,将编码后的数据分成大小为batch_size的批次。批处理是深度学习中常见的做法,可以提高内存使用效率和计算速度。
  4. 预取

    • 使用prefetch方法预加载数据。预取可以在模型训练时提前加载下一批数据,减少CPU和GPU等待数据的时间,从而提高训练效率。buffer_size=tf.data.AUTOTUNE同样允许TensorFlow自动调整预取的缓冲区大小。
2.3.4. 数据可视化(Visualize the data)

数据可视化是指使用图形、图表、图像、动画、视频等方式将数据以更直观、易于理解的形式展现出来。通过数据可视化,人们可以更快速地洞察数据的模式和趋势,发现数据中的关联性和异常值,从而做出更有效的决策。

import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np

# 使用matplotlib创建一个4行4列的子图网格,并设置整个图像的大小为10x5英寸
_, ax = plt.subplots(4, 4, figsize=(10, 5))

# 从训练数据集中取出第一个批次的数据进行可视化
for batch in train_dataset.take(1):
    # 获取批次中的图片和标签
    images = batch["image"]
    labels = batch["label"]
    
    # 遍历批次中的16个样本
    for i in range(16):
        # 将图片数据格式化为灰度图,并转换为uint8类型,以便于显示
        img = (images[i] * 255).numpy().astype("uint8")
        # 将标签从整数序列转换回字符串形式
        label = tf.strings.reduce_join(num_to_char(labels[i])).numpy().decode("utf-8")
        # 在子图网格的相应位置显示图片
        ax[i // 4, i % 4].imshow(img[:, :, 0].T, cmap="gray")
        # 设置子图的标题为对应的标签
        ax[i // 4, i % 4].set_title(label)
        # 关闭子图的坐标轴显示
        ax[i // 4, i % 4].axis("off")

# 显示所有的子图
plt.show()

上述代码主要执行以下功能:

  • 首先,使用plt.subplots创建一个4x4的子图网格,用于可视化训练数据集中的图片。
  • 通过调用train_dataset.take(1),我们取出训练数据集的第一个批次,这通常包含多个样本。
  • 对于批次中的每个样本,首先将图片数据从浮点数格式转换为8位整数格式,以适应显示要求。
  • 使用tf.strings.reduce_joinnum_to_char将标签从数字序列转换回人类可读的字符串。
  • 然后,使用imshow函数在子图网格的相应位置显示每张图片,并通过set_title设置每张图片的标题为对应的标签。
  • 最后,使用axis("off")关闭坐标轴的显示,使图像更加清晰,然后调用plt.show()显示所有的子图。
    在这里插入图片描述

2.4. 建立模型

以下代码定义了一个用于处理CTC(Connectionist Temporal Classification)损失的层,以及构建了一个OCR(Optical Character Recognition,光学字符识别)模型:

import tensorflow as tf
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Reshape, Dense, Dropout, LSTM, Bidirectional
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam

# 定义CTC批量损失函数
def ctc_batch_cost(y_true, y_pred, input_length, label_length):
    # 将标签长度和输入长度转换为整型
    label_length = tf.cast(tf.squeeze(label_length, axis=-1), dtype=tf.int32)
    input_length = tf.cast(tf.squeeze(input_length, axis=-1), dtype=tf.int32)
    # 将密集标签转换为稀疏表示
    sparse_labels = tf.cast(ctc_label_dense_to_sparse(y_true, label_length), dtype=tf.int32)
    
    # 将预测值进行转置和对数变换,添加一个小的epsilon值避免对数为负无穷
    y_pred = tf.math.log(tf.transpose(y_pred, axes=[1, 0, 2]) + tf.keras.backend.epsilon())
    
    # 计算CTC损失并扩展维度
    return tf.expand_dims(
        tf.nn.ctc_loss(inputs=y_pred, labels=sparse_labels, sequence_length=input_length),
        1
    )

# 将密集标签转换为稀疏标签的函数
def ctc_label_dense_to_sparse(labels, label_lengths):
    # ... 略过函数内部实现,函数主要目的是将密集标签转换为稀疏表示 ...

    return tf.SparseTensor(indices=indices, values=vals_sparse, dense_shape=label_shape)

# 定义CTC层
class CTCLayer(tf.keras.layers.Layer):
    def __init__(self, name=None):
        super().__init__(name=name)
        self.loss_fn = ctc_batch_cost

    def call(self, y_true, y_pred):
        # 计算CTC损失并添加到层中
        batch_len = tf.cast(tf.shape(y_true)[0], dtype=tf.int64)
        input_length = tf.cast(tf.shape(y_pred)[1], dtype=tf.int64)
        label_length = tf.cast(tf.shape(y_true)[1], dtype=tf.int64)
        
        # ... 略过损失计算和添加损失的代码 ...

        return y_pred

# 构建OCR模型的函数
def build_model():
    # 定义输入层
    input_img = Input(shape=(img_width, img_height, 1), name="image", dtype="float32")
    labels = Input(name="label", shape=(None,), dtype="float32")
    
    # 构建卷积层、池化层、全连接层、RNN层等模型结构
    # ... 略过模型构建代码 ...

    # 添加CTC层计算损失
    output = CTCLayer(name="ctc_loss")(labels, x)

    # 定义模型
    model = Model(inputs=[input_img, labels], outputs=output, name="ocr_model_v1")
    
    # 定义优化器并编译模型
    opt = Adam()
    model.compile(optimizer=opt)
    
    return model

# 获取模型并打印模型概览
model = build_model()
model.summary()

代码的主要功能如下:

  • ctc_batch_cost 函数用于计算CTC损失,它接受真实标签、预测值、输入长度和标签长度作为参数。
  • ctc_label_dense_to_sparse 函数将密集标签转换为稀疏标签,以适应CTC损失函数的要求。
  • CTCLayer 类是一个自定义的Keras层,用于在训练时计算CTC损失,并在测试时返回预测值。
  • build_model 函数构建了一个OCR模型,包括卷积层、池化层、全连接层、RNN层和一个自定义的CTC层。
  • 最后,实例化模型并打印出模型的概览信息。
Model: "ocr_model_v1"
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓
┃ Layer (type)        ┃ Output Shape      ┃ Param # ┃ Connected to         ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩
│ image (InputLayer)(None, 200, 50,0-                    │
│                     │ 1)                │         │                      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ Conv1 (Conv2D)(None, 200, 50,320 │ image[0][0]          │
│                     │ 32)               │         │                      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ pool1               │ (None, 100, 25,0 │ Conv1[0][0]          │
│ (MaxPooling2D)32)               │         │                      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ Conv2 (Conv2D)(None, 100, 25,18,496 │ pool1[0][0]          │
│                     │ 64)               │         │                      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ pool2               │ (None, 50, 12,0 │ Conv2[0][0]          │
│ (MaxPooling2D)64)               │         │                      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ reshape (Reshape)(None, 50, 768)0 │ pool2[0][0]          │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ dense1 (Dense)(None, 50, 64)49,216 │ reshape[0][0]        │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ dropout (Dropout)(None, 50, 64)0 │ dense1[0][0]         │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ bidirectional       │ (None, 50, 256)197,632 │ dropout[0][0]        │
│ (Bidirectional)     │                   │         │                      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ bidirectional_1     │ (None, 50, 128)164,352 │ bidirectional[0][0]  │
│ (Bidirectional)     │                   │         │                      │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ label (InputLayer)(None, None)0-                    │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ dense2 (Dense)(None, 50, 21)2,709 │ bidirectional_1[0][… │
├─────────────────────┼───────────────────┼─────────┼──────────────────────┤
│ ctc_loss (CTCLayer)(None, 50, 21)0 │ label[0][0],         │
│                     │                   │         │ dense2[0][0]         │
└─────────────────────┴───────────────────┴─────────┴──────────────────────┘
 Total params: 432,725 (1.65 MB)
 Trainable params: 432,725 (1.65 MB)
 Non-trainable params: 0 (0.00 B)

2.5 训练模型

import tensorflow as tf
from tensorflow.keras.callbacks import EarlyStopping

# 设置训练的周期数(epochs)
epochs = 100

# 设置早停法(Early Stopping)的耐心参数,即在停止训练前允许的连续无改进周期数
early_stopping_patience = 10

# 实例化早停法回调,监控验证集损失(val_loss),
# 如果连续early_stopping_patience个epoch没有改善,则停止训练。
# 训练停止时,恢复到最佳权重状态
early_stopping = EarlyStopping(
    monitor="val_loss",
    patience=early_stopping_patience,
    restore_best_weights=True
)

# 训练模型
# 使用fit方法训练模型,并传入训练数据集和验证数据集
# 同时指定训练周期数和回调列表,其中包含早停法回调
history = model.fit(
    train_dataset,               # 训练数据集
    validation_data=validation_dataset,  # 验证数据集
    epochs=epochs,                # 训练周期数
    callbacks=[early_stopping]    # 训练回调列表,包含早停法
)

代码主要功能如下:

  • 设置了训练模型时使用的周期数epochs,表示整个数据集将被遍历多少次。
  • 定义了早停法的patience参数early_stopping_patience,它决定了在验证集损失停止下降时,允许模型继续训练的额外周期数。
  • 创建了一个EarlyStopping回调实例,用于在模型训练过程中监控val_loss。如果连续early_stopping_patience个周期没有改善,则终止训练,并恢复到最佳表现时的模型权重。
  • 使用model.fit方法开始训练过程,传入训练数据集train_dataset和验证数据集validation_dataset。同时指定了训练的周期数epochs和回调列表,其中包含了早停法回调。
  • history对象将记录训练过程中的历史信息,包括损失值和评估指标等,可用于后续的分析和模型性能评估。

2.6 推理预测

以下代码执行CTC解码功能,用于从模型的输出中获取解码文本,并展示了如何在一些验证样本上使用该功能:

import numpy as np
import matplotlib.pyplot as plt

# CTC解码函数,支持贪婪解码和束搜索解码
def ctc_decode(y_pred, input_length, greedy=True, beam_width=100, top_paths=1):
    # 获取预测值的形状
    input_shape = tf.shape(y_pred)
    num_samples, num_steps = input_shape[0], input_shape[1]
    
    # 将预测值进行转置和对数变换,添加一个小的epsilon值避免对数为负无穷
    y_pred = tf.math.log(tf.transpose(y_pred, [1, 0, 2]) + tf.keras.backend.epsilon())
    input_length = tf.cast(input_length, dtype=tf.int32)

    # 根据解码方式不同,选择贪婪解码或束搜索解码
    if greedy:
        decoded, log_prob = tf.nn.ctc_greedy_decoder(
            inputs=y_pred, sequence_length=input_length
        )
    else:
        decoded, log_prob = tf.nn.ctc_beam_search_decoder(
            inputs=y_pred,
            sequence_length=input_length,
            beam_width=beam_width,
            top_paths=top_paths,
        )
    
    # 将解码结果从稀疏张量转换为密集张量
    decoded_dense = []
    for st in decoded:
        st = tf.SparseTensor(st.indices, st.values, (num_samples, num_steps))
        decoded_dense.append(tf.sparse.to_dense(st, default_value=-1))
    return decoded_dense, log_prob

# 提取模型的预测部分,直到输出层
prediction_model = tf.keras.models.Model(
    model.input[0], model.get_layer(name="dense2").output
)
prediction_model.summary()

# 解码批次预测结果的辅助函数
def decode_batch_predictions(pred):
    input_len = np.ones(pred.shape[0]) * pred.shape[1]
    # 使用贪婪搜索解码
    results = ctc_decode(pred, input_length=input_len, greedy=True)[0][0][:, :max_length]
    # 迭代结果并转换回文本
    output_text = []
    for res in results:
        res = tf.strings.reduce_join(num_to_char(res)).numpy().decode("utf-8")
        output_text.append(res)
    return output_text

# 检查一些验证样本上的结果
for batch in validation_dataset.take(1):
    batch_images = batch["image"]
    batch_labels = batch["label"]

    preds = prediction_model.predict(batch_images)
    pred_texts = decode_batch_predictions(preds)

    orig_texts = []
    for label in batch_labels:
        label = tf.strings.reduce_join(num_to_char(label)).numpy().decode("utf-8")
        orig_texts.append(label)

    _, ax = plt.subplots(4, 4, figsize=(15, 5))
    for i in range(len(pred_texts)):
        img = (batch_images[i, :, :, 0] * 255).numpy().astype(np.uint8)
        img = img.T
        title = f"Prediction: {pred_texts[i]}"
        ax[i // 4, i % 4].imshow(img, cmap="gray")
        ax[i // 4, i % 4].set_title(title)
        ax[i // 4, i % 4].axis("off")
plt.show()

代码的主要功能:

  • ctc_decode 函数实现了CTC解码,支持贪婪解码和束搜索解码两种方式。
  • 首先,对模型的输出进行转置和对数变换,然后根据选择的解码方式进行解码。
  • 解码后的结果为稀疏张量,将其转换为密集张量以便于后续处理。
  • decode_batch_predictions 函数用于解码模型预测结果,将其转换为可读的文本。
  • validation_dataset中取出一个批次的样本,使用prediction_model进行预测,并调用decode_batch_predictions函数获取预测文本。
  • 使用matplotlib库展示一些验证样本的图像和对应的预测文本。
Model: "functional_1"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape              ┃    Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ image (InputLayer)(None, 200, 50, 1)0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ Conv1 (Conv2D)(None, 200, 50, 32)320 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ pool1 (MaxPooling2D)(None, 100, 25, 32)0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ Conv2 (Conv2D)(None, 100, 25, 64)18,496 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ pool2 (MaxPooling2D)(None, 50, 12, 64)0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ reshape (Reshape)(None, 50, 768)0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dense1 (Dense)(None, 50, 64)49,216 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dropout (Dropout)(None, 50, 64)0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ bidirectional (Bidirectional)(None, 50, 256)197,632 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ bidirectional_1 (Bidirectional)(None, 50, 128)164,352 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dense2 (Dense)(None, 50, 21)2,709 │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 Total params: 432,725 (1.65 MB)
 Trainable params: 432,725 (1.65 MB)
 Non-trainable params: 0 (0.00 B)

在这里插入图片描述

3. 总结和展望

3.1 总结

在本篇博文中,我们全面探讨了OCR技术在验证码识别中的应用与发展。首先,我们介绍了OCR技术的研究背景,包括它在提升用户体验、保障系统安全和适应不断增长的网络安全需求方面的重要性。接着,我们概述了OCR模型的工作原理,从图像预处理到字符识别的整个流程,并讨论了OCR技术在不同领域的应用潜力。

其次,博文详细介绍了构建和训练OCR模型的过程。这包括了数据预处理、模型构建、设置训练环境、模型训练以及使用早停法等策略来优化模型性能。我们特别强调了CTC损失层在处理序列识别问题中的作用,并展示了如何使用TensorFlow和Keras框架来实现这一过程。

最后,我们讨论了模型的推理预测阶段,包括CTC解码的实现和预测结果的后处理。通过可视化工具,我们展示了模型对验证码图像的识别效果,并提供了对模型性能的直观评估。此外,我们总结了OCR技术在验证码识别领域的现状和未来发展方向,强调了技术的不断进步和适应性,以及在实际应用中提高模型鲁棒性和可解释性的重要性。

总而言之,本博文为读者提供了一个关于OCR验证码识别技术的深入指南,从理论基础到实践应用,再到未来展望,全面覆盖了该领域的各个方面。随着技术的不断发展,OCR技术将在网络安全和用户体验提升方面发挥更加关键的作用。

3.2.展望

在展望OCR验证码识别技术的未来时,我们可以看到几个明显的趋势。首先,技术的持续进步将推动模型变得更加精准和智能。深度学习的最新发展,如新型网络结构和优化算法,将进一步提升模型的识别准确率,尤其是在处理复杂和模糊的验证码图像时。此外,多模态学习和自适应能力的增强将使模型能够更好地泛化到新的验证码样式,同时提高模型对不同环境变化的适应性。

其次,随着对实时响应和高效能需求的增长,OCR模型的推理速度和资源效率将得到显著优化。这不仅将提高用户体验,也将使OCR技术能够满足更多实时应用场景的需求。同时,安全性将继续作为技术开发的重点,研究者们将致力于提高模型对恶意攻击的防御能力,并确保算法的决策过程是透明和可解释的。

最后,随着技术应用的扩展,OCR验证码识别技术将面临更多的伦理和法律挑战。数据隐私、算法偏见和透明度问题将推动制定更加严格的法律法规,以确保技术的安全和公正使用。同时,开源社区和标准化组织将在推动技术共享、标准化和普及方面发挥关键作用,促进技术的健康发展和广泛应用。总而言之,OCR技术的未来将是一个技术创新与社会责任并重的时代,它将在验证码识别以及其他领域发挥更加关键的作用。

参考文献

[1]Nain, A. K. (2020, June 14). How to implement an OCR model using CNNs, RNNs, and CTC loss. Keras Documentation. Retrieved March 13, 2024, from https://keras.io/examples/vision/captcha_ocr/

示例代码

# 导入必要的库
import os
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
import tensorflow as tf
import keras
from keras import ops
from keras import layers

# 设置Keras的后端为TensorFlow
os.environ["KERAS_BACKEND"] = "tensorflow"

## 加载数据:验证码图片
# 下载数据集
!curl -LO https://github.com/AakashKumarNain/CaptchaCracker/raw/master/captcha_images_v2.zip
!unzip -qq captcha_images_v2.zip

# 数据预处理
# 设置数据目录路径
data_dir = Path("./captcha_images_v2/")

# 获取所有图片文件并排序
images = sorted(list(map(str, list(data_dir.glob("*.png")))))
# 从图片路径中提取标签
labels = [img.split(os.path.sep)[-1].split(".png")[0] for img in images]
# 提取所有唯一字符并排序
characters = sorted(set(char for label in labels for char in label))

# 打印数据集统计信息
print("找到的图片数量: ", len(images))
print("找到的标签数量: ", len(labels))
print("找到的唯一字符数量: ", len(characters))
print("存在的字符: ", characters)

# 设置训练和验证时的批次大小
batch_size = 16

# 设置期望的图片尺寸
img_width = 200
img_height = 50

# 创建字符到整数的映射
char_to_num = layers.StringLookup(vocabulary=list(characters), mask_token=None)
# 创建整数回原字符的映射
num_to_char = layers.StringLookup(vocabulary=char_to_num.get_vocabulary(), mask_token=None, invert=True)

# 定义分割数据集的函数
def split_data(images, labels, train_size=0.9, shuffle=True):
    size = len(images)
    indices = tf.range(size)
    if shuffle:
        indices = keras.random.shuffle(indices)
    train_samples = int(size * train_size)
    x_train, y_train = images[indices[:train_samples]], labels[indices[:train_samples]]
    x_valid, y_valid = images[indices[train_samples:]], labels[indices[train_samples:]]
    return x_train, x_valid, y_train, y_valid

# 编码单个样本的函数
def encode_single_sample(img_path, label):
    img = tf.io.read_file(img_path)
    img = tf.io.decode_png(img, channels=1)
    img = tf.image.convert_image_dtype(img, tf.float32)
    img = tf.image.resize(img, [img_height, img_width])
    img = tf.transpose(img, perm=[1, 0, 2])
    label = char_to_num(tf.strings.unicode_split(label, input_encoding="UTF-8"))
    return {"image": img, "label": label}

# 创建数据集对象
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = (
    train_dataset.map(encode_single_sample, num_parallel_calls=tf.data.AUTOTUNE)
    .batch(batch_size)
    .prefetch(buffer_size=tf.data.AUTOTUNE)
)

validation_dataset = tf.data.Dataset.from_tensor_slices((x_valid, y_valid))
validation_dataset = (
    validation_dataset.map(encode_single_sample, num_parallel_calls=tf.data.AUTOTUNE)
    .batch(batch_size)
    .prefetch(buffer_size=tf.data.AUTOTUNE)
)

# 可视化数据
_, ax = plt.subplots(4, 4, figsize=(10, 5))
for batch in train_dataset.take(1):
    images = batch["image"]
    labels = batch["label"]
    for i in range(16):
        img = (images[i] * 255).numpy().astype("uint8")
        label = tf.strings.reduce_join(num_to_char(labels[i])).numpy().decode("utf-8")
        ax[i // 4, i % 4].imshow(img[:, :, 0].T, cmap="gray")
        ax[i // 4, i % 4].set_title(label)
        ax[i // 4, i % 4].axis("off")
plt.show()

# 定义CTC批量损失函数和CTC层
def ctc_batch_cost(y_true, y_pred, input_length, label_length):
    # ... 函数实现 ...

class CTCLayer(layers.Layer):
    # ... 类实现 ...

# 构建模型
def build_model():
    # ... 模型构建代码 ...
    return model

# 获取模型并打印模型概览
model = build_model()
model.summary()

# 训练模型
epochs = 100
early_stopping_patience = 10
early_stopping = keras.callbacks.EarlyStopping(monitor="val_loss", patience=early_stopping_patience, restore_best_weights=True)
history = model.fit(
    train_dataset,
    validation_data=validation_dataset,
    epochs=epochs,
    callbacks=[early_stopping],
)

# 推断
def ctc_decode(y_pred, input_length, greedy=True, beam_width=100, top_paths=1):
    # ... 函数实现 ...

# 获取预测模型
prediction_model = keras.models.Model(model.input[0], model.get_layer(name="dense2").output)
prediction_model.summary()

# 解码批次预测结果的辅助函数
def decode_batch_predictions(pred):
    # ... 函数实现 ...

# 检查一些验证样本上的结果
for batch in validation_dataset.take(1):
    # ... 检查和显示预测结果的代码 ...

plt.show()
# 构建模型的函数
def build_model():
    # 定义模型输入
    input_img = layers.Input(shape=(img_width, img_height, 1), name="image", dtype="float32")
    labels = layers.Input(name="label", shape=(None,), dtype="float32")

    # 构建第一个卷积块
    x = layers.Conv2D(32, (3, 3), activation='relu', 
                      kernel_initializer='he_normal', padding='same', name='Conv1')(input_img)
    x = layers.MaxPooling2D((2, 2), name='pool1')(x)

    # 构建第二个卷积块
    x = layers.Conv2D(64, (3, 3), activation='relu', 
                      kernel_initializer='he_normal', padding='same', name='Conv2')(x)
    x = layers.MaxPooling2D((2, 2), name='pool2')(x)

    # 调整输出形状以适应RNN部分
    new_shape = ((img_width // 4), (img_height // 4) * 64)
    x = layers.Reshape(target_shape=new_shape, name="reshape")(x)
    x = layers.Dense(64, activation='relu', name="dense1")(x)
    x = layers.Dropout(0.2)(x)

    # 堆叠双向LSTM层
    x = layers.Bidirectional(layers.LSTM(128, return_sequences=True, dropout=0.25))(x)
    x = layers.Bidirectional(layers.LSTM(64, return_sequences=True, dropout=0.25))(x)

    # 输出层,使用softmax激活函数
    x = layers.Dense(len(char_to_num.get_vocabulary()) + 1, activation="softmax", name="dense2")(x)

    # 添加CTC层以计算损失
    output = CTCLayer(name="ctc_loss")(labels, x)

    # 定义模型
    model = keras.models.Model(inputs=[input_img, labels], outputs=output, name="ocr_model_v1")
    
    # 编译模型
    optimizer = keras.optimizers.Adam()
    model.compile(optimizer=optimizer)
    
    return model

# 获取模型实例并打印概览
model = build_model()
model.summary()

# 训练模型
epochs = 100  # 训练周期数
early_stopping_patience = 10  # 早停法的耐心参数

# 实例化早停法回调
early_stopping = keras.callbacks.EarlyStopping(
    monitor="val_loss", 
    patience=early_stopping_patience, 
    restore_best_weights=True
)

# 训练模型
history = model.fit(
    train_dataset,             # 训练数据集
    validation_data=validation_dataset,  # 验证数据集
    epochs=epochs,              # 训练周期数
    callbacks=[early_stopping]   # 训练回调列表,包含早停法
)


def ctc_decode(y_pred, input_length, greedy=True, beam_width=100, top_paths=1):
    # 转置并应用对数变换到预测值
    y_pred = tf.math.log(tf.transpose(y_pred, [1, 0, 2]) + keras.backend.epsilon())
    input_length = tf.cast(input_length, dtype=tf.int32)

    # 贪婪解码或束搜索解码
    if greedy:
        decoded, log_prob = tf.nn.ctc_greedy_decoder(y_pred, input_length)
    else:
        decoded, log_prob = tf.nn.ctc_beam_search_decoder(
            y_pred, 
            input_length, 
            beam_width=beam_width, 
            top_paths=top_paths
        )
    
    # 将解码结果转换为密集张量
    decoded_dense = [tf.sparse.to_dense(sp_input=st, default_value=-1) for st in decoded]
    
    return decoded_dense, log_prob

# 获取预测模型
prediction_model = keras.models.Model(model.input[0], model.get_layer(name="dense2").output)
prediction_model.summary()

# 解码网络输出的辅助函数
def decode_batch_predictions(pred):
    input_len = np.ones(pred.shape[0]) * pred.shape[1]
    results = ctc_decode(pred, input_length=input_len, greedy=True)[0][0][:, :max_length]
    
    output_text = []
    for res in results:
        res = tf.strings.reduce_join(num_to_char(res)).numpy().decode("utf-8")
        output_text.append(res)
    
    return output_text

# 检查验证样本上的结果
for batch in validation_dataset.take(1):
    batch_images = batch["image"]
    batch_labels = batch["label"]

    preds = prediction_model.predict(batch_images)
    pred_texts = decode_batch_predictions(preds)

    orig_texts = [tf.strings.reduce_join(num_to_char(label)).numpy().decode("utf-8") for label in batch_labels]

    # 可视化一些验证样本及其预测结果
    fig, ax = plt.subplots(4, 4, figsize=(15, 5))
    for i, (img, title) in enumerate(zip(batch_images, pred_texts)):
        img = (img[:, :, 0] * 255).numpy().astype(np.uint8).T
        ax[i // 4, i % 4].imshow(img, cmap="gray")
        ax[i // 4, i % 4].set_title(title)
        ax[i // 4, i % 4].axis("off")
    plt.show()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1814201.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录:回溯19

332.重新安排行程 题目 给你一份航线列表 tickets ,其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从…

C语言面试题总结(含参考答案)------持续更新

1、关键字static的作用是什么? 在函数内部使用static修饰局部变量时,表示该变量在程序的整个生命周期内只会被初始化一次,并且在函数调用结束后不会被销毁,其值会一直保持。这种特性使得静态局部变量成为一种很有用的工具&#xf…

【ARM】MDK出现报错error: A\L3903U的解决方法

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决MDK出现报错error: A\L3903U这样类型的报错 2、 问题场景 电脑或者软件因为意外情况导致崩溃,无法正常关闭,强制电脑重启之后,打开工程去编译出现下面的报错信息(…

怎么把pdf格式文件其中几页单独弄出来

在现代办公和学习环境中,pdf格式的文件因其跨平台兼容性和良好的保持原样特性而备受欢迎。然而,有时我们可能只需要pdf文件中的某几页,而不是整个文件。这时,将PDF文件中的特定页面单独提取出来就显得尤为重要。 搜索一下&#xf…

React基础教程:TodoList案例

todoList案例——增加 定义状态 // 定义状态state {list: ["kevin", "book", "paul"]}利用ul遍历list数组 <ul>{this.state.list.map(item ><li style{{fontWeight: "bold", fontSize: "20px"}} key{item.i…

后端项目实战--瑞吉外卖项目软件说明书

瑞吉外卖项目软件说明书 一、项目概述 瑞吉外卖项目是一个外卖服务平台&#xff0c;用户可以通过该平台浏览餐厅菜单、下单、支付以及追踪订单状态。产品原型就是一款产品成型之前的一个简单的框架&#xff0c;就是将页面的排版布局展现出来&#xff0c;使产品得初步构思有一…

Linux ldd和ldconfig

ldconfig ldconfig 查看默认库路径和ld.so.conf包含的库路径&#xff0c;来建立运行时动态装载的库查找路径。 ldconfig命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如前介绍,lib*.so*),…

推挽式B类功率放大器的基本原理

单晶体管 B 类放大器&#xff08;图 1&#xff09;使用高 Q 值储能电路作为负载来抑制高次谐波分量。通过采用高 Q 谐振电路&#xff0c;输出电压仅包含基波分量&#xff0c;使放大器能够忠实地再现输入信号。尽管集电极电流是半波整流正弦波&#xff0c;但高 Q 值储能电路将谐…

11_1、多态性:概念及运算符重载

多态性 多态的概念和类型多态的类型多态的实现 运算符重载运算符重载的概念和规则概念规则 运算符重载为类的成员函数双目运算符单目运算符 运算符重载为类的友元函数双目运算符重载单目运算符重载 多态的概念和类型 消息&#xff1a;消息在C编程中指的是对类的成员函数的调用…

【C++进阶】模板与仿函数:C++编程中的泛型与函数式编程思想

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;栈和队列相关知识 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀模板进阶 &#x1f9e9;<&…

深度学习之激活函数

激活函数&#xff08;Activation Function&#xff09;是一种添加到人工神经网络中的函数&#xff0c;旨在帮助网络学习数据中的复杂模式。在神经元中&#xff0c;输入的input经过一系列加权求和后作用于另一个函数&#xff0c;这个函数就是这里的激活函数。 1. 为什么需要激活…

在Oxygen中如何打开文件管理器并显示文件所在目录

▲ 搜索“大龙谈智能内容”关注公众号▲ 在Oxygen中&#xff0c;我们常需要查看项目中的某文件在Windows“文件资源管理器”所在位置&#xff0c;从而进行拷贝、分享等各种操作。 通过以下方法可以实现&#xff1a; 1. 在Oxygen的“项目”中选择文件并单击右键 2. 选择菜单…

数据可视化后起之秀——pyecharts

题目一&#xff1a;绘制折线图&#xff0c;展示商家A与商家B各类饮品的销售额 题目描述&#xff1a; 编写程序。根据第9.3.1&#xff0c;绘制折线图&#xff0c;展示商家A与商家B各类饮品的销售额。 运行代码&#xff1a; #绘制折线图&#xff0c;展示商家A与商家B各类饮品的…

【区块链】解码拜占庭将军问题:区块链共识机制的哲学基石

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 解码拜占庭将军问题&#xff1a;区块链共识机制的哲学基石引言一、拜占庭将军问…

ComfyUI 快速搭建流程

相关地址 ComfyUIPytorch版本 环境准备 nvidia 3090 ----------------------------------------------------------------------------- | NVIDIA-SMI 515.65.01 Driver Version: 515.65.01 CUDA Version: 11.7 | |--------------------------------------------…

码垛机性能的关键因素及优化策略

在工业自动化领域&#xff0c;码垛机以其高效、准确的特点&#xff0c;成为现代生产线上的得力助手。然而&#xff0c;要想充分发挥码垛机的性能优势&#xff0c;就必须深入了解影响其性能的关键因素&#xff0c;并针对性地制定优化策略。星派将为您详细解析码垛机性能的关键影…

数据仓库与数据挖掘实验练习题

练习题2 1. 使用超级英雄列表来填充一个新的 Series 对象。 2. 使用力量元组来填充一个新的 Series 对象。 3. 创建一个 Series&#xff0c;将超级英雄作为索引标签&#xff0c;力量等级作为值。将这个 Series 赋值给 heroes 变量。 4. 提取 heroes Series 的前两行。 5. 提取 …

程序员必备的职业素养:专业精神、沟通能力与持续学习

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 专业精神&#xff1a;技术的执着追求 沟通能力&#xff1a;团队合作的桥梁 持续学习&#xff1a;不断进步的动力 结语 我的…

世优科技AI数字人多模态交互系统“世优波塔”正式发布

2024年6月6日&#xff0c;世优科技“波塔发布会”在北京举办&#xff0c;本次发布会上&#xff0c;世优科技以全新的“波塔”产品诠释了更高效、更智能、更全面的AI数字人产品及软硬件全场景解决方案&#xff0c;实现了世优品牌、产品和价值的全面跃迁。来自行业协会、数字产业…

商用车CAN数据数字化是促进生态环保高质量发展的桥梁纽带

在当今这个智能化、电动化、信息化、数字化快速发展的时代&#xff0c;其中数字化转型已经成为各行各业提升效率、优化管理的关键途径&#xff0c;21世纪这个被誉为“大数据时代”的纪元&#xff0c;数据的潜力、生产力、作为基础设施被无限放大&#xff0c;其在各个领域&#…