Detección de Defectos en Paneles Fotovoltaicos con YOLOv10: Integración de Fusión Híbrida Guiada por Atención de Contenido (CGA)

La detección automatizada de defectos en paneles fotovoltaicos es un campo crucial para optimizar la eficiencia y la longevidad de las instalaciones de energía solar. Este artículo detalla mejoras sustanciales al algoritmo de detección de objetos YOLOv10, específicamente en el contexto de la inspección de paneles solares, mediante la introducción de una novedosa fusión híbrida basada en atención guiada por contenido (CGA), que ha demostrado aumentar significativamente la precisión de detección.

YOLOv10: Fundamentos y Optimización

YOLOv10, la evolución más reciente de la serie YOLO, se ha consolidado como un referente en la detección de objetos en tiempo real, destacando por su equilibrio entre la carga computacional y el rendimiento de detección. El desarrollo de YOLOv10 aborda dos limitaciones principales de sus predecesores, que son esenciales para el despliegue de sistemas de visión artificial:

  1. La dependencia de la supresión no máxima (NMS) en el post-procesamiento, un factor que entroduce latencia y complica la inferencia de extremo a extremo.
  2. La redundancia computacional intrínseca en el diseño de los componentes del modelo, que limita la eficiencia general y el potencial de mejora del rendimiento.

Para superar estos desafíos, YOLOv10 introduce un enfoque de entrenamiento sin NMS mediante una asignación dual persistente, junto con una estrategia de diseño de modelo holística que optimiza la eficiencia y la precisión en todos sus componentes. Estos avances resultan en una arquitectura que no solo reduce los costos computacionales, sino que también ofrece un rendimiento superior, estableciendo nuevos estándares en la detección de objetos en tiempo real.

Bloque Invertido Compacto (CIB) y su Aplicación en C2f

Para mitigar la complejidad en las etapas del modelo que pueden resultar redundantes, se ha propuesto el Bloque Invertido Compacto (CIB). Este diseño arquitectónico innovador emplea convoluciones separables en profundidad (depthwise separable convolutions) para la mezcla espacial y convoluciones 1x1 (pointwise convolutions) para la mezcla de canales, resultando en una estructura más eficiente.

El módulo C2fCompactInvertedBlock es una implementación que integra la estructura CIB, sustituyendo el cuello de botella estándar dentro del bloque C2f, un componente común en arquitecturas como YOLOv8.

A continuación, se presenta la implementación modular:

import torch
import torch.nn as nn

# Definición de una convolución estándar para uso auxiliar
class StandardConv(nn.Module):
    def __init__(self, in_ch, out_ch, kernel_s=3, stride_s=1, pad=None, grps=1, use_bias=False, apply_act=True):
        super().__init__()
        if pad is None:
            pad = kernel_s // 2 if kernel_s > 1 else 0
        self.conv_layer = nn.Conv2d(in_ch, out_ch, kernel_s, stride_s, pad, groups=grps, bias=use_bias)
        self.batch_norm = nn.BatchNorm2d(out_ch)
        self.activation = nn.SiLU() if apply_act else nn.Identity()

    def forward(self, x_input):
        return self.activation(self.batch_norm(self.conv_layer(x_input)))

# Placeholder para RepVGGDW, asumiendo una implementación existente
class CustomRepVGGDW(nn.Module):
    def __init__(self, channels_in):
        super().__init__()
        self.dw_conv = StandardConv(channels_in, channels_in, 3, groups=channels_in, apply_act=False)
    def forward(self, input_data):
        return self.dw_conv(input_data)

class CompactInvertedBlock(nn.Module):
    """Implementa un bloque invertido con diseño compacto."""
    def __init__(self, ch_in, ch_out, res_connection=True, exp_ratio=0.5, use_repvggdw=False):
        super().__init__()
        hidden_ch = int(ch_out * exp_ratio)
        self.seq_conv = nn.Sequential(
            StandardConv(ch_in, ch_in, 3, grps=ch_in), # Convolución depthwise
            StandardConv(ch_in, 2 * hidden_ch, 1), # Convolución pointwise
            StandardConv(2 * hidden_ch, 2 * hidden_ch, 3, grps=2 * hidden_ch) if not use_repvggdw else CustomRepVGGDW(2 * hidden_ch),
            StandardConv(2 * hidden_ch, ch_out, 1), # Convolución pointwise
            StandardConv(ch_out, ch_out, 3, grps=ch_out), # Convolución depthwise
        )
        self.add_shortcut = res_connection and ch_in == ch_out

    def forward(self, data_in):
        return data_in + self.seq_conv(data_in) if self.add_shortcut else self.seq_conv(data_in)

class C2fBaseBlock(nn.Module):
    """Base para la implementación de C2f."""
    def __init__(self, ch_in_c2f, ch_out_c2f, num_blocks=1, shortcut_c2f=False, grps_c2f=1, exp_c2f=0.5):
        super().__init__()
        self.mid_ch_c2f = int(ch_out_c2f * exp_c2f)
        self.conv_initial = StandardConv(ch_in_c2f, 2 * self.mid_ch_c2f, 1, 1)
        self.conv_final = StandardConv(2 * self.mid_ch_c2f, ch_out_c2f, 1)
        self.block_modules = nn.ModuleList(
            nn.Sequential(
                StandardConv(self.mid_ch_c2f, self.mid_ch_c2f, 3, 1, grps=grps_c2f),
                StandardConv(self.mid_ch_c2f, self.mid_ch_c2f, 1, 1)
            ) for _ in range(num_blocks)
        )

    def forward(self, x_tensor):
        feature_chunks = list(self.conv_initial(x_tensor).chunk(2, 1))
        feature_chunks.extend(m(feature_chunks[-1]) for m in self.block_modules)
        return self.conv_final(torch.cat(feature_chunks, 1))

class C2fCompactInvertedBlock(C2fBaseBlock):
    """C2f con bloques internos CompactInvertedBlock."""
    def __init__(self, ch_in_c2f, ch_out_c2f, num_blocks=1, shortcut_c2f=False, use_repvggdw_in_cib=False, grps_c2f=1, exp_c2f=0.5):
        super().__init__(ch_in_c2f, ch_out_c2f, num_blocks, shortcut_c2f, grps_c2f, exp_c2f)
        self_mid_ch = int(ch_out_c2f * exp_c2f) # Recalcular mid_ch para el uso en CIB
        # Reemplaza los módulos bottleneck internos de C2f con CompactInvertedBlock
        self.block_modules = nn.ModuleList(
            CompactInvertedBlock(self_mid_ch, self_mid_ch, shortcut_c2f, exp_ratio=1.0, use_repvggdw=use_repvggdw_in_cib)
            for _ in range(num_blocks)
        )
    # Calcular el número total de canales para qkv
    combined_qkv_channels = self.num_heads * (2 * self.key_dim + self.head_dim)
    self.qkv_projection = StandardConv(feature_dimension, combined_qkv_channels, 1, apply_act=False)
    self.output_projection = StandardConv(feature_dimension, feature_dimension, 1, apply_act=False)
    self.positional_encoding_conv = StandardConv(feature_dimension, feature_dimension, 3, 1, grps=feature_dimension, apply_act=False)

def forward(self, input_features_attn):
    batch_sz, _, img_h, img_w = input_features_attn.shape
    seq_len = img_h * img_w
    
    qkv_output = self.qkv_projection(input_features_attn)
    # Reorganizar y dividir q, k, v
    qkv_reshaped = qkv_output.view(batch_sz, self.num_heads, 2 * self.key_dim + self.head_dim, seq_len)
    q_data, k_data, v_data = qkv_reshaped.split([self.key_dim, self.key_dim, self.head_dim], dim=2)

    attention_scores = (q_data.transpose(-2, -1) @ k_data) * self.scaling_factor
    attention_weights = attention_scores.softmax(dim=-1)
    
    attended_values = (v_data @ attention_weights.transpose(-2, -1)).view(batch_sz, -1, img_h, img_w)
    final_features = attended_values + self.positional_encoding_conv(v_data.reshape(batch_sz, -1, img_h, img_w))
    
    return self.output_projection(final_features)

class ParallelSplitAttentionModule(nn.Module): def init(self, input_ch_psa, output_ch_psa, exp_ratio_psa=0.5): super().init() assert input_ch_psa == output_ch_psa, "Los canales de entrada y salida deben ser idénticos." self.split_ch = int(input_ch_psa * exp_ratio_psa)

    self.conv_split_features = StandardConv(input_ch_psa, 2 * self.split_ch, 1, 1)
    self.conv_recombine_features = StandardConv(2 * self.split_ch, input_ch_psa, 1)
    
    self.attention_submodule = MultiHeadSelfAttentionModule(self.split_ch, attention_ratio=0.5, num_heads_attn=self.split_ch // 64)
    self.feed_forward_network = nn.Sequential(
        StandardConv(self.split_ch, self.split_ch * 2, 1),
        StandardConv(self.split_ch * 2, self.split_ch, 1, apply_act=False)
    )
    
def forward(self, input_tensor_psa):
    # Dividir el mapa de características
    branch_a_features, branch_b_features = self.conv_split_features(input_tensor_psa).split((self.split_ch, self.split_ch), dim=1)
    
    # Aplicar atención y FFN a la rama B
    branch_b_features = branch_b_features + self.attention_submodule(branch_b_features)
    branch_b_features = branch_b_features + self.feed_forward_network(branch_b_features)
    
    # Concatenar y recombinar
    return self.conv_recombine_features(torch.cat((branch_a_features, branch_b_features), 1))
def forward(self, x_scd):
    intermediate_output = self.channel_adjustment_layer(x_scd)
    return self.spatial_reduction_layer(intermediate_output)

Etiquetas: YOLOv10 PyTorch Deep Learning Visión por Computadora Detección de Objetos

Publicado el 6-1 03:07