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:
- 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.
- 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)