Mosaic数据增强介绍

1. 核心概念与目标

Mosaic 是一种在计算机视觉(尤其是目标检测任务)中非常流行且强大的数据增强技术。它最早由 Ultralytics 的 Alexey Bochkovskiy 在 YOLOv4 中提出并推广,后来被广泛应用于 YOLOv5, YOLOv7, YOLOv8 等模型以及其他目标检测框架中。

  • 核心思想: 将 4 张不同的训练图像 按照随机的位置、比例和排列方式(例如 2x2 网格)拼接组合成 1 张新的合成图像

  • 主要目标:

    • 丰富上下文信息: 模型在一张图中同时看到来自 4 个不同场景的对象和背景,学习更鲁棒的特征和上下文关系。

    • 提升小目标检测: 原始图像中的小目标在被缩小后放入合成图像中,可能变得极其微小且密集,迫使模型学习检测更具挑战性的小目标。

    • 增加目标密度: 合成图像通常包含比单张原始图像更多的目标(最多 4 倍),让模型在单次前向传播中看到更多样本,提高训练效率。

    • 模拟遮挡与裁剪: 拼接过程天然地裁剪了原始图像的部分区域,模拟了目标被遮挡或只出现部分的情况。

    • 减少对大批量 (Large Batch Size) 的依赖: 单张合成图像包含的信息量相当于一个小批次 (mini-batch) 的数据,即使物理批大小较小,模型在每次迭代中也能处理更丰富的信息,降低了训练对超大物理批大小的硬件要求。

    • 增强模型鲁棒性: 通过引入大幅度的几何变换(缩放、平移、裁剪)和内容变化,提升模型对各种尺度、位置、背景变化的泛化能力。

2. 具体实现步骤

以下是 Mosaic 增强的典型实现流程:

(1)随机选择 4 张图像: 从训练数据集中随机抽取 4 张原始图像 (img1img2img3img4) 及其对应的标注(边界框 bboxes + 类别 labels)。

(2)定义合成图像尺寸: 确定最终合成图像的目标尺寸 (通常是模型的输入尺寸,如 640x640)。

(3)确定拼接中心点 (随机):

  • 在目标尺寸的宽度 (W) 和高度 (H) 范围内,随机生成一个中心点坐标 (xc, yc)。这个点将作为 4 张图像分割线的交点。
  • xc 通常在 [0.25*W, 0.75*W] 之间随机,yc 通常在 [0.25*H, 0.75*H] 之间随机。这确保了拼接点不会太靠近边缘,从而让每张原始图像都有足够部分被包含进来。

(4)划分 4 个区域: 中心点 (xc, yc) 将目标画布划分为 4 个矩形区域(左上、右上、左下、右下)。

(5)放置并缩放 4 张图像:

  • 左上区域: 放置 img1。根据左上区域的大小 (xc * yc),对 img1 进行缩放(可能放大或缩小),使其填充该区域。将缩放后的 img1 粘贴到画布的 (0, 0) 到 (xc, yc) 区域。
  • 右上区域: 放置 img2。根据右上区域的大小 ((W - xc) * yc),对 img2 进行缩放,使其填充该区域。粘贴到 (xc, 0) 到 (W, yc)
  • 左下区域: 放置 img3。根据左下区域大小 (xc * (H - yc)),缩放 img3,填充该区域。粘贴到 (0, yc) 到 (xc, H)
  • 右下区域: 放置 img4。根据右下区域大小 ((W - xc) * (H - yc)),缩放 img4,填充该区域。粘贴到 (xc, yc) 到 (W, H)

(6)处理边界框标注:

对每张原始图像,应用与其图像相同的缩放比例偏移量来变换其对应的边界框坐标。

  • 缩放: 根据图像被缩放的倍数 (相对于原始尺寸到目标区域尺寸),等比例缩放边界框的 (x, y, w, h) 坐标。
  • 偏移: 根据该图像在合成画布上的起始位置 (x_offset, y_offset),平移边界框的 (x, y) 坐标。
  • 裁剪: 在缩放和偏移后,边界框可能有一部分落在其所在区域之外(被相邻图像覆盖)。需要裁剪掉落在区域外的部分边界框,只保留完全位于其所在区域内部或边界上的部分。如果一个边界框被完全裁剪掉(即没有任何部分留在其所属区域内),则丢弃该标注。

(7)组合标注: 将处理(缩放、偏移、裁剪)后的 4 张图像的边界框和类别标注列表合并,作为这张合成图像的标注。

(8)应用额外增强 (可选但常见): 在 Mosaic 合成之后,通常还会对这张合成图像应用其他标准的数据增强,如:

  • 色彩空间变换 (HSV 色调、饱和度、明度抖动)
  • 随机水平翻转
  • 随机旋转 (角度通常较小)
  • 模糊、噪声等。

3. 视觉示例与代码实现

想象一个 640x640 的画布。随机中心点 (xc=400, yc=300)

  • 左上角 (0:300, 0:400) 区域:缩放并放置一张包含狗的图像。

  • 右上角 (0:300, 400:640) 区域:缩放并放置一张包含汽车和树的图像。

  • 左下角 (300:640, 0:400) 区域:缩放并放置一张包含人和自行车的图像。

  • 右下角 (300:640, 400:640) 区域:缩放并放置一张包含猫和沙发的图像。
    最终得到的合成图像看起来像一张被分成 4 块、内容各异的“马赛克”拼图,每块区域内的目标边界框都根据其位置进行了调整。

import os
import cv2
import numpy as np
import random
from xml.etree import ElementTree as ET
from typing import List, Tuple, Anydef augment_hsv(img: np.ndarray, hgain: float = 0.015, sgain: float = 0.7, vgain: float = 0.4) -> np.ndarray:"""HSV颜色空间增强Args:img: 输入图像 (H, W, C)hgain: 色调增益系数sgain: 饱和度增益系数vgain: 明度增益系数Returns:增强后的图像"""if hgain or sgain or vgain:# 随机增益系数r = np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] + 1# 转换到HSV空间hue, sat, val = cv2.split(cv2.cvtColor(img, cv2.COLOR_BGR2HSV))dtype = img.dtype# 应用随机增益x = np.arange(0, 256, dtype=np.int16)lut_hue = ((x * r[0]) % 180).astype(dtype)lut_sat = np.clip(x * r[1], 0, 255).astype(dtype)lut_val = np.clip(x * r[2], 0, 255).astype(dtype)# 应用查找表img_hsv = cv2.merge((cv2.LUT(hue, lut_hue), (cv2.LUT(sat, lut_sat)), (cv2.LUT(val, lut_val)))img = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR)return imgdef merge_bboxes(bboxes: List[np.ndarray], cutx: int, cuty: int, min_area: int = 10) -> np.ndarray:"""合并并修正边界框Args:bboxes: 四张图像的边界框列表,每张图像的边界框形状为(N, 4)cutx: 横向分割点cuty: 纵向分割点min_area: 最小有效区域阈值Returns:合并后的边界框 (M, 4)"""merged_boxes = []for i, boxes in enumerate(bboxes):for box in boxes:x1, y1, x2, y2 = boxvalid = True# 左上区域if i == 0:# 完全在区域外if x1 > cutx or y1 > cuty:valid = False# 跨越下边界if y2 > cuty and y1 < cuty:y2 = cuty# 跨越右边界if x2 > cutx and x1 < cutx:x2 = cutx# 右上区域elif i == 1:if x2 < cutx or y1 > cuty:valid = Falseif y2 > cuty and y1 < cuty:y2 = cutyif x1 < cutx and x2 > cutx:x1 = cutx# 左下区域elif i == 2:if x1 > cutx or y2 < cuty:valid = Falseif y1 < cuty and y2 > cuty:y1 = cutyif x2 > cutx and x1 < cutx:x2 = cutx# 右下区域elif i == 3:if x2 < cutx or y2 < cuty:valid = Falseif y1 < cuty and y2 > cuty:y1 = cutyif x1 < cutx and x2 > cutx:x1 = cutx# 检查框是否有效if valid:# 确保坐标有效x1, y1 = max(0, x1), max(0, y1)x2, y2 = max(0, x2), max(0, y2)# 检查面积是否足够area = (x2 - x1) * (y2 - y1)if area > min_area:merged_boxes.append([x1, y1, x2, y2])return np.array(merged_boxes) if merged_boxes else np.zeros((0, 4))def mosaic_augmentation(image_paths: List[str], annotation_paths: List[str], input_size: Tuple[int, int] = (416, 416),min_offset: float = 0.2,show_result: bool = False) -> Tuple[np.ndarray, np.ndarray]:"""Mosaic数据增强Args:image_paths: 图像路径列表annotation_paths: 标注路径列表input_size: 输出图像尺寸 (h, w)min_offset: 最小偏移比例show_result: 是否显示结果Returns:mosaic_image: 增强后的图像 (H, W, C)mosaic_boxes: 边界框数组 (M, 4)"""assert len(image_paths) == 4 and len(annotation_paths) == 4, "需要4张图像和4个标注文件"h, w = input_sizemin_offset_x, min_offset_y = min_offset, min_offset# 读取所有图像和标注images = []all_boxes = []for img_path, ann_path in zip(image_paths, annotation_paths):# 读取图像img = cv2.imread(img_path)if img is None:raise FileNotFoundError(f"图像未找到: {img_path}")# 解析XML标注boxes = []tree = ET.parse(ann_path)root = tree.getroot()for obj in root.findall('object'):bndbox = obj.find('bndbox')x1 = int(bndbox.find('xmin').text)y1 = int(bndbox.find('ymin').text)x2 = int(bndbox.find('xmax').text)y2 = int(bndbox.find('ymax').text)boxes.append([x1, y1, x2, y2])images.append(img)all_boxes.append(np.array(boxes))# 随机选择缩放比例scale = random.uniform(0.5, 1.5)# 随机生成分割点cutx = random.randint(int(w * min_offset_x), int(w * (1 - min_offset_x)))cuty = random.randint(int(h * min_offset_y), int(h * (1 - min_offset_y)))# 创建空白画布mosaic_img = np.zeros((h, w, 3), dtype=np.uint8)mosaic_boxes = []# 处理每张图像for i, (img, boxes) in enumerate(zip(images, all_boxes)):ih, iw = img.shape[:2]# 随机缩放图像new_ar = w / hnw = int(scale * iw)nh = int(nw / new_ar)# 调整缩放比例防止过大if nw > w or nh > h:ratio = min(w / nw, h / nh)nw = int(nw * ratio)nh = int(nh * ratio)# 缩放图像img = cv2.resize(img, (nw, nh))# 调整边界框坐标scale_x = nw / iwscale_y = nh / ihif boxes.size > 0:boxes[:, [0, 2]] = boxes[:, [0, 2]] * scale_xboxes[:, [1, 3]] = boxes[:, [1, 3]] * scale_y# 确定图像位置if i == 0:  # 左上x1a, y1a, x2a, y2a = 0, 0, min(nw, cutx), min(nh, cuty)img = img[:y2a, :x2a]if boxes.size > 0:boxes[:, [0, 2]] = boxes[:, [0, 2]]boxes[:, [1, 3]] = boxes[:, [1, 3]]elif i == 1:  # 右上x1a, y1a, x2a, y2a = cutx, 0, w, min(nh, cuty)img = img[:y2a, (x1a - (w - nw)):x2a - (w - nw)]if boxes.size > 0:boxes[:, [0, 2]] = boxes[:, [0, 2]] + w - nwboxes[:, [1, 3]] = boxes[:, [1, 3]]elif i == 2:  # 左下x1a, y1a, x2a, y2a = 0, cuty, min(nw, cutx), himg = img[(y1a - (h - nh)):y2a - (h - nh), :x2a]if boxes.size > 0:boxes[:, [0, 2]] = boxes[:, [0, 2]]boxes[:, [1, 3]] = boxes[:, [1, 3]] + h - nhelse:  # 右下x1a, y1a, x2a, y2a = cutx, cuty, w, himg = img[(y1a - (h - nh)):y2a - (h - nh), (x1a - (w - nw)):x2a - (w - nw)]if boxes.size > 0:boxes[:, [0, 2]] = boxes[:, [0, 2]] + w - nwboxes[:, [1, 3]] = boxes[:, [1, 3]] + h - nh# 将图像放置到mosaic画布上mosaic_img[y1a:y2a, x1a:x2a] = imgmosaic_boxes.append(boxes)# 合并并修正边界框final_boxes = merge_bboxes(mosaic_boxes, cutx, cuty)# 应用HSV增强mosaic_img = augment_hsv(mosaic_img)# 可选:显示结果if show_result:display_img = mosaic_img.copy()for box in final_boxes:x1, y1, x2, y2 = map(int, box)cv2.rectangle(display_img, (x1, y1), (x2, y2), (0, 255, 0), 2)# 绘制分割线cv2.line(display_img, (cutx, 0), (cutx, h), (255, 0, 0), 2)cv2.line(display_img, (0, cuty), (w, cuty), (255, 0, 0), 2)cv2.imshow('Mosaic Augmentation', display_img)cv2.waitKey(0)cv2.destroyAllWindows()return mosaic_img, final_boxesif __name__ == '__main__':# 数据集路径base_dir = 'VOCdevkit/VOC2007'image_dir = os.path.join(base_dir, 'JPEGImages')annotation_dir = os.path.join(base_dir, 'Annotations')# 获取所有图像和标注文件all_images = [f for f in os.listdir(image_dir) if f.endswith('.jpg')]all_annotations = [f for f in os.listdir(annotation_dir) if f.endswith('.xml')]# 确保图像和标注匹配image_stems = [os.path.splitext(f)[0] for f in all_images]annotation_stems = [os.path.splitext(f)[0] for f in all_annotations]valid_stems = set(image_stems) & set(annotation_stems)# 创建完整路径image_paths = [os.path.join(image_dir, f"{stem}.jpg") for stem in valid_stems]annotation_paths = [os.path.join(annotation_dir, f"{stem}.xml") for stem in valid_stems]# 随机选择4张图像进行Mosaic增强if len(image_paths) >= 4:indices = random.sample(range(len(image_paths)), 4)selected_images = [image_paths[i] for i in indices]selected_annotations = [annotation_paths[i] for i in indices]# 执行Mosaic增强mosaic_img, mosaic_boxes = mosaic_augmentation(selected_images, selected_annotations,input_size=(640, 640),  # 更大的输入尺寸min_offset=0.3,  # 更大的最小偏移show_result=True  # 显示结果)# 保存结果cv2.imwrite('mosaic_result.jpg', mosaic_img)print(f"Mosaic增强完成! 检测框数量: {len(mosaic_boxes)}")else:print("需要至少4张带标注的图像进行Mosaic增强")

4. 关键优势

  • 提升小目标性能: 如前所述,这是其最显著的优势之一。

  • 数据利用效率高: 一张合成图包含 4 张图的标注信息,相当于增大了有效批大小。

  • 学习复杂上下文: 模型被迫理解不同场景片段拼合在一起的上下文。

  • 增强几何鲁棒性: 大幅度的缩放和裁剪模拟了现实世界目标的尺度变化和部分遮挡。

  • 降低训练成本: 可以在较小的物理批大小下达到接近使用更大批大小的效果(尤其在显存受限时)。

5. 潜在缺点与注意事项

  • 不自然的图像: 合成的图像在视觉上可能非常不自然(如天空和地板相接),虽然这有助于鲁棒性,但过于离奇的组合可能引入噪声。通常实践中利大于弊。

  • 标注噪声: 边界框裁剪可能导致部分目标信息丢失(如只保留半个目标),或者裁剪边缘目标时可能引入轻微的定位噪声。需要仔细实现裁剪逻辑。

  • 训练后期可能不稳定: 一些研究发现,在训练后期继续使用高概率的 Mosaic 可能导致优化困难或性能震荡。常见的策略是随着训练进行线性衰减 Mosaic 的应用概率(例如,从第 N 个 Epoch 开始,每个 Epoch 将 Mosaic 的概率乘以一个衰减因子,最终降为 0)。

  • 计算开销: 合成图像和变换标注需要额外的计算,但通常这个开销被其带来的训练效率提升所抵消。

  • 与其他增强的协调: Mosaic 通常作为增强流水线的第一步,其后应用其他像素级或轻量几何增强。过强的后续增强可能破坏 Mosaic 带来的好处。

6. 应用场景

  • 目标检测 (Object Detection): 这是 Mosaic 最主要的应用领域,尤其适用于 YOLO 系列、SSD 等单阶段检测器。

  • 实例分割 (Instance Segmentation): 理论上也可用,但需要额外处理掩码 (mask) 的缩放、偏移和裁剪,实现更复杂。不如在目标检测中普及。

  • 其他密集预测任务 (如语义分割): 较少使用,因为拼接可能导致语义边界严重不连续,合成图像过于离奇。

7. 总结

Mosaic 数据增强是一种通过拼接 4 张随机图像及其标注来创建新训练样本的强大技术。它通过显著增加图像中目标的密度和多样性、强制模型学习不同尺度和上下文、以及提高小目标检测能力,在目标检测任务(特别是基于 YOLO 的模型)中取得了巨大成功。尽管会生成视觉上不自然的图像并带来一些实现复杂性,但其在提升模型性能、特别是对小目标的鲁棒性方面的优势使其成为现代目标检测训练流程中一个不可或缺的组件。合理使用(如训练后期衰减其概率)可以最大化其收益。

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

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

相关文章

LINUX 722 逻辑卷快照

逻辑卷快照 lvcreate -L 128M -s -n lv1-snap /dev/vg1/lv1 lvs lvscan mount -o ro /dev/vg1/lv1 /mmt/lv1-snap dmsetup ls --tree 测试 lvs /dev/vg1/lv1-snap dd if/dev/zero of/uc1/test bs1M count40 lvs /dev/vg1/lv1-snap 问题 [rootweb ~]# cd /mnt [rootweb mnt]# m…

Springboot+vue个人健康管理系统的设计与实现

文章目录前言详细视频演示具体实现截图后端框架SpringBoot前端框架Vue持久层框架MyBaits成功系统案例&#xff1a;代码参考数据库源码获取前言 博主介绍:CSDN特邀作者、985高校计算机专业毕业、现任某互联网大厂高级全栈开发工程师、Gitee/掘金/华为云/阿里云/GitHub等平台持续…

数据结构 --栈和队链

一.栈的概念一种特殊的线性表&#xff0c;只能从固定的一端插入和删除元素。栈中元素遵循先进后出的原则。二.模拟实现public class MyStack {public int size;public int[] array;public MyStack(){array new int[10];}private void grow(){array Arrays.copyOf(array,array…

文档处理控件TX Text Control系列教程:使用 C# .NET 将二维码添加到 PDF 文档

PDF 文档通常是合同、发票、证书和报告的最终格式。尽管它们在设计上是静态的&#xff0c;但用户现在希望能够与它们交互、验证信息并直接从这些文件访问数字服务。这时&#xff0c;二维码就变得至关重要。 PDF 文档中的二维码将印刷或数字内容与动态在线体验连接起来。用户只需…

Google Chrome 谷歌浏览器全部版本集合

Google Chrome 谷歌浏览器全部版本集合 Collection of all software versions of Google Chrome. 项目介绍 本项目为Google Chrome谷歌浏览器的全部版本集合&#xff0c;方便大家下载旧版本使用。 因为Gitee项目限制仓库1G大小&#xff0c;所以许多谷歌浏览器版本无法上传。…

论文略读:Towards Safer Large Language Models through Machine Unlearning

ACL 2024大型语言模型&#xff08;LLMs&#xff09;的迅猛发展展现了其在多个领域的巨大潜力&#xff0c;这主要得益于其广泛的预训练知识和出色的泛化能力。然而&#xff0c;当面对问题性提示&#xff08;problematic prompts&#xff09;时&#xff0c;LLMs 仍然容易生成有害…

深度学习 ---参数初始化以及损失函数

深度学习 —参数初始化以及损失函数 文章目录深度学习 ---参数初始化以及损失函数一&#xff0c;参数初始化1.1 固定值初始化1.1.1 全0初始化1.1.2 全1初始化1.3 任意常数初始化1.2 随机初始化一&#xff0c;参数初始化 神经网络的参数初始化是训练深度学习模型的关键步骤之一…

JS--M端事件

移动端&#xff08;Mobile 端&#xff0c;简称 M 端&#xff09;开发中&#xff0c;由于设备特性&#xff08;触摸屏、手势操作等&#xff09;&#xff0c;需要处理一些与桌面端不同的事件。这些事件主要针对触摸交互、手势识别等场景 一、触摸事件&#xff08;Touch Events&am…

Linux网络编程-tcp

tcp、udp对比&#xff1a;UDP1. 特点无连接&#xff1a;无需建立连接即可发送数据。不可靠&#xff1a;不保证数据顺序或完整性。低延迟&#xff1a;适合实时性要求高的场景。2. 应用场景视频/音频流传输&#xff08;如直播&#xff09;。DNS 查询、在线游戏。TCP1. 特点面向连…

记一次flink资源使用优化

一.现状分析 现有任务的资源配置如下&#xff0c;根据ui监控中Garbage Collection可以发现&#xff0c;此任务频繁的发生GC&#xff0c;且老年代GC时间较久二.整体memory使用分析如下Framework Heap&#xff08;框架堆内存&#xff09;用于Flink框架自身的堆内存&#xff08;如…

Vue底层换成啥了?如何更新DOM的?

摘要&#xff1a;之前的vue是使用虚拟 DOM的&#xff0c;但是Vue 3.6 带来了一个意义重大的更新&#xff1a; Vapor Mode 渲染模式。Vue 渲染策略的演进&#xff1a; Vue 1.x&#xff1a; 基于模板渲染策略&#xff0c;直接将模板转换为DOM元素&#xff0c;并为每个DOM元素创建…

0722 数据结构顺序表

Part 1.顺序表的代码一.顺序表的内存申请head.h: typedef int datatype;typedef struct sqlist {//数据元素datatype data[MAXSIZE];//顺序表长度int len;}*sqlist; //*sqlist的作用: //sqlist:struct Sqlist * sqlist create();head.c: sqlist create() {sqlist list (sqlist)…

为何在 Vue 的 v-model 指令中不能使用可选链(Optional Chaining)?

Vue 的 v-model 是实现组件与数据双向绑定的核心指令之一&#xff0c;它本质上是一个语法糖&#xff0c;用于简化对表单元素和组件 props 的同步更新。然而&#xff0c;在 Vue 3&#xff08;以及 Vue 2 的某些模式下&#xff09;&#xff0c;开发者尝试在 v-model 中使用 JavaS…

基于单片机智能药盒/智能药箱/定时吃药系统

传送门 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目速选一览表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目功能速览 概述 本设计实现了一种基于单片机的智能药盒&#xff0c;系统以微控制器&#xff08;如STM32&#xff…

(25)python+playwright自动化处理单选和多选按钮-中

1.简介上一篇中讲解和介绍的单选框有点多&#xff0c;而且由于时间的关系&#xff0c;决定今天讲解和分享复选框的相关知识。2.什么是单选框、复选框&#xff1f;单选按钮一般叫raido button&#xff0c;就像我们在电子版的单选答题过程一样&#xff0c;单选只能点击一次&#…

Nginx IP授权页面实现步骤

目标&#xff1a;一、创建白名单文件sudo mkdir -p /usr/local/nginx/conf/whitelist sudo touch /usr/local/nginx/conf/whitelist/temporary.conf二、创建Python认证服务文件路径&#xff1a;/opt/script/auth_server.pyimport os import time from flask import Flask, requ…

2025年7月中科院一区-向光生长优化算法Phototropic growth algorithm-附Matlab免费代码

引言 本期介绍一种新的元启发式算法——向光生长优化算法Phototropic growth algorithm&#xff0c;PGA。灵感来自植物细胞在阳光下的生长模式。于2025年7月最新发表在JCR 1区&#xff0c;中科院1区 SCI 期刊 Knowledge-Based Systems。 该算法将生物学启发的确定性生长行为与…

poi-excel-添加水印

1、官网快速指南 https://poi.apache.org/components/spreadsheet/quick-guide.html 访问如上地址可以查看到poi的相关操作方式&#xff1a; How to create a new workbookHow to create a sheetHow to create cellsHow to create date cellsWorking with different types of…

STM32 开发的鼠标:技术详解与实现指南

概述基于STM32微控制器开发的鼠标是一种高度可定化的输入设备解决方案&#xff0c;广泛应用于工业控制、嵌入式系统、特殊人机交互等领域。相比传统鼠标&#xff0c;STM32鼠标具有以下优势&#xff1a;高度可定制性&#xff1a;可添加特殊功能按键、传感器集成低功耗设计&#…

GoLang教程007:打印空心金字塔

4.6 案例一&#xff1a;打印金字塔编写一个程序&#xff0c;可以接收一个整数&#xff0c;表示层数&#xff0c;打印出金字塔。1️⃣第一步&#xff1a;打印一个矩形 package mainimport "fmt"func main() {// i表示层数for i : 1; i < 3; i {// j表示每层打印多少…