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

项目2 车牌检测

检测车牌

  • 1. 基本思想
  • 2. 基础知识
    • 2.1 YOLOV5(参考鱼苗检测)
      • 2.1.1 模型 省略
      • 2.1.2 输入输出 省略
      • 2.1.3 损失函数 省略
    • 2.2 LPRNet
      • 2.2.1 模型
      • 2.2.2 输入输出
      • 2.2.3 损失函数
  • 3. 流程
    • 3.1 数据处理
      • 3.1.1 YOLOV5数据处理
      • 3.2.2 LPRNet数据处理
    • 3.2 训练
      • 3.2.1 YOLOV5训练 省略
      • 3.2.2 LPRNet训练
    • 3.3 推理
      • 3.3.1 YOLOV5推理 省略
      • 3.3.2 LPRNet推理
    • 3.4 测试
      • 3.4.1 YOLOV5测试 省略
      • 3.4.2 LPRNet测试
    • 3.5 合并检测与识别
    • 3.6 结果

1. 基本思想

YOLOv5+LPRNet。先使用YOLOv5检测车牌,再把检测车牌送入LPRNet得到检测结果。

2. 基础知识

2.1 YOLOV5(参考鱼苗检测)

2.1.1 模型 省略

2.1.2 输入输出 省略

2.1.3 损失函数 省略

2.2 LPRNet

2.2.1 模型

在这里插入图片描述
图像统一尺寸后输入到模型,先经过Backbone得到特征f2、f6、 f13、 f22,四个特征经过Neck处理后拼接在一起,最后经过检测头得到[bs,68,18]的结果,18表示模型输出18个字符,每个字符有68类。代码如下:

import torch.nn as nn
import torch
CHARS = ['京', '沪', '津', '渝', '冀', '晋', '蒙', '辽', '吉', '黑','苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤','桂', '琼', '川', '贵', '云', '藏', '陕', '甘', '青', '宁','新','0', '1', '2', '3', '4', '5', '6', '7', '8', '9','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K','L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V','W', 'X', 'Y', 'Z', 'I', 'O', '-']
class small_basic_block(nn.Module):def __init__(self, ch_in, ch_out):super(small_basic_block, self).__init__()self.block = nn.Sequential(nn.Conv2d(ch_in, ch_out // 4, kernel_size=1),nn.ReLU(),nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(3, 1), padding=(1, 0)),nn.ReLU(),nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(1, 3), padding=(0, 1)),nn.ReLU(),nn.Conv2d(ch_out // 4, ch_out, kernel_size=1),)def forward(self, x):return self.block(x)class LPRNet(nn.Module):def __init__(self, lpr_max_len, phase, class_num, dropout_rate):super(LPRNet, self).__init__()self.phase = phaseself.lpr_max_len = lpr_max_lenself.class_num = class_numself.backbone = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1),    # 0  -> [bs,3,24,94] -> [bs,64,22,92]nn.BatchNorm2d(num_features=64),                                       # 1  -> [bs,64,22,92]nn.ReLU(),                                                             # 2  -> [bs,64,22,92]nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 1, 1)),                 # 3  -> [bs,64,20,90]small_basic_block(ch_in=64, ch_out=128),                               # 4  -> [bs,128,20,90]nn.BatchNorm2d(num_features=128),                                      # 5  -> [bs,128,20,90]nn.ReLU(),                                                             # 6  -> [bs,128,20,90]nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(2, 1, 2)),                 # 7  -> [bs,64,18,44]small_basic_block(ch_in=64, ch_out=256),                               # 8  -> [bs,256,18,44]nn.BatchNorm2d(num_features=256),                                      # 9  -> [bs,256,18,44]nn.ReLU(),                                                             # 10 -> [bs,256,18,44]small_basic_block(ch_in=256, ch_out=256),                              # 11 -> [bs,256,18,44]nn.BatchNorm2d(num_features=256),                                      # 12 -> [bs,256,18,44]nn.ReLU(),                                                             # 13 -> [bs,256,18,44]nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(4, 1, 2)),                 # 14 -> [bs,64,16,21]nn.Dropout(dropout_rate),  # 0.5 dropout rate                          # 15 -> [bs,64,16,21]nn.Conv2d(in_channels=64, out_channels=256, kernel_size=(1, 4), stride=1),   # 16 -> [bs,256,16,18]nn.BatchNorm2d(num_features=256),                                            # 17 -> [bs,256,16,18]nn.ReLU(),                                                                   # 18 -> [bs,256,16,18]nn.Dropout(dropout_rate),  # 0.5 dropout rate                                  19 -> [bs,256,16,18]nn.Conv2d(in_channels=256, out_channels=class_num, kernel_size=(13, 1), stride=1),  # class_num=68  20  -> [bs,68,4,18]nn.BatchNorm2d(num_features=class_num),                                             # 21 -> [bs,68,4,18]nn.ReLU(),                                                                          # 22 -> [bs,68,4,18])self.container = nn.Sequential(nn.Conv2d(in_channels=448+self.class_num, out_channels=self.class_num, kernel_size=(1, 1), stride=(1, 1)),def forward(self, x):keep_features = list()for i, layer in enumerate(self.backbone.children()):x = layer(x)if i in [2, 6, 13, 22]:keep_features.append(x)global_context = list()# keep_features: [bs,64,22,92]  [bs,128,20,90] [bs,256,18,44] [bs,68,4,18]for i, f in enumerate(keep_features):if i in [0, 1]:# [bs,64,22,92] -> [bs,64,4,18]# [bs,128,20,90] -> [bs,128,4,18]f = nn.AvgPool2d(kernel_size=5, stride=5)(f)if i in [2]:# [bs,256,18,44] -> [bs,256,4,18]f = nn.AvgPool2d(kernel_size=(4, 10), stride=(4, 2))(f)# 没看懂这是在干嘛?有上面的avg提取上下文信息不久可以了?f_pow = torch.pow(f, 2)     # [bs,64,4,18]  所有元素求平方f_mean = torch.mean(f_pow)  # 1 所有元素求平均f = torch.div(f, f_mean)    # [bs,64,4,18]  所有元素除以这个均值global_context.append(f)x = torch.cat(global_context, 1)  # [bs,516,4,18]x = self.container(x)             # -> [bs, 68, 4, 18]   head头logits = torch.mean(x, dim=2)     # -> [bs, 68, 18]  # 68 字符类数   18字符return logitsif __name__=="__main__":lpr_max_len=18; phase=False; class_num=68; dropout_rate=0.5i = torch.rand([6,3,24,94])Net = LPRNet(lpr_max_len, phase, class_num, dropout_rate)o = Net(i) # torch.Size([6, 68, 18])

2.2.2 输入输出

  1. 模型输入
    图像处理步骤:
    (1) 处理图片。读入图片,把图片的通道由BGR转换成RGB,统一图片尺寸为[94,24],通过transform对图片归一化及改变通道位置。
    (2) 生成标签。图片的地址是标签,去除地址后缀得到标签,把标签转换成数字,判断标签是否正确。
    (3) 返回图片数组,标签及标签长度。标签长度在模型损失中使用。代码如下:
from imutils import paths
import numpy as np
import random
import cv2
import osfrom torch.utils.data import DatasetCHARS = ['京', '沪', '津', '渝', '冀', '晋', '蒙', '辽', '吉', '黑','苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤','桂', '琼', '川', '贵', '云', '藏', '陕', '甘', '青', '宁','新','0', '1', '2', '3', '4', '5', '6', '7', '8', '9','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K','L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V','W', 'X', 'Y', 'Z', 'I', 'O', '-']CHARS_DICT = {char:i for i, char in enumerate(CHARS)}class LPRDataLoader(Dataset):def __init__(self, img_dir, imgSize, lpr_max_len, PreprocFun=None):self.img_dir = img_dirself.img_paths = []for i in range(len(img_dir)):self.img_paths += [el for el in paths.list_images(img_dir[i])]random.shuffle(self.img_paths)self.img_size = imgSize         # [94, 24]self.lpr_max_len = lpr_max_len  # 8if PreprocFun is not None:self.PreprocFun = PreprocFunelse:self.PreprocFun = self.transformdef __len__(self):return len(self.img_paths)def __getitem__(self, index):filename = self.img_paths[index]Image = cv2.imdecode(np.fromfile(filename, dtype=np.uint8), -1)Image = cv2.cvtColor(Image, cv2.COLOR_RGB2BGR)height, width, _ = Image.shapeif height != self.img_size[1] or width != self.img_size[0]:Image = cv2.resize(Image, self.img_size)Image = self.PreprocFun(Image)basename = os.path.basename(filename)         # 'datasets/rec_images/train/沪A9B821.jpg'-->'沪A9B821.jpg'imgname, suffix = os.path.splitext(basename)  # '沪A9B821.jpg' -->  ('沪A9B821', '.jpg')imgname = imgname.split("-")[0].split("_")[0]label = list()for c in imgname:label.append(CHARS_DICT[c])if len(label) == 8:if self.check(label) == False:print(imgname)assert 0, "Error label ^~^!!!"return Image, label, len(label)def transform(self, img):img = img.astype('float32')   # 图片由Uint8转换为float32类型img -= 127.5                  # 图片减均值乘方差倒数实现归一化,去除噪声影响img *= 0.0078125img = np.transpose(img, (2, 0, 1))  #  [h,w,c]-->[c,h,w]return imgdef check(self, label):     # 检测标签是否正确if label[2] != CHARS_DICT['D'] and label[2] != CHARS_DICT['F'] \and label[-1] != CHARS_DICT['D'] and label[-1] != CHARS_DICT['F']:print("Error label, Please check!")return Falseelse:return Trueif __name__ == "__main__":train_img_dirs = "datasets/rec_images/train"img_size = [94, 24]lpr_max_len = 8train_dataset = LPRDataLoader(train_img_dirs.split(','), img_size, lpr_max_len)

2.模型输出步骤:
(1) 图片输入模型得到logits。
(2)对logits转换通道[6, 68,18]–>[18, 6, 68],
其中6是batch_size,68是一共68个类别,18是输出18个字符序列。
(3)用softmax把logits最后一维变成概率。代码如下:

logits = lprnet(images)
log_probs = logits.permute(2, 0, 1) # for ctc loss: T x N x C  torch.Size([18, 6, 68])
log_probs = log_probs.log_softmax(2).requires_grad_()  # [18, bs, 68]

2.2.3 损失函数

ctc_loss用来处理不等长序列的损失,用动态规划的方法找到有标签匹配的各种序列,通过使序列概率最大化来更新参数。代码如下:

loss = ctc_loss(log_probs, labels, input_lengths=input_lengths, target_lengths=target_lengths)
# input_lengths[18,18,18,...,18]  18是模型输出的字符数。target_lengths[7,7,7,...,7]  7是真实标签的的字符数,有些车牌是8个字符,依实际情况而定。

3. 流程

3.1 数据处理

3.1.1 YOLOV5数据处理

数据集 官方CCPD数据https://github.com/detectRecog/CCPD

  1. CCPD数据集中图片名称包含车牌框box的位置信息和车牌号,数据处理的目的是把获取车牌的中心点及高宽在图像中的相对位置并以txt格式保存。
    在这里插入图片描述
  2. 代码
import shutil
import cv2
import osdef txt_translate(path, txt_path):''' 根据图片的地址获取车牌的左上角和右下角坐标,把左上角和右下角坐标转成中心点和宽高格式,最后中心点和宽高格式除以图片的宽高以.txt格式保存在指定位置'''for filename in os.listdir(path):print(filename)if not "-" in filename: # 对于np等无标签的图片,过滤continuesubname = filename.split("-", 3)[2]  # 第一次分割,以减号'-'做分割,提取车牌两角坐标. '231&522_405&574'extension = filename.split(".", 1)[1] #判断车牌是否为图片if not extension == 'jpg':continuelt, rb = subname.split("_", 1)  # 第二次分割,以下划线'_'做分割lx, ly = lt.split("&", 1) # 左上角坐标rx, ry = rb.split("&", 1) # 右下角坐标width = int(rx) - int(lx) # 车牌宽度height = int(ry) - int(ly)  # bounding box的宽和高cx = float(lx) + width / 2cy = float(ly) + height / 2  # bounding box中心点img = cv2.imread(os.path.join(path , filename))if img is None:  # 自动删除失效图片(下载过程有的图片会存在无法读取的情况)os.remove(os.path.join(path, filename))continuewidth = width / img.shape[1]height = height / img.shape[0]cx = cx / img.s

相关文章:

项目2 车牌检测

检测车牌 1. 基本思想2. 基础知识2.1 YOLOV5(参考鱼苗检测)2.1.1 模型 省略2.1.2 输入输出 省略2.1.3 损失函数 省略2.2 LPRNet2.2.1 模型2.2.2 输入输出2.2.3 损失函数3. 流程3.1 数据处理3.1.1 YOLOV5数据处理3.2.2 LPRNet数据处理3.2 训练3.2.1 YOLOV5训练 省略3.2.2 LPRN…...

Linux: 网络基础

1.协议 为什么要有协议:减少通信成本。所有的网络问题,本质是传输距离变长了。 什么是协议:用计算机语言表达的约定。 2.分层 软件设计方面的优势—低耦合。 一般我们的分层依据:功能比较集中,耦合度比较高的模块层…...

【实战篇】巧用 DeepSeek,让 Excel 数据处理更高效

一、为何选择用 DeepSeek 处理 Excel 在日常工作与生活里,Excel 是我们频繁使用的工具。不管是统计公司销售数据、分析学生成绩,还是梳理个人财务状况,Excel 凭借其强大的功能,如数据排序、筛选和简单公式计算,为我们提供了诸多便利。但当面对复杂的数据处理任务,比如从…...

Flink CDC YAML:面向数据集成的 API 设计

摘要:本文整理自阿里云智能集团 、Flink PMC Member & Committer 徐榜江(雪尽)老师在 Flink Forward Asia 2024 数据集成(一)专场中的分享。主要分为以下四个方面: Flink CDC YAML API Transform A…...

RabbitMQ技术深度解析:打造高效消息传递系统

引言 在当前的分布式系统架构中,消息队列作为一种高效的消息传递机制,扮演着越来越重要的角色。RabbitMQ,作为广泛使用的开源消息代理,以其高可用性、扩展性和灵活性赢得了众多开发者的青睐。本文将深入探讨RabbitMQ的核心概念、…...

DeepSeek与人工智能的结合:探索搜索技术的未来

云边有个稻草人-CSDN博客 目录 引言 一、DeepSeek的技术背景 1.1 传统搜索引擎的局限性 1.2 深度学习在搜索中的优势 二、DeepSeek与人工智能的结合 2.1 自然语言处理(NLP) 示例代码:基于BERT的语义搜索 2.2 多模态搜索 示例代码&…...

TAPEX:通过神经SQL执行器学习的表格预训练

摘要 近年来,语言模型预训练的进展通过利用大规模非结构化文本数据取得了巨大成功。然而,由于缺乏大规模高质量的表格数据,在结构化表格数据上应用预训练仍然是一个挑战。本文提出了TAPEX,通过在一个合成语料库上学习神经SQL执行…...

Qt:Qt基础介绍

目录 Qt背景介绍 什么是Qt Qt的发展史 Qt支持的平台 Qt版本 Qt的优点 Qt的应用场景 Qt的成功案例 Qt的发展前景及就业分析 Qt背景介绍 什么是Qt Qt是⼀个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供了建立艺术级图形界面所需的所有功能。它是完全面向…...

加速度计信号处理

【使用 DSP 滤波器加速速度和位移】使用信号处理算法过滤加速度数据并将其转换为速度和位移研究(Matlab代码实现)_加速度计滤波器-CSDN博客 https://wenku.baidu.com/view/622d38b90f22590102020740be1e650e52eacff9.html?_wkts_1738906719916&bdQ…...

基于SpringBoot养老院平台系统功能实现六

一、前言介绍: 1.1 项目摘要 随着全球人口老龄化的不断加剧,养老服务需求日益增长。特别是在中国,随着经济的快速发展和人民生活水平的提高,老年人口数量不断增加,对养老服务的质量和效率提出了更高的要求。传统的养…...

Conmi的正确答案——Rider中添加icon作为exe的图标

C#版本&#xff1a;.net 8.0 Rider版本&#xff1a;#RD-243.22562.250&#xff08;非商业使用版&#xff09; 1、添加图标到解决方案下&#xff1a; 2、打开“App.xaml”配置文件&#xff0c;添加配置&#xff1a; <Applicationx:Class"ComTransmit.App"xmlns&q…...

机试题——DNS本地缓存

题目描述 正在开发一个DNS本地缓存系统。在互联网中&#xff0c;DNS&#xff08;Domain Name System&#xff09;用于将域名&#xff08;例如www.example.com&#xff09;解析为IP地址&#xff0c;以便将请求发送到正确的服务器上。通常情况下&#xff0c;DNS请求会发送到互联…...

Day38【AI思考】-彻底打通线性数据结构间的血脉联系

文章目录 **彻底打通线性数据结构间的血脉联系****数据结构家族谱系图****一、线性表&#xff08;老祖宗的规矩&#xff09;****核心特征** **二、嫡系血脉解析**1. **数组&#xff08;规矩森严的长子&#xff09;**2. **链表&#xff08;灵活变通的次子&#xff09;** **三、庶…...

【LeetCode】152、乘积最大子数组

【LeetCode】152、乘积最大子数组 文章目录 一、dp1.1 dp1.2 简化代码 二、多语言解法 一、dp 1.1 dp 从前向后遍历, 当遍历到 nums[i] 时, 有如下三种情况 能得到最大值: 只使用 nums[i], 例如 [0.1, 0.3, 0.2, 100] 则 [100] 是最大值使用 max(nums[0…i-1]) * nums[i], 例…...

[MRCTF2020]Ez_bypass1(md5绕过)

[MRCTF2020]Ez_bypass1(md5绕过) ​​ 这道题就是要绕过md5强类型比较&#xff0c;但是本身又不相等&#xff1a; md5无法处理数组&#xff0c;如果传入的是数组进行md5加密&#xff0c;会直接放回NULL&#xff0c;两个NuLL相比较会等于true&#xff1b; 所以?id[]1&gg…...

MySQL 缓存机制与架构解析

目录 一、MySQL缓存机制概述 二、MySQL整体架构 三、SQL查询执行全流程 四、MySQL 8.0为何移除查询缓存&#xff1f; 五、MySQL 8.0前的查询缓存配置 六、替代方案&#xff1a;应用层缓存与优化建议 总结 一、MySQL缓存机制概述 MySQL的缓存机制旨在提升数据访问效率&am…...

LabVIEW自定义测量参数怎么设置?

以下通过一个温度采集案例&#xff0c;说明在 LabVIEW 中设置自定义测量参数的具体方法&#xff1a; 案例背景 ​ 假设使用 NI USB-6009 数据采集卡 和 热电偶传感器 监测温度&#xff0c;需自定义以下参数&#xff1a; 采样率&#xff1a;1 kHz 输入量程&#xff1a;0~10 V&a…...

海思的一站式集成环境Hispark Studio更新了

HiSpark Studio是海思提供的面向智能设备开发者提供一站式集成开发环境&#xff0c;支持代码编辑、编译、烧录和调试等功能。我以前在评测星闪芯片的时候用过&#xff0c;当时写了篇博客&#xff1a;【星闪开发连载】WS63E开发板Windows环境的构建_hispark studio-CSDN博客。那…...

TresJS:用Vue组件构建3D场景的新选择

在当今数字化时代&#xff0c;3D图形技术正以前所未有的速度发展&#xff0c;从游戏开发到虚拟现实&#xff08;VR&#xff09;、增强现实&#xff08;AR&#xff09;&#xff0c;再到各种沉浸式体验&#xff0c;3D技术的应用场景日益丰富。TresJS作为一款基于Three.js的Web3D开…...

Axure设计教程:动态排名图(中继器实现)

一、开篇 在Axure原型设计中&#xff0c;动态图表是展示数据和交互效果的重要元素。今天&#xff0c;我们将学习如何使用中继器来创建一个动态的排名图&#xff0c;该图表不仅支持自动轮播&#xff0c;还可以手动切换&#xff0c;极大地增强了用户交互体验。此教程旨在提供一个…...

攻防世界 文件上传

题目名称-文件包含 今天的题大概提一下解题思路就好了 这里要使用php://filter 在此基础上因为网页过滤了一些关键字 我们要进行爆破 UCS-4* UCS-4BE UCS-4LE* UCS-2 UCS-2BE UCS-2LE UTF-32* UTF-32BE* UTF-32LE* UTF-16* UTF-16BE* UTF-16LE* UTF-7 UTF7-IMAP UTF-8* ASCII…...

从 .NET Framework 升级到 .NET 8 后 SignalR 问题处理与解决方案

随着 .NET Framework 向 .NET 8 的迁移&#xff0c;许多开发者在使用 SignalR 时遇到了一些前后端连接、配置、调用等方面的问题。尤其是在处理 SignalR 实时通信功能时&#xff0c;升级后的一些兼容性问题可能导致应用程序无法正常工作。本文将介绍在从 .NET Framework 升级到…...

《Node.js Express 框架》

《Node.js Express 框架》 引言 Node.js 是一种基于 Chrome V8 引擎的 JavaScript 运行环境,它允许开发者使用 JavaScript 来编写服务器端代码。Express 是一个简洁、灵活的 Node.js Web 应用框架,它为 Web 和移动应用程序提供了一系列强大的功能。本文将详细介绍 Node.js …...

Unity LineRenderer 画线及代码控制--Unity小记

Unity LineRenderer 画线及代码控制 目录 Unity LineRenderer 画线及代码控制 一、添加LineRenderer 组件 二、LineRenderer设置起始坐标 三、设置LinRenderer 四、代码片段&#xff0c;找代码直接点我&#xff08;找代码直接点我&#xff09; 一、添加LineRenderer 组件…...

llama.cpp GGML Quantization Type

llama.cpp GGML Quantization Type 1. GGML Quantization Type2. static const struct ggml_type_traits type_traits[GGML_TYPE_COUNT]3. Q#_K_M and Q#_KReferences 什么神仙妖魔&#xff0c;不过是他们禁锢异族命运的枷锁&#xff01; GGUF https://huggingface.co/docs/hu…...

k8s部署go-fastdfs

前置环境:已部署k8s集群,ip地址为 192.168.10.1~192.168.10.5,总共5台机器。 1. 创建provisioner制备器(如果已存在,则不需要) 制备器的具体部署方式可参考我的上一篇文章: k8s部署rabbitmq-CSDN博客文章浏览阅读254次,点赞3次,收藏5次。k8s部署rabbitmqhttps://blo…...

Python----Python高级(并发编程:协程Coroutines,事件循环,Task对象,协程间通信,协程同步,将协程分布到线程池/进程池中)

一、协程 1.1、协程 协程&#xff0c;Coroutines&#xff0c;也叫作纤程(Fiber) 协程&#xff0c;全称是“协同程序”&#xff0c;用来实现任务协作。是一种在线程中&#xff0c;比线程更加轻量级的存在&#xff0c;由程序员自己写程序来管理。 当出现IO阻塞时&#xff0c;…...

什么是可观测性?

现代服务架构常常谈及三个性&#xff1a; 弹性&#xff0c;韧性&#xff0c;可观测性。今天且按下其他两性不表&#xff0c;着重聊一聊可观测性。本文就几个主题对可观测性展开讨论&#xff1a; 可观测性是什么可观测性是必须的吗企业的可观测性落地 可观测性理念 可观测性是…...

3. 【.NET Aspire 从入门到实战】--理论入门与环境搭建--环境搭建

构建现代云原生应用程序时&#xff0c;开发环境的搭建至关重要。NET Aspire 作为一款专为云原生应用设计的开发框架&#xff0c;提供了一整套工具、模板和集成包&#xff0c;旨在简化分布式系统的构建和管理。开始项目初始化之前&#xff0c;确保开发环境的正确配置是成功的第一…...

kubeadm构建k8s源码阅读环境

目标 前面看了minikube的源码了解到其本质是调用了kubeadm来启动k8s集群&#xff0c;并没有达到最初看代码的目的。 所以继续看看kubeadm的代码&#xff0c;看看能否用来方便地构建源码调试环境。 k8s源码编译 kubeadm源码在k8s源码库中&#xff0c;所以要先克隆k8s源码。之…...