深度學習Y7周:YOLOv8訓練自己數據集

  • 🍨 本文為🔗365天深度學習訓練營中的學習記錄博客
  • 🍖 原作者:K同學啊

一、配置環境

1.官網下載源碼

2.安裝需要環境

二、準備好自己的數據

目錄結構:

·主目錄

? ? ? ? ·data

? ? ? ? ? ? ? ? ·images(存放圖片)

? ? ? ? ? ? ? ? ·annotations(放置.xml文件)

? ? ? ? ? ? ? ? ·imagesets

? ? ? ? ? ? ? ? ? ? ? ? ·main(會在該文件夾內自動生成train.txt、val.txt、test.txt、trainval.txt文件,? 存放訓練集、驗證集、測試集圖片的名字)

? ? ? ? ? ? ? ? ·split_train_val.py(劃分訓練集、驗證集與測試集)

? ? ? ? ? ? ? ? ·voc_label.py(獲取劃分訓練集、驗證集與測試集路徑)

? ? ? ? ? ? ? ? ·ab.yaml(數據集及數據類別聲明文件)

? ? ? ? ·trainByK.py(代碼運行文件)

1.運行split_train_val.py文件

文件代碼:

import os,random,argparseparser=argparse.ArgumentParser()# 添加命令行參數,用于指定XML文件的路徑,默認為‘Annotation'文件夾
parser.add_argument('--xml_path',default='Annotations',type=str,help='input xml label path')#添加命令行參數,用于指定輸出txt標簽文件的路徑,默認為'ImageSets/Main'文件
parser.add_argument('--txt_path',default='ImageSets/Main',type=str,help='output txt label path')#解析命令行參數
opt=parser.parse_args()#定義訓練驗證集和測試集的劃分比例
trainval_percent=1.0  # 使用全部數據
train_percent=0.9  # 訓練集占訓練驗證集的90%# 設置XML文件夾的路徑,根據命令行參數指定
xmlfilepath=opt.xml_path# 設置輸出txt標簽文件的路徑,根據命令行參數指定
txtsavepath=opt.txt_path# 獲取XML文件夾中的所有XML文件列表
total_xml=os.listdir(xmlfilepath)# 如果輸出txt標簽文件的文件夾不存在,創建它
if not os.path.exists(txtsavepath):os.makedirs(txtsavepath)# 獲取XML文件的總數
num=len(total_xml)# 創建一個包含所有XML文件索引的列表
list_index=range(num)# 計算訓練驗證集的數量
tv=int(num*trainval_percent)# 計算訓練集的數量
tr=int(tv*train_percent)# 從所有XML文件索引中隨機選擇出訓練驗證集的索引
trainval=random.sample(list_index,tv)# 從訓練驗證集的索引中隨機選擇出訓練集的索引
train=random.sample(trainval,tr)# 打開要寫入的訓練驗證集、測試集、訓練集、驗證集的txt文件
file_trainval=open(txtsavepath+'/trainval.txt','w')
file_test=open(txtsavepath+'/test.txt','w')
file_train=open(txtsavepath+'/train.txt','w')
file_val=open(txtsavepath+'/val.txt','w')#遍歷所有XML文件的索引
for i in list_index:name = total_xml[i][:-4] + '\n'  #獲取XML文件的名稱(去掉后綴.xml),并添加換行符#如果該索引在訓練驗證集中if i in trainval:file_trainval.write(name) #寫入訓練驗證集txt文件if i in train:file_train.write(name)else:file_val.write(name)else:file_test.write(name)file_trainval.close()
file_train.close()
file_val.close()
file_test.close()

運行后得到四個文件:

注:如何修改數據集中訓練集、驗證集、測試集的比例?

2.運行voc_label.py文件(將label格式轉換為VOC格式)

文件代碼:

#-*- coding:utf-8 -*-import xml.etree.ElementTree as ET
import os
from os import getcwd#定義數據集的名稱
sets =['train','val','test']#請根據您的數據集修改這些類別名稱
# classes =['Banana','Snake fruit','Dragon fruit','Pineapple']
classes = ["banana","snake fruit","dragon fruit","pineapple"]#獲取當前工作目錄的絕對路徑
abs_path = os.getcwd()
print(abs_path)#定義一個函數,將邊界框的坐標從絕對值轉換為相對于圖像大小的比例
def convert(size,box):dw = 1./(size[0])     #計算圖像寬度的倒數dh = 1./(size[1])     #計算圖像高度的倒數x = (box[0]+box[1])/2.0-1     #計算中心點的x坐標y = (box[2]+box[3])/2.0-1     # 計算中心點的y坐標w= box[1]- box[0]           # 計算邊界框的寬度h = box[3] - box[2]         # 計算邊界框的高度x = x * dw  # 縮放x坐標w = w * dw  # 縮放寬度y = y * dh  # 縮放y坐標h=h*dh      #縮放高度return x, y, w, h# 定義一個函數,將標注文件從XML格式轉換為YOLO格式
def convert_annotation(image_id):in_file = open('./Annotations/%s.xml' % (image_id),encoding='UTF-8')  # 打開XML標注文件out_file = open('./labels/%s.txt'%(image_id),'w')   # 打開要寫入的YOL0格式標簽文件tree =ET.parse(in_file)     #解析XML文件root = tree.getroot()filename = root.find('filename').text  # 獲取圖像文件名filenameFormat=filename.split(".")[1]  # 獲取文件格式size = root.find('size')  # 獲取圖像尺寸信息w= int(size.find('width').text)  # 獲取圖像寬度h=int(size.find('height').text)  #獲取圖像高度for obj in root.iter('object'):difficult = obj.find('difficult').text  # 獲取對象的難度標志cls= obj.find('name').text #獲取對象的類別名稱if cls not in classes or int(difficult)== 1:continuecls_id = classes.index(cls)    # 獲取類別的索引xmlbox = obj.find('bndbox')    # 獲取邊界框坐標信息b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),float(xmlbox.find('ymax').text))b1,b2,b3,b4 = b# 標注越界修正if b2 > w:b2 = wif b4 > h:b4 = hb=(b1,b2,b3,b4)bb=convert((w,h),b)     #調用convert函數,將邊界框坐標轉換為Y0OLO格式out_file.write(str(cls_id)+" "+" ".join([str(a) for a in bb])+'\n')     #寫入YOLO格式標簽文件return filenameFormat#獲取當前工作目錄
wd = getcwd()#遍歷每個數據集(train、val、test)
for image_set in sets:#如果labels目錄不存在,創建它if not os.path.exists('./labels/'):os.makedirs('./labels/')#從數據集文件中獲取圖像ID列表image_ids = open('./ImageSets/Main/%s.txt'%(image_set)).read().strip().split()#打開要寫入的文件,寫入圖像的文件路徑和格式list_file =open('./%s.txt'%(image_set),'w')for image_id in image_ids:filenameFormat=convert_annotation(image_id)list_file.write(abs_path +'/images/%s.%s\n'%(image_id,filenameFormat)) #注意你的圖片格式,如果是.jpg記得修改list_file.close()

運行voc_label.py,獲得三個文件

3.創建ab.yaml文件

train:D:\learn\data\train.txt  #絕對路徑
val:D:\learn\data\val.txt    #絕對路徑nc: 4    # number of classesnames: ["banana", "snake fruit", "dragon fruit", "pineapple"]

三、開始用自己數據集訓練模型

自建train.py文件運行

from ultralytics import YOLOif __name__ == '__main__':model = YOLO("yolov8s.pt")model.train(data=r"D:\learn\ultralytics-main\data\ab.yaml",seed=0,epoches=10,batch=4,workes=2)

修改了很久,可能環境方面出了問題,依舊連接不到我的數據文件夾里。

四、訓練參數設置(defalult.yaml)

# Ultralytics YOLO 🚀, AGPL-3.0 許可證,由K同學啊注解
# 默認的訓練設置和超參數,用于中度增強 COCO 訓練task: detect  # (str) YOLO 任務,例如 detect、segment、classify、pose
mode: train   # (str) YOLO 模式,例如 train、val、predict、export、track、benchmark# 訓練設置 -------------------------------------------------------------------------------------------------------
model:  # (str, 可選) 模型文件路徑,例如 yolov8n.pt、yolov8n.yaml
data:   #./paper_data/ab.yaml # (str, 可選) 數據文件路徑,例如 coco128.yamlepochs: 100   # (int) 訓練的輪次數
patience: 50  # (int) 提前停止訓練的等待周期數,如果沒有明顯的改進
batch: 1      # (int) 每批處理的圖像數量(-1 表示自動批處理)
imgsz: 640    # (int | list) 輸入圖像的大小,以像素為單位,用于訓練和驗證模式,或以列表[w,h]形式用于預測和導出模式
save: True    # (bool) 保存訓練檢查點和預測結果
save_period: -1  # (int) 每隔 x 個周期保存一個檢查點(如果 < 1 則禁用)
cache: False     # (bool) 使用緩存加載數據(True/ram、disk 或 False)
device: 0          # (int | str | list, 可選) 運行模型的設備,例如 cuda device=0 或 device=0,1,2,3 或 device=cpu
workers: 4       # (int) 數據加載的工作線程數(每個 DDP 會有一個)
project:         # (str, 可選) 項目名稱
name:            # (str, 可選) 實驗名稱,結果保存在 'project/name' 目錄下
exist_ok: False  # (bool) 是否覆蓋現有的實驗
pretrained: True # (bool | str) 是否使用預訓練模型(True 或加載權重的模型路徑字符串)
optimizer: auto  # (str) 優化器選擇,選項=[SGD、Adam、Adamax、AdamW、NAdam、RAdam、RMSProp、auto]
verbose: True    # (bool) 是否打印詳細輸出
seed: 0          # (int) 隨機種子,用于可重復性
deterministic: True  # (bool) 是否啟用確定性模式
single_cls: False    # (bool) 是否將多類數據訓練為單一類別
rect: False          # (bool) 如果模式='train',則進行矩形訓練;如果模式='val',則進行矩形驗證
cos_lr: False        # (bool) 是否使用余弦學習率調度器
close_mosaic: 10     # (int) 在最后幾個周期內禁用馬賽克增強(0 表示禁用)
resume: False   # (bool) 是否從上次檢查點恢復訓練
amp: True       # (bool) 是否啟用自動混合精度(AMP)訓練,選項=[True, False],True 表示運行 AMP 檢查
fraction: 1.0   # (float) 訓練集數據分數(默認為 1.0,使用訓練集中的所有圖像)
profile: False  # (bool) 在訓練期間記錄 ONNX 和 TensorRT 速度以供記錄器使用
freeze: None    # (int | list, 可選) 凍結前 n 層,或在訓練期間凍結的層索引列表
# 分割
overlap_mask: True  # (bool) 訓練期間是否允許蒙版重疊(僅適用于分割訓練)
mask_ratio: 4       # (int) 蒙版下采樣比率(僅適用于分割訓練)
# 分類
dropout: 0.0  # (float) 是否使用 dropout 正則化(僅適用于分類訓練)# 驗證/測試設置 ----------------------------------------------------------------------------------------------------
val: True   # (bool) 訓練期間是否進行驗證/測試
split: val  # (str) 用于驗證的數據集分割,例如 'val'、'test' 或 'train'
save_json: False    # (bool) 是否將結果保存為 JSON 文件
save_hybrid: False  # (bool) 是否保存標簽的混合版本(標簽 + 額外的預測)
conf:     # (float, 可選) 用于檢測的對象置信度閾值(默認 0.25 用于預測,0.001 用于驗證)
iou: 0.7  # (float) NMS 的交并比(IoU)閾值
max_det: 300  # (int) 每張圖像的最大檢測數
half: False   # (bool) 是否使用半精度(FP16)
dnn: False    # (bool) 是否使用 OpenCV DNN 進行 ONNX 推理
plots: True   # (bool) 在訓練/驗證期間保存圖形# 預測設置 --------------------------------------------------------------------------------------------------
source:           # (str, 可選) 圖像或視頻的源目錄
show: False       # (bool) 如果可能的話是否顯示結果
save_txt: False   # (bool) 是否將結果保存為 .txt 文件
save_conf: False  # (bool) 是否保存帶有置信度分數的結果
save_crop: False  # (bool) 是否保存裁剪后的帶有結果的圖像
show_labels: True # (bool) 是否在圖形中顯示對象標簽
show_conf: True   # (bool) 是否在圖形中顯示對象置信度分數
vid_stride: 1     # (int) 視頻幀率跨度
stream_buffer: False  # (bool) 是否緩存所有流式幀(True)或返回最新的幀(False)
line_width:       # (int, 可選) 邊界框的線寬,如果缺失則自動設置
visualize: False  # (bool) 是否可視化模型特征
augment: False    # (bool) 是否對預測源應用圖像增強
agnostic_nms: False  # (bool) 是否進行類別無關的 NMS
classes:             # (int | list[int], 可選) 按類別篩選結果,例如 classes=0,或 classes=[0,2,3]
retina_masks: False  # (bool) 是否使用高分辨率分割蒙版
boxes: True          # (bool) 在分割預測中顯示邊界框# 導出設置 ------------------------------------------------------------------------------------------------------
format: torchscript  # (str) 導出格式,可選項請查看 https://docs.ultralytics.com/modes/export/#export-formats
keras: False         # (bool) 是否使用 Kera=s
optimize: False      # (bool) TorchScript:優化為移動設備
int8: False       # (bool) CoreML/TF INT8 量化
dynamic: False    # (bool) ONNX/TF/TensorRT:動態軸
simplify: False   # (bool) ONNX:簡化模型
opset:            # (int, 可選) ONNX:opset 版本
workspace: 4      # (int) TensorRT:工作空間大小(GB)
nms: False        # (bool) CoreML:添加 NMS# 超參數 ------------------------------------------------------------------------------------------------------
lr0: 0.01             # (float) 初始學習率(例如,SGD=1E-2,Adam=1E-3)
lrf: 0.01             # (float) 最終學習率(lr0 * lrf)
momentum: 0.937       # (float) SGD 動量/Adam beta1
weight_decay: 0.0005  # (float) 優化器權重衰減 5e-4
warmup_epochs: 3.0    # (float) 預熱周期數(分數也可以)
warmup_momentum: 0.8  # (float) 預熱初始動量
warmup_bias_lr: 0.1   # (float) 預熱初始偏置 lr
box: 7.5     # (float) 目標框損失增益
cls: 0.5     # (float) 類別損失增益(與像素一起縮放)
dfl: 1.5     # (float) DFL 損失增益
pose: 12.0   # (float) 姿勢損失增益
kobj: 1.0    # (float) 關鍵點 obj 損失增益
label_smoothing: 0.0  # (float) 標簽平滑化(分數)
nbs: 64               # (int) 名義批次大小
hsv_h: 0.015          # (float) 圖像 HSV-Hue 增強(分數)
hsv_s: 0.7      # (float) 圖像 HSV-Saturation 增強(分數)
hsv_v: 0.4      # (float) 圖像 HSV-Value 增強(分數)
degrees: 0.0    # (float) 圖像旋轉(+/- 度)
translate: 0.1  # (float) 圖像平移(+/- 分數)
scale: 0.5      # (float) 圖像縮放(+/- 增益)
shear: 0.0      # (float) 圖像剪切(+/- 度)
perspective: 0.0  # (float) 圖像透視變換(+/- 分數),范圍 0-0.001
flipud: 0.0       # (float) 圖像上下翻轉(概率)
fliplr: 0.5       # (float) 圖像左右翻轉(概率)
mosaic: 1.0       # (float) 圖像馬賽克增強(概率)
mixup: 0.0        # (float) 圖像混合增強(概率)
copy_paste: 0.0   # (float) 段復制粘貼(概率)# 自定義 config.yaml ---------------------------------------------------------------------------------------------------
cfg:  # (str, 可選) 用于覆蓋默認.yaml 的自定義配置# 跟蹤器設置 ------------------------------------------------------------------------------------------------------
tracker: botsort.yaml  # (str) 跟蹤器類型,選項=[botsort.yaml, bytetrack.yaml]

五、總結

閱讀代碼能力比之前強了,但是還是缺少連接所有知識的能力。有的時候總會忘記之前出現問題的解決辦法,導致實驗無法進行。?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/905247.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/905247.shtml
英文地址,請注明出處:http://en.pswp.cn/news/905247.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

英偉達Blackwell架構重構未來:AI算力革命背后的技術邏輯與產業變革

——從芯片暴力美學到分布式智能體網絡&#xff0c;解析英偉達如何定義AI基礎設施新范式 開篇&#xff1a;當算力成為“新石油”&#xff0c;英偉達的“煉油廠”如何升級&#xff1f; 2025年3月&#xff0c;英偉達GTC大會上&#xff0c;黃仁勛身披標志性皮衣&#xff0c;宣布了…

CurrentHashMap的整體系統介紹及Java內存模型(JVM)介紹

當我們提到ConurrentHashMap時&#xff0c;先想到的就是HashMap不是線程安全的&#xff1a; 在多個線程共同操作HashMap時&#xff0c;會出現一個數據不一致的問題。 ConcurrentHashMap是HashMap的線程安全版本。 它通過在相應的方法上加鎖&#xff0c;來保證多線程情況下的…

Android開發-設計規范

在Android應用開發中&#xff0c;遵循良好的設計規范不僅能夠提升用戶體驗&#xff0c;還能確保代碼的可維護性和擴展性。本文將從用戶界面&#xff08;UI&#xff09;、用戶體驗&#xff08;UX&#xff09;、性能優化以及代碼結構等多個維度探討Android開發中的設計規范&#…

泛型加持的策略模式:打造高擴展的通用策略工具類

一、傳統策略模式的痛點與突破 1.1 傳統策略實現回顧 // 傳統支付策略接口 public interface PaymentStrategy {void pay(BigDecimal amount); }// 具體策略實現 public class AlipayStrategy implements PaymentStrategy {public void pay(BigDecimal amount) { /* 支付寶支…

物聯網從HomeAssistant開始

文章目錄 一、什么是home-assistant?1.核心架構2.集成架構 二、在樹梅派5上安裝home-assistant三、接入米家1.對比下趨勢2.手動安裝插件3.配置方式 四、接入公牛1.手動安裝插件2.配置方式 五、接入海爾1.手動安裝插件2.配置方式 六、接入國家電網 一、什么是home-assistant? …

系統架構-嵌入式系統架構

原理與特征 嵌入式系統的典型架構可概括為兩種模式&#xff0c;即層次化模式架構和遞歸模式架構 層次化模式架構&#xff0c;位于高層的抽象概念與低層的更加具體的概念之間存在著依賴關系&#xff0c;封閉型層次架構指的是&#xff0c;高層的對象只能調用同一層或下一層對象…

計算機圖形學編程(使用OpenGL和C++)(第2版)學習筆記 09.天空和背景

天空和背景 對于 3D 場景&#xff0c;通常可以通過在遠處的地平線附近創造一些逼真的效果&#xff0c;來增強其真實感。我們可以采用天空盒、天空柱&#xff08;Skydome&#xff09;或天空穹&#xff08;Skydome&#xff09;等技術來模擬天空。 天空盒 天空盒&#xff08;Sk…

【Leetcode 每日一題】1550. 存在連續三個奇數的數組

問題背景 給你一個整數數組 a r r arr arr&#xff0c;請你判斷數組中是否存在連續三個元素都是奇數的情況&#xff1a;如果存在&#xff0c;請返回 t r u e true true&#xff1b;否則&#xff0c;返回 f a l s e false false。 數據約束 1 ≤ a r r . l e n g t h ≤ 10…

面試題解析 | C++空類的默認成員函數(附生成條件與底層原理)

在C面試中&#xff0c;“空類默認生成哪些成員函數”是考察對象模型和編譯器行為的高頻題目。許多資料僅提及前4個函數&#xff0c;但完整的答案應包含6個核心函數&#xff0c;并結合C標準深入解析其生成規則與使用場景。 一、空類默認生成的6大成員函數 1. ?缺省構造函數? …

視頻編解碼學習7之視頻編碼簡介

視頻編碼技術發展歷程與主流編碼標準詳解 視頻編碼技術是現代數字媒體領域的核心技術之一&#xff0c;它通過高效的壓縮算法大幅減少了視頻數據的體積&#xff0c;使得視頻的存儲、傳輸和播放變得更加高效和經濟。從早期的H.261標準到最新的AV1和H.266/VVC&#xff0c;視頻編碼…

使用Stable Diffusion(SD)中,步數(Steps)指的是什么?該如何使用?

Ⅰ定義&#xff1a; 在Stable Diffusion&#xff08;SD&#xff09;中&#xff0c;步數&#xff08;Steps&#xff09; 指的是采樣過程中的迭代次數&#xff0c;也就是模型從純噪聲一步步“清晰化”圖像的次數。你可以理解為模型在畫這張圖時“潤色”的輪數。 Ⅱ步數的具體作…

消息隊列如何保證消息可靠性(kafka以及RabbitMQ)

目錄 RabbitMQ保證消息可靠性 生產者丟失消息 MQ丟失消息 消費端丟失了數據 Kakfa的消息可靠性 生產者的消息可靠性 Kakfa的消息可靠性 消費者的消息可靠性 RabbitMQ保證消息可靠性 生產者丟失消息 1.事務消息保證 生產者在發送消息之前&#xff0c;開啟事務消息隨后生…

如何查看項目是否支持最新 Android 16K Page Size 一文匯總

前幾天剛聊過 《Google 開始正式強制 Android 適配 16 K Page Size》 之后&#xff0c;被問到最多的問題是「怎么查看項目是否支持 16K Page Size」 &#xff1f;其實有很多直接的方式&#xff0c;但是最難的是當你的項目有很多依賴時&#xff0c;怎么知道這個「不支持的動態庫…

HttpServletResponse的理解

HttpServletResponse 是 Java Servlet API 提供的一個接口 常用方法 方法用途setContentType(String type)設置響應內容類型&#xff08;如 "application/json"、"text/html"&#xff09;setStatus(int sc)設置響應狀態碼&#xff08;如 200、404&#x…

可靈 AI:開啟 AI 視頻創作新時代

在當今數字化浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;技術正以前所未有的速度滲透到各個領域&#xff0c;尤其是在內容創作領域&#xff0c;AI 的應用正引發一場革命性的變革。可靈 AI 作為快手團隊精心打造的一款前沿 AI 視頻生成工具&#xff0c;宛如一顆璀璨的…

用 AltSnap 解鎖 Windows 窗口管理的“魔法”

你有沒有遇到過這樣的場景&#xff1a;電腦屏幕上堆滿了窗口&#xff0c;想快速調整它們的大小和位置&#xff0c;卻只能拖來拖去&#xff0c;費時又費力&#xff1f;或者你是個多任務狂魔&#xff0c;喜歡一邊寫代碼、一邊看文檔、一邊刷視頻&#xff0c;卻發現 Windows 自帶的…

深度策略梯度算法PPO

一、策略梯度核心思想和原理 從時序差分算法Q學習到深度Q網絡&#xff0c;這些算法都側重于學習和優化價值函數&#xff0c;屬于基于價值的強化學習算法&#xff08;Value-based&#xff09;。 1. 基于策略方法的主要思想&#xff08;Policy-based&#xff09; 基于價值類方…

【LaTeX】Word插入LaTeX行間公式如何編號和對齊

在 Word 文檔中插入公式&#xff0c;需要用到 LaTeX \LaTeX LATE?X 。但遺憾的是&#xff0c;Word 只支持部分 LaTeX \LaTeX LATE?X 語法&#xff0c;這就導致很多在 Markdown 能正常渲染的公式在 Word 中無法正常顯示。 “內嵌”和“顯示” 首先介紹一下 Word 的“內嵌”…

互聯網大廠Java面試實戰:Spring Boot到微服務的技術問答解析

&#x1f4aa;&#x1f3fb; 1. Python基礎專欄&#xff0c;基礎知識一網打盡&#xff0c;9.9元買不了吃虧&#xff0c;買不了上當。 Python從入門到精通 &#x1f601; 2. 畢業設計專欄&#xff0c;畢業季咱們不慌忙&#xff0c;幾百款畢業設計等你選。 ?? 3. Python爬蟲專欄…

spring boot3.0自定義校驗注解:文章狀態校驗示例

文章目錄 Spring Boot 自定義校驗注解&#xff1a;狀態校驗示例一、創建 State 注解步驟&#xff1a;1. 創建自定義注解&#xff1a;2. 實現校驗邏輯&#xff1a; 二、 實現自定義校驗步驟:1. 在實體類中使用自定義校驗注解 State&#xff1a;2. 添加 State 注解&#xff1a; 總…