首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >0.08M参数吊打YOLO!MicroCharNet车牌字符检测mAP@50达0.85,推理速度1ms,轻量王者

0.08M参数吊打YOLO!MicroCharNet车牌字符检测mAP@50达0.85,推理速度1ms,轻量王者

原创
作者头像
AI小怪兽
发布2026-07-17 16:29:53
发布2026-07-17 16:29:53
1870
举报
文章被收录于专栏:毕业设计毕业设计YOLO大作战

💡💡💡本文核心贡献如下:

  1. 超轻量架构设计:提出MicroCharNet,仅0.08M参数、0.096 GFLOPs,实现车牌字符检测mAP@50达0.85,推理仅1ms。
  2. 定制化特征提取:采用C2f骨干+CoordAtt模块,在保留空间位置信息的同时增强小字符特征表达。
  3. 高效颈部与检测头:轻量C3k2颈部融合多级特征,单级无锚框检测头实现端到端预测,去除NMS后处理,降低延迟。
  4. 硬件部署验证:在Jetson Nano(3.0ms)和Orange Pi(3.9ms)上验证实时推理能力,CPU负载大幅降低。

博主简介

AI小怪兽 | 计算机视觉布道者 | 视觉检测领域创新者

深耕计算机视觉与深度学习领域,专注于视觉检测前沿技术的探索与突破。长期致力于YOLO系列算法的结构性创新、性能极限优化与工业级落地实践,旨在打通从学术研究到产业应用的最后一公里。

🚀 核心专长与技术创新

  • YOLO算法结构性创新:于CSDN平台原创发布《YOLO26魔术师》、《YOLO11魔术师》等全系列深度专栏。系统性提出并开源了多项原创自研模块,在模型轻量化设计、多维度注意力机制融合、特征金字塔重构等关键方向完成了一系列突破性实践,为行业提供了具备高参考价值的技术路径与完整解决方案。
  • 技术生态建设与知识传播:独立运营 “计算机视觉大作战” 公众号(粉丝2.2万),成功构建高质量的技术交流社群。致力于将复杂算法转化为通俗易懂的解读与可复现的工程代码,显著降低了计算机视觉的技术入门门槛。

🏆 行业影响力与商业实践

  • 荣获腾讯云年度影响力作者创作之星奖项,内容质量与专业性获行业权威平台认证。
  • 全网累计拥有 8万+ 垂直领域技术受众,专栏文章总阅读量突破百万,在目标检测领域形成了广泛的学术与工业影响力。
  • 具备丰富的企业级项目交付经验,曾为工业视觉检测、智慧城市安防等多个关键领域提供定制化的算法模型与解决方案,驱动业务智能化升级。

💡 未来方向与使命

秉持 “让每一行代码都有温度” 的技术理念,未来将持续聚焦于实时检测、语义分割及工业缺陷检测的商业化闭环等核心方向。愿与业界同仁协同创新,共同推动技术边界,以坚实的技术能力赋能实体经济与行业变革。

0.原理介绍

摘要——车牌字符检测是智能交通系统中的关键组成部分,要求在实时部署中兼具高精度和高计算效率。尽管近年基于深度学习的方法已显著提升检测性能,但许多高精度模型依赖大规模架构,带来大量计算开销,限制了其在资源受限设备上的适用性。本文提出 MicroCharNet,一种专为车牌字符检测设计的超轻量级模型。该架构采用由 C2f 模块构成的紧凑骨干网络,集成 CoordAtt 模块以在保留空间信息的同时增强特征提取。轻量级 C3k2 颈部融合多级特征,随后由单层无锚框检测头实现端到端预测。在 UFPR-ALPR 数据集上的实验表明,MicroCharNet 仅以 0.08M 参数和 0.096 GFLOPs 便达到具有竞争力的检测精度,并优于多个近期基于 YOLO 的基线。硬件级评估进一步证实了其在边缘设备上实时部署的高效性。这些结果表明,精心设计的超轻量级架构能够有效平衡车牌字符检测中的精度与效率。

伪代码如下:

代码语言:txt
复制
import torch
import torch.nn as nn
import torch.nn.functional as F


class C2fBlock(nn.Module):
    """
    C2f block: split-transform-concatenate paradigm.
    Paper Eq. (1)-(3): X1,X2 = split(Conv1x1(X)); X̂2 = X2 + Conv3x3(Conv3x3(X2));
    Y = Conv1x1(Concat(X1, X̂2))
    """
    def __init__(self, in_channels, out_channels, hidden_ratio=0.5):
        super().__init__()
        hidden_channels = int(in_channels * hidden_ratio)
        
        self.proj = nn.Conv2d(in_channels, hidden_channels * 2, kernel_size=1)
        self.conv1 = nn.Conv2d(hidden_channels, hidden_channels, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(hidden_channels, hidden_channels, kernel_size=3, padding=1)
        self.fuse = nn.Conv2d(hidden_channels * 2, out_channels, kernel_size=1)
        
    def forward(self, x):
        # Project and split
        x_proj = self.proj(x)                    # [B, 2*C', H, W]
        x1, x2 = x_proj.chunk(2, dim=1)          # [B, C', H, W], [B, C', H, W]
        
        # Transform branch
        x2_hat = self.conv2(self.conv1(x2))      # [B, C', H, W]
        x2_hat = x2 + x2_hat                     # Residual connection (Eq. 2)
        
        # Concatenate and fuse (Eq. 3)
        out = torch.cat([x1, x2_hat], dim=1)     # [B, 2*C', H, W]
        out = self.fuse(out)                     # [B, out_channels, H, W]
        return out


class CoordAtt(nn.Module):
    """
    Coordinate Attention module.
    Decomposes contextual encoding into two separate spatial directions.
    """
    def __init__(self, in_channels, reduction=32):
        super().__init__()
        self.reduction = reduction
        reduced_channels = max(1, in_channels // reduction)
        
        self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
        self.pool_w = nn.AdaptiveAvgPool2d((1, None))
        
        self.shared_conv = nn.Sequential(
            nn.Conv2d(in_channels, reduced_channels, kernel_size=1),
            nn.BatchNorm2d(reduced_channels),
            nn.Hardswish()
        )
        self.conv_h = nn.Conv2d(reduced_channels, in_channels, kernel_size=1)
        self.conv_w = nn.Conv2d(reduced_channels, in_channels, kernel_size=1)
        
    def forward(self, x):
        B, C, H, W = x.shape
        
        # Height-aware and width-aware pooling
        x_h = self.pool_h(x)                     # [B, C, H, 1]
        x_w = self.pool_w(x).permute(0, 1, 3, 2) # [B, C, W, 1] -> [B, C, 1, W]
        
        # Shared transformation
        y = torch.cat([x_h, x_w], dim=2)         # [B, C, H+W, 1]? Actually [B,C,H+W,1]? 
        # Correct implementation: concatenate along spatial dimension
        # x_h: [B,C,H,1], x_w: [B,C,1,W] -> need to permute x_w to [B,C,W,1]
        x_w = x_w.permute(0, 1, 3, 2)            # [B, C, W, 1]
        y = torch.cat([x_h, x_w], dim=2)         # [B, C, H+W, 1]
        y = self.shared_conv(y)                  # [B, C//r, H+W, 1]
        
        # Split back
        y_h, y_w = torch.split(y, [H, W], dim=2)
        y_h = y_h.unsqueeze(3)                   # [B, C//r, H, 1]
        y_w = y_w.unsqueeze(3)                   # [B, C//r, W, 1]
        
        # Generate attention maps
        a_h = torch.sigmoid(self.conv_h(y_h))    # [B, C, H, 1]
        a_w = torch.sigmoid(self.conv_w(y_w))    # [B, C, 1, W]
        
        # Apply attention (Eq. 4)
        out = x * a_h * a_w
        return out


class C3k2Block(nn.Module):
    """
    C3k2: lightweight variant derived from C2f structure.
    Enhanced feature aggregation with short information paths.
    """
    def __init__(self, in_channels, out_channels, hidden_ratio=0.5):
        super().__init__()
        hidden_channels = int(in_channels * hidden_ratio)
        
        self.proj = nn.Conv2d(in_channels, hidden_channels, kernel_size=1)
        self.conv1 = nn.Conv2d(hidden_channels, hidden_channels, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(hidden_channels, hidden_channels, kernel_size=3, padding=1)
        self.fuse = nn.Conv2d(hidden_channels + hidden_channels, out_channels, kernel_size=1)
        
    def forward(self, x):
        # Short path + local transformations
        x_proj = self.proj(x)
        x1 = self.conv1(x_proj)
        x2 = self.conv2(x1)
        
        # Concatenate short path and transformed features
        out = torch.cat([x1, x2], dim=1)
        out = self.fuse(out)
        return out


class DetectionHead(nn.Module):
    """
    Single-level anchor-free detection head.
    reg_max=1 for direct bounding-box regression (end-to-end).
    """
    def __init__(self, in_channels, num_classes=36, reg_max=1):
        super().__init__()
        self.num_classes = num_classes
        self.reg_max = reg_max
        
        # Shared convolution
        self.shared_conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, padding=1)
        
        # Classification branch
        self.cls_conv = nn.Conv2d(in_channels, num_classes, kernel_size=1)
        
        # Regression branch (4 coordinates * reg_max)
        self.reg_conv = nn.Conv2d(in_channels, 4 * reg_max, kernel_size=1)
        
    def forward(self, x):
        x = self.shared_conv(x)
        
        # Class predictions: [B, num_classes, H, W]
        cls_pred = self.cls_conv(x)
        
        # Box predictions: [B, 4*reg_max, H, W]
        reg_pred = self.reg_conv(x)
        
        # Reshape to [B, H, W, 4] for bounding boxes
        B, _, H, W = reg_pred.shape
        reg_pred = reg_pred.view(B, 4, self.reg_max, H, W).sum(dim=2)  # reg_max=1 -> direct
        reg_pred = reg_pred.permute(0, 2, 3, 1)  # [B, H, W, 4]
        
        # cls_pred: [B, num_classes, H, W] -> [B, H, W, num_classes]
        cls_pred = cls_pred.permute(0, 2, 3, 1)
        
        return cls_pred, reg_pred


class MicroCharNet(nn.Module):
    """
    Ultra-lightweight model for license plate character detection.
    Total: ~0.08M parameters, 0.096 GFLOPs.
    Input: 128x128 RGB image.
    """
    def __init__(self, num_classes=36, input_channels=3):
        super().__init__()
        
        # ---- Backbone ----
        # Stem
        self.stem = nn.Conv2d(input_channels, 16, kernel_size=3, stride=2, padding=1)
        
        # Stage 1
        self.conv1 = nn.Conv2d(16, 32, kernel_size=3, stride=2, padding=1)
        self.c2f1 = C2fBlock(32, 32)
        
        # Stage 2 (deepest)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1)
        self.c2f2 = C2fBlock(64, 64)
        
        # Coordinate Attention
        self.coord_att = CoordAtt(64)
        
        # ---- Neck ----
        # Upsample and fuse (deep -> shallow)
        self.upsample = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=False)
        self.fusion_conv = nn.Conv2d(64 + 32, 64, kernel_size=1)
        self.c3k2 = C3k2Block(64, 64)
        
        # ---- Detection Head ----
        self.head = DetectionHead(64, num_classes=num_classes, reg_max=1)
        
    def forward(self, x):
        # ---- Backbone ----
        # Stem
        x = self.stem(x)                        # [B, 16, H/2, W/2]
        
        # Stage 1 (shallow feature for fusion)
        x1 = self.conv1(x)                      # [B, 32, H/4, W/4]
        x1 = self.c2f1(x1)                     # [B, 32, H/4, W/4]
        
        # Stage 2 (deep feature)
        x2 = self.conv2(x1)                     # [B, 64, H/8, W/8]
        x2 = self.c2f2(x2)                     # [B, 64, H/8, W/8]
        
        # Coordinate Attention
        x2 = self.coord_att(x2)                 # [B, 64, H/8, W/8]
        
        # ---- Neck ----
        # Upsample deep feature and fuse with shallow
        x_up = self.upsample(x2)                # [B, 64, H/4, W/4]
        x_fused = torch.cat([x_up, x1], dim=1)  # [B, 96, H/4, W/4]
        x_fused = self.fusion_conv(x_fused)     # [B, 64, H/4, W/4]
        x_fused = self.c3k2(x_fused)            # [B, 64, H/4, W/4]
        
        # ---- Detection Head ----
        cls_pred, reg_pred = self.head(x_fused)  # [B, H/4, W/4, C], [B, H/4, W/4, 4]
        
        return cls_pred, reg_pred


def decode_predictions(cls_pred, reg_pred, threshold=0.5):
    """
    Decode model outputs to bounding boxes and class labels.
    
    Args:
        cls_pred: [B, H, W, num_classes] class logits
        reg_pred: [B, H, W, 4] bounding box offsets (cx, cy, w, h) in grid coordinates
        threshold: confidence threshold
    
    Returns:
        List of detection results per image: [{'boxes': [...], 'labels': [...], 'scores': [...]}]
    """
    batch_size = cls_pred.shape[0]
    H, W = cls_pred.shape[1], cls_pred.shape[2]
    results = []
    
    for b in range(batch_size):
        boxes, labels, scores = [], [], []
        
        # Get class probabilities
        probs = torch.softmax(cls_pred[b], dim=-1)  # [H, W, num_classes]
        max_probs, max_labels = probs.max(dim=-1)    # [H, W]
        
        # Find detections above threshold
        mask = max_probs > threshold
        indices = torch.nonzero(mask)                # [N, 2] (h, w)
        
        for h, w in indices:
            # Grid coordinates (center of cell)
            cx = (w + 0.5) / W
            cy = (h + 0.5) / H
            
            # Decode bounding box (relative to image)
            dx, dy, dw, dh = reg_pred[b, h, w]
            cx_pred = cx + dx
            cy_pred = cy + dy
            w_pred = dw
            h_pred = dh
            
            # Clamp to valid range
            x1 = max(0, cx_pred - w_pred / 2)
            y1 = max(0, cy_pred - h_pred / 2)
            x2 = min(1, cx_pred + w_pred / 2)
            y2 = min(1, cy_pred + h_pred / 2)
            
            boxes.append([x1, y1, x2, y2])
            labels.append(max_labels[h, w].item())
            scores.append(max_probs[h, w].item())
        
        results.append({
            'boxes': torch.tensor(boxes) if boxes else torch.empty(0, 4),
            'labels': torch.tensor(labels) if labels else torch.empty(0, dtype=torch.long),
            'scores': torch.tensor(scores) if scores else torch.empty(0)
        })
    
    return results


# ============ Usage Example ============
if __name__ == "__main__":
    # Create model
    model = MicroCharNet(num_classes=36)
    
    # Count parameters
    total_params = sum(p.numel() for p in model.parameters())
    print(f"Total parameters: {total_params:,} (~{total_params/1e6:.2f}M)")
    # Expected: ~0.08M
    
    # Forward pass
    dummy_input = torch.randn(1, 3, 128, 128)
    cls_pred, reg_pred = model(dummy_input)
    
    print(f"Class predictions shape: {cls_pred.shape}")  # [1, 32, 32, 36]
    print(f"Box predictions shape: {reg_pred.shape}")    # [1, 32, 32, 4]
    
    # Decode predictions
    results = decode_predictions(cls_pred, reg_pred, threshold=0.5)
    print(f"Detected {len(results[0]['boxes'])} characters")

I. 引言

车牌识别是众多智能交通系统中的关键组成部分,包括车辆监控[1]、智能停车[2]和交通违章管理[3]。在实际部署场景中,尤其是在边缘摄像头或资源受限的计算平台上,识别模型不仅需要达到高精度,还必须确保快速推理以支持稳定的实时运行。

近年来,深度学习模型显著提升了自然图像中字符和字符序列识别的性能。然而,大多数高精度架构伴随着大量参数和高计算成本,使其难以直接部署在资源有限的边缘设备上。在现有方法中,YOLO系列[4]–[9]凭借其快速、灵活的目标检测能力,为实时车牌字符检测提供了一种有效的解决方案。然而,由于这些架构最初是为通用计算机视觉任务设计的,它们在直接应用于车牌字符检测时,往往无法充分利用字符的细粒度几何特征、序列关系以及该任务核心的窄区域兴趣区。因此,在通用视觉任务上训练的轻量级网络,在迁移到车牌字符检测等专业应用领域时,通常无法达到最优性能[1]–[3], [10]。

与优先采用大规模模型以最大化精度的策略不同,本研究侧重于适用于资源受限设备实际部署的配置。我们的目标不仅是开发一个紧凑模型,还要证明专为车牌字符识别定制的架构能够在显著降低计算复杂度和部署成本的同时,保持具有竞争力的性能。特别地,所提出的MicroCharNet仅包含82.5K个参数,需要0.096 GFLOPs的计算量,同时在UFPR-ALPR数据集[14]的字符检测任务上仍能达到具有竞争力的精度。这些结果表明,超轻量级模型在适当设计时,具有在真实世界车牌识别系统中同时满足精度和可部署性要求的巨大潜力。

本文的贡献总结如下:(1) 我们提出了MicroCharNet,一种专为车牌字符检测设计的超轻量级架构,适用于资源受限设备上的实时部署。(2) 在UFPR-ALPR数据集[14]上的大量实验证明了其在检测精度和计算效率方面的竞争力。(3) 通过在不同硬件平台上的推理速度和运行时效率评估,进一步验证了其实际部署能力。

II. 相关工作

自动车牌识别是计算机视觉和智能交通系统中的一个重要问题。除车牌定位外,字符预测扮演着尤为关键的角色,因为它直接决定了最终识别出的车牌信息的准确性。为应对这一任务,研究者提出了多种方法,包括目标检测[10]、[14]、[15]、Seq2Seq[16]和图像分割[17]。深度学习的快速发展推动了端到端和单阶段方法的发展,使模型能够直接从数据中学习判别性特征,并显著提升了在真实车牌数据集上的性能。在这些方法中,基于目标检测的方法因其灵活性、预测有效性和实际部署的便利性而受到了广泛关注。近年来,YOLO系列因其在精度和推理速度之间的良好平衡,已被广泛用于实时目标检测。然而,大多数YOLO架构最初是为通用视觉任务设计的,因此并未针对车牌字符检测进行充分优化,其中目标通常较小、较窄且密集排列。此外,高精度模型往往伴随着大量参数和巨大的计算成本,这限制了它们在边缘设备和资源受限嵌入式平台上的适用性。

因此,大量研究聚焦于轻量级架构[18]–[21],在保持竞争性能的同时降低计算复杂度。与此同时,注意力模块[22]–[25]被引入以增强特征表示,使网络能够强调信息区域并提高定位能力。然而,简单地缩小通用架构或引入注意力模块并不一定能获得车牌字符检测的最佳性能。该任务不仅需要保留细粒度的局部细节,还需要精确建模紧密排列字符之间的微妙空间关系。因此,近年来的工作越来越多地转向针对目标应用领域定制的轻量级模型。

基于上述观察,本研究开发了一种用于车牌字符检测的超轻量级架构,该架构结合了紧凑设计与增强的空间信息建模,旨在检测精度、计算成本和跨不同硬件平台的实际部署能力之间实现良好的平衡。

III. 方法

我们的车牌字符检测模型基于三个组件构建,如图2所示。

A. 骨干网络

给定输入图像 I ∈ R³×H×W,骨干网络首先执行层次化特征提取。在此阶段,采用步长为2的卷积层逐步降低空间分辨率,同时增加通道维度,使模型能够高效捕获不同抽象层次的局部模式。在下采样阶段后的中间特征图上,引入C2f模块以增强特征表示,同时保持轻量级的计算结构。

C2f模块遵循“分割-变换-拼接”范式。给定输入特征图 X_c2f ∈ R^{C1×H×W},首先通过1×1卷积进行投影,然后沿通道维度进行分割:

然后通过拼接后接1×1卷积进行特征融合:

​ 与较重的残差块相比,这种设计改进了特征复用和信息流,同时减少了冗余变换。对于车牌字符检测,这种机制尤其有益,因为它能在学习稳定几何字符模式的同时保留精细的边缘细节。

在骨干网络的末端,集成了一个坐标注意力模块,以增强空间特征表示,同时保持较低的计算开销。与将整个空间域压缩为单个全局描述符的传统注意力机制不同,CoordAtt将上下文编码分解为两个独立的空间方向。这种设计使模块不仅能捕获通道间依赖关系,还能保留沿每个坐标轴的位置信息,这对于检测如车牌字符这样的小尺寸、几何结构化目标尤其有利。给定一个由前层产生的输入特征张量 X_attn ∈ R^{C2×H/8×W/8},CoordAtt首先执行两个一维特征聚合操作。具体来说,特征图沿宽度方向进行池化,生成高度感知表示 X_h ∈ R^{C2×H/8×1},并沿高度方向进行池化,生成宽度感知表示 X_w ∈ R^{C2×1×W/8}。

在将宽度感知分支转置以匹配高度感知分支的空间布局后,将两个表示沿空间维度拼接,并通过共享的1×1卷积,后接批归一化和hard-swish激活函数。该变换生成一个紧凑的中间表示,共同编码来自两个空间方向的坐标信息。然后将得到的特征分割回对应于高度和宽度方向的两个分支。每个分支由独立的1×1卷积处理,并通过sigmoid激活进行归一化,生成两个注意力图 A_h ∈ R^{C2×H/8×1} 和 A_w ∈ R^{C2×1×W/8}。

最后,这两个注意力图通过逐元素乘法应用于输入特征张量:

B. 颈部

基于骨干网络提取的深层特征,颈部被设计为轻量级并执行多级特征融合。具体来说,将坐标注意力后的特征图上采样,并与较浅层的中间特征图拼接。这种融合机制使模型能够将来自深层次的高层上下文信息与来自高分辨率特征的细粒度空间细节相结合,从而改善小字符的检测。

融合后,应用C3k2块来细化和重组特征表示。本质上,C3k2是从C2f结构衍生而来的轻量级变体,旨在提高特征聚合效率,而不显著增加计算成本。通过将短信息路径与有效的局部变换相结合,C3k2增强了细粒度几何细节的表示,包括边缘、曲线和字符局部结构。这对于车牌字符检测尤其有益,因为目标小、密集排列,并且仅在细微几何变化上有所差异。因此,所提出的颈部不仅执行特征融合,还在为检测头生成更具判别性的表示方面发挥着关键作用。

C. 检测头

检测头接收来自颈部的融合特征图,并预测字符位置和类别标签。我们没有采用复杂的多级结构,而是采用单级检测头,这适用于车牌字符检测,因为经过车牌裁剪或归一化后,字符尺度相对一致。这种设计简化了预测并降低了计算成本。所提出的检测头采用无锚框范式,其中每个空间位置对应一个候选预测,消除了预定义锚框的需求。此外,端到端的预测机制减少了中间后处理步骤,如图4所示。为进一步提高效率,我们设置reg_max=1,实现直接边界框回归,而非基于分布的回归。

总体而言,单级、无锚框和直接回归的设计使检测头保持紧凑,同时为车牌字符检测提供了足够的能力。

IV. 结果

A. 数据集与实验设置

本研究聚焦于车牌字符检测,并在 UFPR-ALPR 数据集 [14] 上进行评估。该数据集包含 4,500 张完全标注的图像,涵盖从 150 辆车在真实世界条件下采集的超过 30,000 个车牌字符。数据划分为 1,800 张训练图像、900 张验证图像和 1,800 张测试图像。车牌在颜色和尺寸上存在差异,字符集包括对应于字母数字符号的 36 个类别。

除非另有说明,所有实验均使用 Ultralytics 框架¹ 和默认超参数进行。输入图像分辨率设为 128×128,模型使用 MuSGD 优化器 [9],[26] 训练 100 个轮次,批量大小为 16。所有实验均在配备 NVIDIA RTX 4090 GPU 和 Intel(R) Core(TM) i9-10900X CPU 的系统上执行。

B. 定量比较

为评估 MicroCharNet 的有效性,我们将其与多个 YOLO 变体 [4]–[9] 和 CTM 模型 [10] 进行了比较。为了公平的轻量级比较,我们使用了每个 YOLO 变体可用的最小官方配置。所有模型均在 Ultralytics 框架内使用相同的实验设置进行训练,包括输入尺寸 128×128、MuSGD 优化器以及相同的训练超参数。推理和后处理延迟在 CPU 上测量,以更好地反映在资源受限设备上的实际部署情况。

如表 I 所示,MicroCharNet 在检测精度和计算效率之间取得了良好的平衡。在验证集上,所提出的模型取得了最高的 mAP@50,达到 0.83,并达到 0.52 的 mAP@50-95,与表现最佳的基线相当。在测试集上,MicroCharNet 取得了最高的 mAP@50,达到 0.85,而其 0.51 的 mAP@50-95 与最强的 YOLO 基线相当。这些结果表明,所提出的轻量级架构保持了有竞争力的检测性能,尤其是在 mAP@50 指标下,同时在更严格的 mAP@50-95 评估下也保持了鲁棒性。

在效率方面,MicroCharNet 相比所有对比方法显示出明显优势。它仅需 0.08M 参数和 0.096 GFLOPs,这远低于 YOLO 变体和 CTM。尽管模型尺寸小得多,但它实现了最快的推理时间 1.023 ms。此外,其后处理时间是所有方法中最低的。与 YOLOv10 [6] 和 YOLO26 [9] 等其他无 NMS 检测器相比,MicroCharNet 通过使用单级检测头和更少的预测候选,进一步减少了后处理延迟。

总体而言,结果表明,任务特定且轻量级的架构不仅能显著降低模型复杂度,还能在精度和效率上超越更大的通用检测模型。这证实了 MicroCharNet 在资源受限设备上进行实时车牌字符检测的有效性。

C. 定性结果

UFPR-ALPR 测试集上的定性结果如图 5 所示。具体来说,图 5a 展示了 MicroCharNet 的几个成功检测示例,展示了其在各种条件下(包括旋转车牌、颜色变化和不同光照环境)准确检测汽车和摩托车车牌上字符的能力。这些结果表明,所提出的模型对现实场景中常见的几何和光度变化具有鲁棒性。

除成功案例外,图 5b 还展示了几个模型失败或产生次优预测的具有挑战性的示例。特别是,在强光反射、模糊或低质量车牌以及极端旋转等不利条件下,性能会下降。这些示例表明,尽管所提出的模型在大多数情况下表现良好,但处理严重的图像退化和极端几何变化仍是未来工作的挑战。

D. 消融研究

  1. 不同组件的影响:表 II 显示,MicroCharNet 中的每个组件都对最终的精度-效率权衡有所贡献。移除 CoordAtt 导致 mAP@50 下降最大,强调了空间信息建模对小字符检测的重要性。替换 C3k2 或 C2f 会增加参数和 GFLOPs,但未带来明确的整体增益,证实了所提出的轻量级特征提取设计的有效性。关闭端到端设置也会增加复杂度并降低精度。尽管多尺度变体取得了略好的检测性能,但它引入了额外的参数和计算。因此,所提出的单尺度设计提供了更优的精度-效率平衡。

  1. 不同分辨率:表 III 显示了输入分辨率对精度-效率权衡的影响。将输入尺寸从 96×96 增加到 128×128 显著提升了检测性能,而进一步增大分辨率仅带来 mAP@50-95 的微小增益。相比之下,GFLOPs 和延迟随输入尺寸持续增加,尤其是在 CPU 上。这些结果表明,对于所提出的模型,128×128 在检测精度和计算效率之间提供了最有利的平衡。

E. 部署

为了评估 MicroCharNet 在智能摄像头场景中的实际适用性,我们直接将模型部署在嵌入式计算设备上。具体来说,表 IV 报告了在 Jetson Nano 和 Orange Pi 5 Plus 上的推理延迟和 CPU 使用率。对于 CPU 推理,模型使用 ONNX Runtime 执行。对于硬件加速,Jetson Nano GPU 使用 TensorRT,而 Orange Pi 5 Plus NPU 使用 RKNN。

结果表明,MicroCharNet 可以在两种设备上实现实时推理。在 Jetson Nano 上,使用 TensorRT 的 GPU 加速将延迟从 21.1 ms 显著降低至 3.0 ms,同时将 CPU 使用率从 100% 降至 1.4%。在 Orange Pi 5 Plus 上,基于 ONNX 的 CPU 推理实现了 3.9 ms 的延迟。相比之下,基于 RKNN 的 NPU 推理获得了 5.4 ms 的延迟,且 CPU 使用率低得多。尽管 NPU 延迟略高于 CPU 推理,但它显著降低了 CPU 负载,这对于需要并行运行多个任务的智能摄像头系统是有益的。这些结果表明,MicroCharNet 适用于嵌入式平台上的实时车牌字符检测。

V. 讨论与结论

A. 局限性

尽管 MicroCharNet 具有强大的性能和效率,但它仍存在一些局限性。首先,该模型在具有挑战性的成像条件下可能表现不佳,例如显著的光照变化、模糊、低质量输入和高倾斜车牌。一些代表性的失败案例见图 5b。其次,评估是在裁剪后的车牌图像上进行的,其中背景噪声相对有限。因此,MicroCharNet 在全场景图像和无约束真实世界环境中的性能有待进一步研究。

B. 结论

在本文中,我们提出了 MicroCharNet,一种用于车牌字符检测的超轻量级模型。所提出的设计能够为小尺寸且密集分布的字符进行有效的特征提取,同时保持高度高效的架构。在 UFPR-ALPR 数据集上的实验结果表明,MicroCharNet 在显著降低模型复杂度的同时,取得了最先进的性能。特别是,所提出的模型仅以 0.08M 参数和 0.096 GFLOPs 实现了卓越的检测精度,同时在所有对比方法中实现了最快的推理速度。此外,在多个硬件平台上进行的实验进一步证实了所提出方法在实际部署场景中的效率。总体而言,结果表明,精心设计的轻量级架构能够有效平衡精度和效率,甚至能够超越更大的通用检测模型。因此,MicroCharNet 为实时车牌字符检测提供了一种实用且有效的解决方案,尤其是在资源受限的设备上。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 0.原理介绍
  • I. 引言
  • II. 相关工作
  • III. 方法
    • A. 骨干网络
    • B. 颈部
    • C. 检测头
  • IV. 结果
    • A. 数据集与实验设置
    • B. 定量比较
    • C. 定性结果
    • E. 部署
  • V. 讨论与结论
    • A. 局限性
    • B. 结论
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档