YOLOv4和Darknet实现坑洼检测

关于深度实战社区

我们是一个深度学习领域的独立工作室。团队成员有:中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等,曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万+粉丝,拥有2篇国家级人工智能发明专利。

社区特色:深度实战算法创新

获取全部完整项目数据集、代码、视频教程,请进入官网:zzgcz.com。竞赛/论文/毕设项目辅导答疑,v:zzgcz_com


1. 项目简介

该项目旨在实现基于深度学习的目标检测模型YOLO(You Only Look Once)在道路坑洞检测中的应用。项目主要使用YOLOv4模型,通过实时视频流的方式对道路中的坑洞进行识别和标注,从而提高交通安全和道路养护的效率。YOLO系列模型因其轻量级和高效的特性,能够在保持较高检测精度的同时,实现实时检测。本项目使用了darknet库来加载YOLOv4模型,并结合OpenCV库实现视频帧的读取和结果展示。用户可以通过指定不同的配置文件、权重文件和数据文件,实现对不同场景和目标的检测。此项目适用于各类交通管理场景,如自动驾驶辅助系统和道路维护监控。目标是通过自动化检测坑洞,减少人工检测的时间和成本,同时提升检测的准确度。
在这里插入图片描述

2.技术创新点摘要

  1. 实时多线程处理:项目使用多线程技术(Thread)实现视频流的并行处理,将整个检测流程分为视频捕获、模型推理和结果绘制三个独立的线程模块。这种设计能够有效提高系统的运行效率,减少因逐帧处理带来的延迟问题,并保持较高的帧率(FPS),实现流畅的实时检测效果。
  2. 动态图像预处理与优化:代码中通过OpenCV库对每一帧视频图像进行预处理,包括将图像转换为RGB格式(cv2.cvtColor)和调整图像尺寸(cv2.resize),确保每一帧输入图像与YOLOv4模型的输入尺寸匹配。这种高效的预处理方式能够降低模型推理时的计算开销,避免因图像格式不匹配而影响检测效果。
  3. YOLOv4模型的自定义配置与加载:项目灵活地使用了配置文件(config_file)、权重文件(weights)和数据文件(data_file)的参数化设计,允许用户根据不同场景需求自定义模型配置,从而实现对不同目标(如行人、车辆、坑洞等)的检测。此外,项目还引入了参数化阈值设置(thresh),用户可以动态调整检测精度与召回率之间的平衡,适应不同检测环境。
  4. 检测结果的自定义可视化:代码中设计了检测结果的可视化模块,通过自定义颜色和框选(convert2originaldraw_boxes)对不同类别目标进行标注。尤其是对于检测到的坑洞,采用了特殊颜色(如class_colors中定义的白色)进行高亮显示,并且在输出视频中实时显示FPS和检测坐标,使检测结果更加直观,便于用户快速识别和分析。

3. 数据集与预处理

本项目的数据集主要用于道路坑洞检测,通常来源于公开的交通监控视频数据或自定义拍摄的道路影像数据集。这些数据集的主要特点是包含不同种类的道路场景,光照条件、天气情况和道路表面状况各异,具有较高的多样性和复杂性。同时,数据集中标注了不同种类的道路缺陷(如裂缝、坑洞等),并提供了相应的边界框(Bounding Box)坐标,便于模型进行目标检测训练。

在数据预处理方面,本项目采用了以下流程:

  1. 数据格式转换:首先,对原始图像数据进行统一的格式转换,将数据集中的图像转换为YOLO模型所需的输入格式(例如将标注文件转化为YOLO格式的class, x_center, y_center, width, height),并将不同类别的标签映射到特定的类别索引中。
  2. 归一化处理:将输入图像的像素值进行归一化操作(通常将像素值缩放到0到1之间),从而减少数据的尺度差异,使得模型在训练时更加稳定,并且提升收敛速度。
  3. 数据增强:为了提升模型的泛化能力,应对不同光照和天气条件下的检测场景,数据增强是本项目中重要的一环。常用的数据增强操作包括:随机裁剪、旋转、水平翻转、亮度和对比度调整等。这些操作能够生成更多的训练样本,防止模型过拟合,并增强模型在各种场景下的检测能力。
  4. 图像尺寸调整:由于YOLOv4模型对输入图像尺寸有严格要求,因此预处理步骤中会将所有输入图像的尺寸调整为固定大小(如416×416或608×608),以便匹配模型输入层的大小。

4. 模型架构

  1. 模型结构的逻辑

本项目采用了YOLOv4(You Only Look Once Version 4)目标检测模型,该模型以其在实时检测任务中的高效性和准确性而著称。YOLOv4是由多个深度学习组件集成而成的目标检测框架,核心架构包含以下几个部分:

  • Backbone(特征提取网络) :YOLOv4的主干网络使用了CSPDarknet53(Cross-Stage Partial Darknet)来作为特征提取器。它引入了CSPNet(Cross-Stage Partial Network)结构,可以有效减少计算量和内存需求,提升网络的学习能力。该网络通过逐层卷积操作(Conv2D)、批量归一化(Batch Normalization)和激活函数(Leaky ReLU)对图像特征进行提取和编码。其输入图像大小为416×416×3416 \times 416 \times 3416×416×3(RGB通道),输出特征图为多个不同尺度的特征层(通常为52×5252 \times 5252×52、26×2626 \times 2626×26、13×1313 \times 1313×13)。

  • Neck(特征融合层) :在特征提取之后,YOLOv4引入了PANet(Path Aggregation Network)进行特征融合。该模块使用FPN(Feature Pyramid Network)和自定义的路径聚合操作(如下采样和上采样)对不同尺度的特征进行组合,生成多尺度特征图。通过融合不同层次的特征,YOLOv4能够提升对不同尺度目标(如小目标和大目标)的检测能力。

  • Head(输出层) :YOLOv4的输出层包含三种尺度的检测头(Detection Head),每个检测头使用以下公式进行目标分类和边界框预测:

    • 边界框预测(Bounding Box Prediction)
    • t x = σ ( p x ) + c x , t y = σ ( p y ) + c y , t w = p w ⋅ e b w , t h = p h ⋅ e b h \begin{equation} t_x = \sigma(p_x) + c_x, \quad t_y = \sigma(p_y) + c_y, \quad t_w = p_w \cdot e^{b_w}, \quad t_h = p_h \cdot e^{b_h} \end{equation} tx=σ(px)+cx,ty=σ(py)+cy,tw=pwebw,th=phebh
    • 其中,tx,ty,tw,th 分别表示预测边界框的中心坐标和宽高;σ为Sigmoid函数,px,py,pw,bw,bh是网络输出值,cx,cy为先验框的坐标。
    • 目标置信度与类别概率(Objectness and Class Probability)
    • C = σ ( o ) , P ( class ∣ object ) = σ ( c i ) \begin{equation} C = \sigma(o), \quad P(\text{class}|\text{object}) = \sigma(c_i) \end{equation} C=σ(o),P(classobject)=σ(ci)
    • 其中,C为目标置信度,o为网络输出的目标性判断值,P(class∣object)表示在目标存在的前提下属于特定类别的概率。
  • Anchor Box(先验框)机制:YOLOv4在不同尺度的检测头中使用了预定义的锚框(Anchor Box),分别在特征图上进行目标回归(Regression)和分类(Classification),从而能够对不同大小、长宽比的目标进行有效检测。

  1. 模型的整体训练流程

YOLOv4模型训练流程包含以下几个步骤:

  1. 数据输入与预处理:根据输入图像的大小调整为固定尺寸,并将每个目标的边界框坐标和类别标签转换为YOLO格式(相对坐标形式)。

  2. 前向传播(Forward Pass) :输入图像经过CSPDarknet53主干网络进行特征提取,生成多尺度特征图;通过PANet模块进行特征融合,并将融合后的特征图送入三个不同尺度的检测头(52x52、26x26、13x13),在每个检测头上进行目标分类和边界框回归。

  3. 损失函数计算(Loss Calculation) :YOLOv4使用了以下三种损失进行训练:

    1. 边界框回归损失(Bounding Box Loss) :计算预测框和真实框之间的IoU(Intersection-over-Union)损失。
    2. 目标置信度损失(Objectness Loss) :用于评估模型是否正确判断目标是否存在。
    3. 类别分类损失(Classification Loss) :用于评估在目标存在前提下模型对目标类别的分类准确性。
  4. 损失函数总和表示为:

  5. Total Loss = λ c o o r d ⋅ Bounding Box Loss + λ o b j ⋅ Objectness Loss + λ c l s ⋅ Classification Loss \begin{equation} \text{Total Loss} = \lambda_{coord} \cdot \text{Bounding Box Loss} + \lambda_{obj} \cdot \text{Objectness Loss} + \lambda_{cls} \cdot \text{Classification Loss} \end{equation} Total Loss=λcoordBounding Box Loss+λobjObjectness Loss+λclsClassification Loss

  6. 反向传播与权重更新(Backward Pass and Weight Update) :通过反向传播算法(Backpropagation)计算梯度,并使用Adam或SGD优化器对网络权重进行更新,最小化总损失。

  7. 评估指标(Evaluation Metrics) :在模型训练和评估过程中,使用mAP(Mean Average Precision)作为主要评估指标。mAP用于衡量模型在多个目标类别上的整体检测精度,并通过计算Precision-Recall曲线下的平均面积来确定模型在不同阈值下的检测性能。

5. 核心代码详细讲解

本项目代码主要包含了三个核心功能模块:视频捕获与预处理YOLO模型推理结果可视化与输出。以下为每个模块的关键代码片段及其详细解释。

1. 视频捕获与预处理

该模块负责从指定的视频源(视频文件或摄像头)捕获帧,并将其转化为YOLOv4模型的输入格式。

def video_capture(frame_queue, darknet_image_queue):while cap.isOpened():
        ret, frame = cap.read()  # 读取视频帧if not ret:break
        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # 将BGR格式转换为RGB格式
        frame_resized = cv2.resize(frame_rgb, (darknet_width, darknet_height),  # 调整图像尺寸
                                   interpolation=cv2.INTER_LINEAR)
        frame_queue.put(frame)  # 将原始帧放入队列中,供其他模块使用
        img_for_detect = darknet.make_image(darknet_width, darknet_height, 3)  # 创建空的darknet图像对象
        darknet.copy_image_from_bytes(img_for_detect, frame_resized.tobytes())  # 将图像数据复制到darknet格式中
        darknet_image_queue.put(img_for_detect)  # 将预处理后的图像放入队列中,供推理模块使用
    cap.release()
  • ret, frame = cap.read() :从视频源中逐帧读取图像数据。cap 是一个 cv2.VideoCapture 对象,ret 表示是否成功读取一帧图像,frame 为当前帧的图像数据。
  • cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) :将 OpenCV 默认的 BGR 格式图像转换为 YOLO 所需的 RGB 格式。
  • cv2.resize(frame_rgb, (darknet_width, darknet_height), interpolation=cv2.INTER_LINEAR) :将图像调整为 YOLOv4 模型输入所需的尺寸 (darknet_widthdarknet_height)。INTER_LINEAR 插值方法可以保证图像缩放时的质量。
  • frame_queue.put(frame) :将原始帧(未缩放、未转换格式)放入队列中,后续用于绘制和可视化。
  • darknet.make_image(darknet_width, darknet_height, 3) :创建一个 YOLO 需要的空白图像对象,3 代表 RGB 三通道。
  • darknet.copy_image_from_bytes(img_for_detect, frame_resized.tobytes()) :将调整好的图像数据(字节流格式)复制到 darknet 图像对象中,供 YOLO 模型使用。
  • darknet_image_queue.put(img_for_detect) :将处理完的图像放入队列中,供 YOLO 推理模块调用。
2. YOLO 模型推理模块

该模块用于将预处理好的图像输入 YOLO 模型进行推理,获取检测结果(包括目标类别、置信度和边界框信息)。

def inference(darknet_image_queue, detections_queue, fps_queue):while cap.isOpened():
        darknet_image = darknet_image_queue.get()  # 从队列中取出待推理的图像
        prev_time = time.time()  # 记录当前时间,用于计算FPS
        detections = darknet.detect_image(network, class_names, darknet_image, thresh=args.thresh)  # 进行目标检测
        detections_queue.put(detections)  # 将检测结果放入队列
        fps = int(1/(time.time() - prev_time))  # 计算当前帧率
        fps_queue.put(fps)  # 将帧率放入队列中print("FPS: {}".format(fps))  # 输出帧率信息
        darknet.print_detections(detections, args.ext_output)  # 打印检测结果到控制台
        darknet.free_image(darknet_image)  # 释放YOLOv4分配的图像内存
    cap.release()
  • darknet_image_queue.get() :从 darknet_image_queue 队列中取出一帧图像,该图像已经经过预处理,准备进行YOLO推理。
  • prev_time = time.time() :记录当前时间,用于计算推理后的帧率(FPS)。
  • darknet.detect_image(network, class_names, darknet_image, thresh=args.thresh) :调用 darknetdetect_image 函数对图像进行检测。network 为模型结构,class_names 为类别标签,darknet_image 为输入图像,thresh 为置信度阈值。返回值 detections 包含每个检测目标的类别、置信度和边界框。
  • detections_queue.put(detections) :将检测结果(detections)放入队列中,供后续绘图模块使用。
  • fps = int(1/(time.time() - prev_time)) :根据前后两次时间差计算FPS。
  • darknet.print_detections(detections, args.ext_output) :将每个目标的类别、置信度和坐标信息输出到控制台。args.ext_output 决定是否打印额外的坐标信息。
  • darknet.free_image(darknet_image) :释放 darknet 图像内存,防止内存泄漏。
3. 结果绘制与视频输出

该模块负责根据 YOLO 检测结果在图像上绘制边界框,并将结果视频保存到指定文件。

def drawing(frame_queue, detections_queue, fps_queue):
    random.seed(3)  # 设置随机种子,确保检测框颜色一致
    video = set_saved_video(cap, args.out_filename, (video_width, video_height))  # 初始化视频保存对象while cap.isOpened():
        frame = frame_queue.get()  # 从帧队列中获取原始帧
        detections = detections_queue.get()  # 获取检测结果
        fps = fps_queue.get()  # 获取当前帧率
        detections_adjusted = []  # 用于存储调整后的检测结果if frame is not None:for label, confidence, bbox in detections:
                bbox_adjusted = convert2original(frame, bbox)  # 将YOLO格式的相对坐标转换为原图像坐标
                detections_adjusted.append((str(label), confidence, bbox_adjusted))
            image = darknet.draw_boxes(detections_adjusted, frame, class_colors)  # 在图像上绘制边界框
            image = cv2.putText(
                img=image,
                text=str(fps) + ' FPS',
                org=(10, 30),
                fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                fontScale=1,
                color=(0, 0, 255),
                thickness=2,
                lineType=cv2.LINE_AA
            )  # 将FPS信息绘制在图像上if not args.dont_show:
                cv2.imshow('Inference', image)  # 显示图像if args.out_filename is not None:
                video.write(image)  # 将图像保存到输出视频中if cv2.waitKey(fps) == 27:  # 按下ESC键退出break
    cap.release()
    video.release()
    cv2.destroyAllWindows()
  • set_saved_video(cap, args.out_filename, (video_width, video_height)) :初始化视频保存对象,指定保存路径、编码方式和视频尺寸。
  • frame_queue.get() :从帧队列中获取一帧原始图像数据。
  • convert2original(frame, bbox) :将 YOLO 检测的相对坐标转换为原图像的绝对坐标,用于准确绘制边界框。
  • darknet.draw_boxes(detections_adjusted, frame, class_colors) :在图像上根据检测结果绘制边界框和标签信息。
  • cv2.putText() :在图像左上角显示当前帧率(FPS)信息,便于监控检测效率。
  • video.write(image) :将绘制完的检测结果帧保存到指定视频文件中。

综上所述,这些模块通过多线程处理、YOLO模型推理和可视化输出,实现了高效的实时目标检测系统。

4o

6. 模型优缺点评价

优点:

  1. 实时性强:本项目通过多线程技术优化了YOLOv4模型的检测流程,将视频捕获、模型推理和结果绘制分为独立线程,提高了系统的并行处理能力,能够在保持高检测精度的同时实现流畅的实时视频检测。
  2. 多尺度目标检测能力强:YOLOv4模型采用了CSPDarknet53作为主干网络,并结合了PANet进行多尺度特征融合,使得模型能够有效识别大目标和小目标,提高了检测的稳定性和准确性。
  3. 易于定制与配置:代码设计灵活,用户可以通过配置文件(如cfgweights)和参数(如thresh)快速调整模型的输入和输出,实现对不同检测场景(如道路坑洞或行人检测)的需求。
  4. 结果可视化丰富:项目提供了自定义的边界框颜色、高亮显示和检测结果的实时输出,使得用户能够直观地了解检测结果,便于后续分析和决策。

缺点:

  1. 模型较为复杂,训练时间长:YOLOv4虽然优化了实时性,但其模型结构较为复杂(如CSPNet、PANet等模块的引入),训练时间较长,且对硬件资源(如GPU)有较高的需求。
  2. 小目标检测效果不理想:虽然YOLOv4引入了多尺度检测策略,但在实际应用中,对于密集的小目标(如细小裂缝)检测仍然存在一定的误检和漏检问题。
  3. 超参数调优复杂:YOLOv4的参数(如学习率、锚框大小、IoU阈值等)较多,手动调整时难以找到最佳组合,可能需要多次实验来验证效果。

改进方向:

  1. 模型结构优化:可以考虑引入轻量化网络(如MobileNet或EfficientNet)作为主干网络,减少模型的计算量和参数数量,从而提升实时检测能力。
  2. 超参数自动化调优:使用超参数优化工具(如Optuna)进行自动化搜索,提升参数调优效率。
  3. 数据增强改进:引入更多的增强方法(如CutMix、Mosaic等),进一步提升模型的鲁棒性和对不同场景的适应能力。

↓↓↓更多热门推荐:
WaveNet模型实现电力预测

全部项目数据集、代码、教程进入官网zzgcz.com

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

插画共享系统小程序的设计

管理员账户功能包括:系统首页,个人中心,管理员管理,插画信息管理,基础数据管理,论坛管理,公告信息管理,轮播图信息管理 微信端账号功能包括:系统首页,插画信…

【JAVA开源】基于Vue和SpringBoot的服装生产管理系统

本文项目编号 T 066 ,文末自助获取源码 \color{red}{T066,文末自助获取源码} T066,文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

Vue的基本用法及模板语法

Vue.js使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue实例的数据。所有 Vue.js的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析。 在底层的实现上,Vue将模板编译成虚拟 DOM 渲染函数。结合响应系…

10.2 Linux_进程_进程相关函数

创建子进程 函数声明如下: pid_t fork(void); 返回值:失败返回-1,成功返回两次,子进程获得0(系统分配),父进程获得子进程的pid 注意:fork创建子进程,实际上就是将父进程复制一遍作为子进程&…

【基础算法总结】链表篇

目录 一, 链表常用技巧和操作总结二,算法原理和代码实现2.两数相加24.两两交换链表中的节点143.重排链表23.合并k个升序链表25.k个一组翻转链表 三,算法总结 一, 链表常用技巧和操作总结 有关链表的算法题也是一类常见并且经典的题…

STM32-HAL库驱动DHT11温湿度传感器 --2024.9.28

目录 一、教程简介 二、驱动原理讲解 (一)通信4步骤 (二)传感器数据解析 三、CubeMX生成底层代码 (一)基础配置 (二)配置DHT11的驱动引脚 (三)配置串口 四…

pytest(三)——参数化@pytest.mark.parametrize

目录 前言 参数化场景 实际Web UI自动化中的开发场景,比如是一个登录框 parametrize单参数 “笛卡尔积”,多个参数化装饰器 重点知识 参考文献 前言 pytest.mark.parametrize 允许在测试函数或类中定义多组参数和fixtures pytest_generate_tests 允…

对于基础汇编的趣味认识

汇编语言 机器指令 机器语言是机器指令的集合 机器指令展开来讲就是一台机器可以正确执行的命令 电子计算机的机器指令是一列二进制数字 (计算机将其转变为一列高低电平,使得计算机的电子器件受到驱动,进行运算 寄存器:微处理器…

C(九)while循环 --- 军训匕首操情景

匕首操,oi~oi~oi~~~~~ 接下来的几篇推文,杰哥记录的是三大循环结构的运行流程及其变式。 本篇的主角是while循环。👉 目录: while循环 的组成、运行流程及其变式关键字break 和 continue 在while 循环中的作用while 循环的嵌套题目…

C/C++逆向:数据类型识别

在逆向工程中,数据类型识别是理解程序逻辑的重要步骤,因为它直接影响对程序逻辑和功能的理解,识别出数据类型有助于确定变量的含义和函数的行为。在分析恶意软件或者寻找安全漏洞时,识别数据类型能够帮助发现代码中的潜在问题。例…

【越学学糊涂的Linux系统】(5)shell命令以及运行原理|权限问题

Ⅰ.shell命名以及运行原理: 0x00引用: 什么是shell命令?? ✔️ Shell 是一种命令行解释器(Command - Line Interpreter),它为用户提供了与操作系统内核进行交互的接口。用户通过在 She…

【Qt】控件概述(3)—— 显示类控件

显示类控件 1. QLabel——标签1.1 setPixmap设置图片1.2 setAlignment设置文本对齐方式1.3 setWordWrap设置自动换行1.4 setIndent设置缩进1.5 setMargin设置边距1.6 body 2. QLCDNumber2.1 使用QTimer实现一个倒计时效果2.2 使用循环的方式实现倒计时 3. QProgressBar——进度…

【工程测试技术】第6章 信号处理初步,频谱分析,相关系数

目录 6.1 数字信号处理的基本步骤 6.2 离散信号及其频谱分析 6.2.1 概述 6.2.2 时域采样、混叠和采样定理 6.2.3 量化和量化误差 6.2.4 截断、泄漏和窗函数 6.2.5 频域采样、时域周期延拓和栅栏效应 6.2.6 频率分辨率、整周期截断 6.3 相关分析及其应用 6.3.1 两…

【C++】--类与对象(1)

🧇个人主页: 起名字真南 🌭个人专栏:【数据结构初阶】 【C语言】 【C】 目录 1 类的定义1.1 类定义格式1.1.1 Stack类1.1.2 Date类1.1.3 Struct格式 1.2 访问限定符1.3 类域 2 实例化2.2 对象大小 3 this指针 1 类的定义 1.1 类定义格式 1 class为定义…

解决磁盘负载不均——ElasticSearch 分片分配和路由设置

ES 分片分配(Shard Allocation)时间点: 初始恢复(Initial Recovery)副本分配(Replica Allocation)重平衡(Rebalance)节点添加或移除 小结: 准备移除节点时&a…

【Golang】关于Go语言字符串转换strconv

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…

k8s-集群部署1

k8s-集群部署1 一、基础环境准备二、docker环境准备三、k8s集群部署1.kubeadm创建集群2.使用kubeadm引导集群 总结 一、基础环境准备 首先,需要准备三个服务器实例,这里我使用了阿里云创建了三个实例,如果不想花钱,也可以在VM上创…

1panel申请https/ssl证书自动续期

参考教程 https://hin.cool/posts/sslfor1panel.html #Acme 账户 #1panel.腾讯云dns账号 这里有一步不需要参考,腾讯云dns账号,就是子帐号授权 直接控制台搜索 访问管理 创建用户 授权搜索dns,选择第一个 点击用户名,去掉AdministratorAccess权限 5.点击api密钥生成即可…

CSS3练习--电商web

免责声明:本文仅做分享! 目录 小练--小兔鲜儿 目录构建 SEO 三大标签 Favicon 图标 布局网页 版心 快捷导航(shortcut) 头部(header) logo 导航 搜索 购物车 底部(footer&#xff0…

初学51单片机之I2C总线与E2PROM二

总结下上篇博文的结论: 1:ACK信号在SCL为高电平期间会一直保持。 2:在字节数据传输过程中如果发送电平跳变,那么电平信号就会变成重复起始或者结束的信号。(上篇博文的测试方法还是不能够明确证明这个结论&#xff0…