当前位置: 首页 > news >正文

yolov26改进 | Neck/颈部改进篇 | CVPR最新低照度图像增强模块HVI改进YOLOv26(有效涨点)

开始讲解之前推荐一下我的专栏,本专栏的内容支持(分类、检测、分割、追踪、关键点检测),专栏目前为限时折扣,欢迎大家订阅本专栏,本专栏每周更新5-7篇最新机制,更有包含我所有改进的文件和交流群提供给大家,本人定期在群内分享发表论文方法和经验。


一、本文介绍

本文给大家带来的最新改进机制是CVPR顶会中的一种新型颜色空间HVI机制针对低照度图像增强任务中的红色区域断裂和暗区噪声问题。HVI通过极化映射重构色相表示,解决HSV中红色不连续问题,并引入可学习的强度塌缩机制稳定暗区几何分布。核心设计包括:1) 极坐标化HS平面消除红色断裂;2) 自适应暗区压缩抑制噪声。本文内容可以无缝插入到任何的机制中实现二次创新,同时本文将其集成在YOLOv26的Neck中实现有效涨点。

欢迎大家订阅我的专栏一起学习YOLO,购买专栏读者联系读者入群获取进阶项目文件!

(文字学不会的读者,作者可提供视频学习方法.)

专栏链接:YOLOv26有效涨点专栏包含:Conv、注意力机制、主干/Backbone、损失函数、优化器、后处理等改进机制


目录

一、本文介绍

二、原理介绍

三、核心代码

四、添加方法

4.1 修改一

4.2 修改二

4.3 修改三

4.4 修改四

五、正式训练

5.1 yaml文件

5.2 训练代码

5.3 训练过程截图

五、本文总结


二、原理介绍

论文链接:官方论文链接点击此处即可跳转

代码链接:官方代码链接点击此处即可跳转

HVI 不是一个普通的网络层,而是文章最核心的“颜色空间模块”。它的目标不是直接堆叠更复杂的网络,而是先从表示空间上解决低照度增强里最棘手的两个问题:红色区域的不连续噪声极暗区域的黑平面噪声。作者认为,很多方法在 sRGB 空间里做增强时,亮度和颜色强耦合,容易出现颜色偏移;即便换到 HSV,虽然亮度与颜色解耦了,但又会引入新的伪影。因此,HVI 的本质就是:在 HSV 的基础上重新组织色彩表示,让“相似颜色在空间里更接近”,尤其让红色更连续、暗区更稳定。

从文章的逻辑来看,HVI 可以概括为两个关键设计:极坐标化的 HS 平面可学习的强度塌缩机制。作者先沿用 HSV 的优点,把亮度信息从颜色中分离出来,其中亮度并不是靠网络去猜,而是依据 Max-RGB 理论直接由输入图像得到强度图。随后,作者指出 HSV 的色相轴在红色处存在首尾断裂:红色既出现在 (h=0) 也出现在 (h=6),这会让原本相近的红色点在颜色空间里被拉得很远,增强后就容易在红色区域产生斑点和断裂伪影。为了解决这个问题,文章把 Hue 进行极化映射,用正交的水平/垂直坐标来表示色相方向,从而把原先“断开的红色两端”重新接起来。

与红色断裂问题对应的第二个核心设计,是 HVI 对极暗区域的处理。作者发现,HSV 在非常暗的区域会形成所谓的black plane noise,也就是黑色附近的颜色点分布很散,增强时特别容易把暗区噪声当成真实纹理一起放大。为此,HVI 引入了一个可训练的 darkness density 参数 (k),并通过一个基于亮度的intensity collapse function去自适应压缩低亮度区域的半径。直观理解就是:亮度很低时,把这些颜色点往中心“收拢”;亮度逐渐升高时,再让颜色分布逐渐展开。这样一来,暗区中的杂乱噪声不会被无约束地放大,而是先被压缩到更稳定、更紧凑的表示区域里。最终,作者用,再与组合成 HVI 表示。也就是说,HVI 最终由三个部分组成:横向色彩分量、纵向色彩分量和强度分量。

如果配合图片来理解,图1(第2页)是最重要的总览图。图中上排展示了从sRGB → HSV → HVI的变化过程,下排则展示了相应增强结果。作者想表达的非常清楚:在 sRGB 中,亮度与颜色耦合太强,所以增强后容易整体偏色;换到 HSV 后,亮度恢复变得更自然,但红色区域会出现明显的不连续噪声,黑色附近也会产生伪影;进一步变成 HVI 后,红色区域的连续性恢复了,黑色平面也被压缩,最终输出图像在颜色自然性和亮度稳定性上都更好。图1其实就是整篇论文的核心思想图:HVI 不是简单换颜色空间,而是在 HSV 的“色相结构”和“暗区几何”上做了针对低照度任务的重构。

图2(第5页)进一步说明了 HVI 在整个方法中的位置。这里可以看出,HVI 不是独立存在的,它是后续 CIDNet 的前端表示基础。作者先做HVI Transformation,把输入图像分解成HV color mapintensity map;然后送入一个双分支增强网络,其中HV-branch 负责暗区去噪与色彩恢复,I-branch 负责整体照明估计与亮度提升;最后再通过Perceptual-inverse HVI Transformation (PHVIT)映射回 sRGB。换句话说,HVI 的价值不只是“表示更好”,而是它把低照度增强自然拆成了两件事:颜色/结构处理和亮度处理。这也是为什么文章后面专门设计双分支网络去匹配 HVI 的解耦特性。

图5(第8页)是最能说明 HVI 两个子设计分别在做什么的消融图。作者分别比较了 sRGB、HSV、只加极化、只加、完整 HVI 的结果。图中可以看到:只用 HSV 时,红色区域会出现明显黑点和断裂;只加 polarization 时,红色连续性有所改善,但暗区问题仍未彻底解决;只加时,亮度关系更稳,但颜色仍可能混淆,甚至在其他区域引入色偏;而当polarization 和同时使用时,红色斑点和暗区伪影都得到明显抑制。对应的消融结果里,完整 HVI-CIDNet 在 LOLv2-Real 上取得了24.111 PSNR / 0.871 SSIM / 0.108 LPIPS,优于 sRGB、HSV 以及各单独组件版本。这说明 HVI 的两个设计并不是可替代关系,而是互补关系:一个负责“修色相拓扑”,一个负责“稳暗区几何”。

补充材料里的图7、图8、图9(第12页)对参数 (k) 的作用解释得很直观。图7画出了不同 (k) 取值下 (C_k) 随强度变化的曲线,说明 (C_k) 本质上是一个从低亮度到高亮度的重映射函数;图8展示了不同 (k) 下 HVI 空间外形的变化,可以看到随着 (k) 增大,低亮度部分的“底部几何”逐渐变宽;图9则直接给出低照度图像在不同 (k) 下的 HV-map,可见 (k) 越大,暗区噪声越容易被放大,细节与噪声之间的冲突也越明显。作者因此把 (k) 视为一个在不同数据集、不同网络条件下调节暗区信噪比的重要参数。也就是说,HVI 不是固定死的颜色空间,它带有一定的可学习、自适应属性

如果从“模块创新点”角度来总结,HVI 的关键贡献可以概括成三句话。第一,它抓住了低照度增强里一个常被忽略的问题:颜色空间本身也会制造噪声,尤其是 HSV 的红色断裂和黑平面问题。第二,它不是靠更深的网络硬学,而是先通过polarized HS + learnable intensity collapse让输入表示更适合增强。第三,它不仅自己有效,而且还能作为plug-and-play 的颜色空间变换提升其他 LLIE 方法,论文表3显示,把 HVI 变换接到其他方法前后,多个模型的 PSNR、SSIM、LPIPS 都得到改善,其中 GSAD 的 PSNR 提升达3.562 dB。这说明 HVI 的价值具有一定通用性,而不只是绑定 CIDNet 才有效。

如果你想写成一段比较适合博客或论文模块介绍的表述,可以直接用下面这段:

HVI 是针对低照度图像增强任务设计的一种新型颜色空间,其核心思想是在 HSV 的基础上进一步重构色彩表示方式,以同时抑制红色不连续噪声和极暗区域黑平面噪声。具体而言,HVI 通过对 Hue 进行极化映射,将原本在 HSV 中首尾断裂的红色区域投影到连续的水平—垂直平面中,从而缩小相似红色之间的欧氏距离;同时,引入由可训练参数 (k) 控制的强度塌缩函数,对低亮度区域的颜色半径进行自适应压缩,使暗区噪声在增强前得到更稳定的几何约束。最终,HVI 以 (\hat H)、(\hat V) 和 (I) 三个分量共同表征图像,不仅保留了亮度与颜色解耦的优点,还显著提升了低照度增强过程中的颜色自然性、暗区稳定性和整体视觉质量。


三、核心代码

核心代码的使用方式看章节四

from einops import rearrange import torch import torch.nn as nn import torch.nn.functional as F import math __all__ = ['HVI'] class LayerNorm(nn.Module): r""" LayerNorm that supports two data formats: channels_last (default) or channels_first. The ordering of the dimensions in the inputs. channels_last corresponds to inputs with shape (batch_size, height, width, channels) while channels_first corresponds to inputs with shape (batch_size, channels, height, width). """ def __init__(self, normalized_shape, eps=1e-6, data_format="channels_first"): super().__init__() self.weight = nn.Parameter(torch.ones(normalized_shape)) self.bias = nn.Parameter(torch.zeros(normalized_shape)) self.eps = eps self.data_format = data_format if self.data_format not in ["channels_last", "channels_first"]: raise NotImplementedError self.normalized_shape = (normalized_shape,) def forward(self, x): if self.data_format == "channels_last": return F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps) elif self.data_format == "channels_first": u = x.mean(1, keepdim=True) s = (x - u).pow(2).mean(1, keepdim=True) x = (x - u) / torch.sqrt(s + self.eps) x = self.weight[:, None, None] * x + self.bias[:, None, None] return x class NormDownsample(nn.Module): def __init__(self, in_ch, out_ch, scale=0.5, use_norm=False): super(NormDownsample, self).__init__() self.use_norm = use_norm if self.use_norm: self.norm = LayerNorm(out_ch) self.prelu = nn.PReLU() self.down = nn.Sequential( nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=False), nn.UpsamplingBilinear2d(scale_factor=scale)) def forward(self, x): x = self.down(x) x = self.prelu(x) if self.use_norm: x = self.norm(x) return x else: return x class NormUpsample(nn.Module): def __init__(self, in_ch, out_ch, scale=2, use_norm=False): super(NormUpsample, self).__init__() self.use_norm = use_norm if self.use_norm: self.norm = LayerNorm(out_ch) self.prelu = nn.PReLU() self.up_scale = nn.Sequential( nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=False), nn.UpsamplingBilinear2d(scale_factor=scale)) self.up = nn.Conv2d(out_ch * 2, out_ch, kernel_size=1, stride=1, padding=0, bias=False) def forward(self, x, y): x = self.up_scale(x) x = torch.cat([x, y], dim=1) x = self.up(x) x = self.prelu(x) if self.use_norm: return self.norm(x) else: return x # Cross Attention Block class CAB(nn.Module): def __init__(self, dim, num_heads, bias): super(CAB, self).__init__() self.num_heads = num_heads self.temperature = nn.Parameter(torch.ones(num_heads, 1, 1)) self.q = nn.Conv2d(dim, dim, kernel_size=1, bias=bias) self.q_dwconv = nn.Conv2d(dim, dim, kernel_size=3, stride=1, padding=1, groups=dim, bias=bias) self.kv = nn.Conv2d(dim, dim * 2, kernel_size=1, bias=bias) self.kv_dwconv = nn.Conv2d(dim * 2, dim * 2, kernel_size=3, stride=1, padding=1, groups=dim * 2, bias=bias) self.project_out = nn.Conv2d(dim, dim, kernel_size=1, bias=bias) def forward(self, x, y): b, c, h, w = x.shape q = self.q_dwconv(self.q(x)) kv = self.kv_dwconv(self.kv(y)) k, v = kv.chunk(2, dim=1) q = rearrange(q, 'b (head c) h w -> b head c (h w)', head=self.num_heads) k = rearrange(k, 'b (head c) h w -> b head c (h w)', head=self.num_heads) v = rearrange(v, 'b (head c) h w -> b head c (h w)', head=self.num_heads) q = torch.nn.functional.normalize(q, dim=-1) k = torch.nn.functional.normalize(k, dim=-1) attn = (q @ k.transpose(-2, -1)) * self.temperature attn = nn.functional.softmax(attn, dim=-1) out = (attn @ v) out = rearrange(out, 'b head c (h w) -> b (head c) h w', head=self.num_heads, h=h, w=w) out = self.project_out(out) return out def autopad(k, p=None, d=1): # kernel, padding, dilation """Pad to 'same' shape outputs.""" if d > 1: k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size if p is None: p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad return p class Conv(nn.Module): """Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation).""" default_act = nn.SiLU() # default activation def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True): """Initialize Conv layer with given arguments including activation.""" super().__init__() self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False) self.bn = nn.BatchNorm2d(c2) self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity() def forward(self, x): """Apply convolution, batch normalization and activation to input tensor.""" return self.act(self.bn(self.conv(x))) def forward_fuse(self, x): """Perform transposed convolution of 2D data.""" return self.act(self.conv(x)) # Intensity Enhancement Layer class IEL(nn.Module): def __init__(self, dim, ffn_expansion_factor=2.66, bias=False): super(IEL, self).__init__() hidden_features = int(dim * ffn_expansion_factor) self.project_in = nn.Conv2d(dim, hidden_features * 2, kernel_size=1, bias=bias) self.dwconv = nn.Conv2d(hidden_features * 2, hidden_features * 2, kernel_size=3, stride=1, padding=1, groups=hidden_features * 2, bias=bias) self.dwconv1 = nn.Conv2d(hidden_features, hidden_features, kernel_size=3, stride=1, padding=1, groups=hidden_features, bias=bias) self.dwconv2 = nn.Conv2d(hidden_features, hidden_features, kernel_size=3, stride=1, padding=1, groups=hidden_features, bias=bias) self.project_out = nn.Conv2d(hidden_features, dim, kernel_size=1, bias=bias) self.Tanh = nn.Tanh() def forward(self, x): x = self.project_in(x) x1, x2 = self.dwconv(x).chunk(2, dim=1) x1 = self.Tanh(self.dwconv1(x1)) + x1 x2 = self.Tanh(self.dwconv2(x2)) + x2 x = x1 * x2 x = self.project_out(x) return x class HVI(nn.Module): def __init__(self, channels_in, channels_mid, heads=8, use_bias=False): super().__init__() c_a, c_b = channels_in self.align_a = self._make_proj(c_a, channels_mid, use_bias) self.align_b = self._make_proj(c_b, channels_mid, use_bias) self.pre_norm = LayerNorm(channels_mid) self.cross_interact = CAB(channels_mid, heads, bias=use_bias) self.detail_refine = IEL(channels_mid) @staticmethod def _make_proj(cin, cout, use_bias): if cin == cout: return nn.Identity() return Conv(cin, cout, 1) def forward(self, feats): feat_a, feat_b = feats feat_a = self.align_a(feat_a) feat_b = self.align_b(feat_b) norm_a = self.pre_norm(feat_a) norm_b = self.pre_norm(feat_b) feat_a = feat_a + self.cross_interact(norm_a, norm_b) feat_a = feat_a + self.detail_refine(self.pre_norm(feat_a)) return feat_a

四、添加方法

4.1 修改一

第一还是建立文件,我们找到如下ultralytics/nn文件夹下建立一个目录名字呢就是'Addmodules'文件夹(用群内的文件的话已经有了无需新建)!然后在其内部建立一个新的py文件将核心代码复制粘贴进去即可。


4.2 修改二

第二步我们在该目录下创建一个新的py文件名字为'__init__.py'(用群内的文件的话已经有了无需新建),然后在其内部导入我们的检测头如下图所示。


4.3 修改三

第三步找到如下文件'ultralytics/nn/tasks.py'进行导入和注册我们的模块(用群内的文件的话已经有了无需重新导入直接开始第四步即可)


4.4 修改四

找到文件到如下文件'ultralytics/nn/tasks.py',在其中的parse_model方法中添加即可(根据周围代码进行定位即可,如果不会入群内有视频讲解)。

# ------------------------------HVI-------------------------------- elif m is HVI: c1 = [ch[x] for x in f] if c2 != nc: c2 = make_divisible(min(args[0],max_channels)*width, 8) args = [c1, c2] # ------------------------------HVI--------------------------------

​​​


到此就修改完成了,大家可以复制下面的yaml文件运行,

如果不会添加可联系作者入群观看视频教程。


五、正式训练


5.1 yaml文件

训练信息:YOLO26-Neck-HVI summary: 318 layers, 3,808,932 parameters, 3,808,932 gradients, 8.6 GFLOPs

使用方式:在代码中进行了注释大家需要仔细观看.

# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license # Ultralytics YOLO26 object detection model with P3/8 - P5/32 outputs # Model docs: https://docs.ultralytics.com/models/yolo26 # Task docs: https://docs.ultralytics.com/tasks/detect # Parameters nc: 80 # number of classes end2end: True # whether to use end-to-end mode reg_max: 1 # DFL bins scales: # model compound scaling constants, i.e. 'model=yolo26n.yaml' will call yolo26.yaml with scale 'n' # [depth, width, max_channels] n: [0.50, 0.25, 1024] # summary: 260 layers, 2,572,280 parameters, 2,572,280 gradients, 6.1 GFLOPs s: [0.50, 0.50, 1024] # summary: 260 layers, 10,009,784 parameters, 10,009,784 gradients, 22.8 GFLOPs m: [0.50, 1.00, 512] # summary: 280 layers, 21,896,248 parameters, 21,896,248 gradients, 75.4 GFLOPs l: [1.00, 1.00, 512] # summary: 392 layers, 26,299,704 parameters, 26,299,704 gradients, 93.8 GFLOPs x: [1.00, 1.50, 512] # summary: 392 layers, 58,993,368 parameters, 58,993,368 gradients, 209.5 GFLOPs # YOLO26n backbone backbone: # [from, repeats, module, args] - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 2, C3k2, [256, False, 0.25]] - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 2, C3k2, [512, False, 0.25]] - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 2, C3k2, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 2, C3k2, [1024, True]] - [-1, 1, SPPF, [1024, 5, 3, True]] # 9 - [-1, 2, C2PSA, [1024]] # 10 # YOLO26n head head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 6], 1, HVI, [512]] # cat backbone P4 - [-1, 2, C3k2, [512, True]] # 13 - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 4], 1, HVI, [256]] # cat backbone P3 - [-1, 2, C3k2, [256, True]] # 16 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] - [[-1, 13], 1, HVI, [512]] # cat head P4 - [-1, 2, C3k2, [512, True]] # 19 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] - [[-1, 10], 1, HVI, [1024]] # cat head P5 - [-1, 2, C3k2, [1024, True, 0.5, True]] # 22 (P5/32-large) - [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)

5.2 训练代码

大家可以创建一个py文件将我给的代码复制粘贴进去,配置好自己的文件路径即可运行。

import warnings warnings.filterwarnings('ignore') from ultralytics import YOLO if __name__ == '__main__': model = YOLO('替换你的模型配置文件yaml文件地址') # 如何切换模型版本, 上面的ymal文件可以改为 yolov11s.yaml就是使用的v11s, # 类似某个改进的yaml文件名称为yolov11-XXX.yaml那么如果想使用其它版本就把上面的名称改为yolov11l-XXX.yaml即可(改的是上面YOLO中间的名字不是配置文件的)! # model.load('yolo11n.pt') # 是否加载预训练权重,科研不建议大家加载否则很难提升精度 model.train(data=r"替换你的数据集配置文件地址", # 如果大家任务是其它的'ultralytics/cfg/default.yaml'找到这里修改task可以改成detect, segment, classify, pose cache=False, imgsz=640, epochs=150, single_cls=False, # 是否是单类别检测 batch=16, close_mosaic=0, workers=0, device='0', optimizer='SGD', # using SGD # resume='runs/train/exp21/weights/last.pt', # 如过想续训就设置last.pt的地址 amp=False, # 如果出现训练损失为Nan可以关闭amp project='runs/train', name='exp', )

5.3 训练过程截图

​​


五、本文总结

到此本文的正式分享内容就结束了,在这里给大家推荐我的YOLOv26改进有效涨点专栏,本专栏目前为新开的平均质量分98分,后期我会根据各种最新的前沿顶会进行论文复现,也会对一些老的改进机制进行补充,如果大家觉得本文帮助到你了,订阅本专栏,关注后续更多的更新~

专栏链接:

http://www.cnnetsun.cn/news/2913072.html

相关文章:

  • TO-39封装红外测温传感器怎么选?深度对比MLX90614与国产GD60914系列(含5° FOV进灰问题解决)
  • 不止于Vue:用200字节的mitt库,搞定React/原生JS项目中的事件管理
  • 从广播到对讲机:拆解生活中FM与PM调制的真实应用场景与硬件选型
  • 3毛钱的国产RS485芯片,真能省掉TVS和偏置电阻?实测CS48505S在工业板卡上的表现
  • 2026年论文党必备:盘点2026年标杆级的AI论文平台
  • PyQt5界面代码维护指南:.ui文件 vs 纯Python代码,哪种方式更适合你的项目?
  • 5个常见问题解决指南:Windows版Mesa3D图形驱动安装与故障排除
  • 从PyTorch转Rust?tch-rs、Candle、Burn、DFDX四大框架实战对比与选型指南
  • 终极指南:如何免费激活Adobe全家桶软件(2019-2023全版本)
  • PY32F002A vs PY32F003 vs PY32F030:手把手教你根据项目需求选对普冉M0+ MCU
  • AList项目易主后,我的私人云存储方案还安全吗?聊聊替代方案与数据安全实践
  • 工资信息管理系统毕业设计源码
  • 告别充电焦虑:一文看懂CCS、CHAdeMO和国标GB/T的充电枪与协议区别(2024版)
  • 校园健康驿站管理系统毕业设计
  • Java SpringBoot+Vue3+MyBatis WEB旅游推荐系统系统源码|前后端分离+MySQL数据库
  • Unlock-Music终极指南:3步解锁加密音乐,让音乐自由播放
  • AWQ vs GPTQ vs BitsAndBytes:给LLM‘瘦身’,选哪个?一张表讲清楚差异和选型
  • 别再死记硬背了!手把手教你读懂FPGA DDR4芯片型号(以MT40A512M8RH为例)
  • 从RDD到DataFrame:Spark老手教你如何优雅地“升级”你的数据处理代码(性能对比实测)
  • 从《炉石传说》到在线购物:AgentBench如何用8个‘奇葩’场景,测出大模型的真实智商?
  • 深入对比:AXI4、AXI4-Lite和AXI4-Stream到底该怎么选?一张表帮你搞定
  • 别再纠结SVC和LinearSVC了!用sklearn做文本分类,我为什么最终选了LinearSVC?
  • 从开源SIP电话项目看选型:STM32F429、ESP32与AT32,实战中怎么选?
  • 经典问题——验证栈序列
  • AD9854 vs AD9959 vs AD9910:三款热门DDS芯片怎么选?从带宽、接口到代码差异全解析
  • 国产磁编码器MT6816实测:与AS5048对比,在电机控制中的精度与稳定性如何?
  • 给嵌入式新人的AMBA总线扫盲:AHB、APB、AXI到底该怎么选?
  • 从MC1496到三极管:手把手教你用频谱分析仪实测两种混频器性能差异
  • 告别‘一锅炖’:快速热退火(RTA)和激光退火,怎么选才不踩坑?
  • 射频工程师的“速算宝典”:dBm与mW快速心算转换表与实战估算技巧