YOLOX (MATLAB 2023b)

Fred Liu
13 min readSep 22, 2023

--

在2023b的版本中,MATLAB新增了物件偵測演算法YOLOX,在之前只能將YOLOX從外部匯入,並且無法在內部進行訓練,此次YOLOX也是經過了些許架構上的調整,除了重要的Anchor-free以外,還有部分層與層之間的優化。

首先看底下這張圖,來源有點不可考,就暫時沒附上來源。

暫時找不到最初來源

此圖描述了兩大資訊,一個是YOLO各家族的演進,在YOLOv3原作者離開電腦視覺界後,就進入了戰國時代,大概能分成三個分支,第一個是原作者系列AlexeyAB與王建堯博士的YOLOv4、YOLOR、YOLOv7系列,第二個是YOLOv5>YOLOv8系列,第三個是美團發表的YOLOv6系列,並且在幾個月前發布了3.0版本。

YOLOV7

YOLOv6

YOLOv8(ultralytics)

另一個是在不同版本中的license type,在GPL-3.0的license是對商業軟體較為不友善的,因為GPL系列中只要使用到了GPL license相關的程式碼,後續的衍生系列也都必須開源,因此在圖中可以看到,YOLOR、YOLOv6、YOLOv7都是GPL系列的license,所以後續在MATLAB這樣的商業軟體中要新增這幾種算法,可能就會遭遇到一些障礙。

在YOLOX論文中,新增了Decoupled Head、Strong data augmentation、Anchor-free、Multi positives、SimOTA、等技術,在這邊大致簡單介紹一下。

Decoupled Head

Ancohr-free

這是目前我覺得較大且效果還不錯的一項改動,在原始YOLO中是使用 Anochor-base的機制,在使用Anochor的機制中會有一些已知的問題,例如為了得到最佳的檢測效果,在訓練之前會需要先做聚類的分析,先找出一組最佳的錨點,但這就會比較沒有彈性,而且會根據資料的類型會有所限制,另外就是增加了預測的複雜性,在邊緣計算的環境中,可能會拖累一些速度。

因此在過往兩年中Ancohr-free迅速發展,方式也很單純,將每個位置的預測數量從3減少到1,並且直接預測四個值,在左上角兩個位置的偏移量,以及預測框的高度和寬度,並且將每個目標的中心設定為正樣本,這樣就能夠減少許多參數量和 GFLOP,除了可以讓速度更快,也獲得了更好的效能。

並且在實務上因為Anchor-free的機制,可以讓訓練資料與測試資料錨框不一致導致框選有偏差的問題大大降低,在實測的結果中發現,框選的效果提升非常多。

大致簡單介紹了一下YOLO家族系列的狀況與License Type後,回來看一下在MATLAB中是如何實現YOLOX的演算法,有一個很好的範例

Detect Defects on Printed Circuit Boards Using YOLOX Network

論文名稱:YOLOX: Exceeding YOLO Series in 2021
Paper參考:https://arxiv.org/abs/2007.01760

使用資料庫論文名稱:A PCB Dataset for Defects Detection and Classification
Paper參考:https://arxiv.org/abs/1901.08204
PCB-DATASET:https://github.com/Ironbrotherstyle/PCB-DATASET

在使用此範例前,請記得去下載Add-On中的補充Library:
Computer Vision Toolbox Automated Visual Inspection Library

Download Pretrained YOLOX Detector(下載訓練好的YOLOX檢測器)

trainedPCBDefectDetectorNet_url = "https://ssd.mathworks.com/supportfiles/"+ ...
"vision/data/trainedPCBDefectDetectorYOLOX.zip";
downloadTrainedNetwork(trainedPCBDefectDetectorNet_url,pwd);
load("trainedPCBDefectDetectorYOLOX.mat");

Download PCB Defect Data Set(下載PCB資料)

dataDir = fullfile(tempdir,"PCBDefects");
downloadPCBDefectData(dataDir)

Perform Object Detection(使用剛載的檢測器與資料預檢視)

%讀影像
sampleImage = imread(fullfile(dataDir,"PCB-DATASET-master","images", ...
"Missing_hole","01_missing_hole_01.jpg"));

%做檢測
[bboxes,scores,labels] = detect(detector,sampleImage);


imshow(sampleImage)
showShape("rectangle",bboxes,Label=labels);
title("Predicted Defects")

Prepare Data for Training(資料前處理)

% 建立ImageDatastore
imageDir = fullfile(dataDir,"PCB-DATASET-master","images");
imds = imageDatastore(imageDir,FileExtensions=".jpg",IncludeSubfolders=true);
% 載入標記資料
annoDir = fullfile(dataDir,"PCB-DATASET-master","Annotations");
fds = fileDatastore(annoDir,ReadFcn=@readPCBDefectAnnotations, ...
FileExtensions=".xml",IncludeSubfolders=true);

annotations = readall(fds);
tbl = struct2table(vertcat(annotations{:}));
blds = boxLabelDatastore(tbl);

classNames = categories(blds.LabelData{1,2})

%整併資料庫
ds = combine(imds,blds);
countEachLabel(blds)

Partition Data(進行資料分割)

% 鎖住
rng("default");


numImages = ds.numpartitions;
numTrain = floor(0.7*numImages);
numVal = floor(0.15*numImages);

shuffledIndices = randperm(numImages);
dsTrain = subset(ds,shuffledIndices(1:numTrain));
dsVal = subset(ds,shuffledIndices(numTrain+1:numTrain+numVal));
dsTest = subset(ds,shuffledIndices(numTrain+numVal+1:end));

Augment Training Data(資料擴增)

dsTrain = transform(dsTrain,@augmentDataForPCBDefectDetection);

Define YOLOX Object Detector Network Architecture(定義YOLOX架構)

% 定義尺寸與使用YOLOX(tiny-coco) 
inputSize = [800 800 3];
detectorIn = yoloxObjectDetector("tiny-coco",classNames,InputSize=inputSize);

Specify Training Options(定義訓練參數)

options = trainingOptions("sgdm", ...
InitialLearnRate=5e-4, ...
LearnRateSchedule="piecewise", ...
LearnRateDropFactor=0.99, ...
LearnRateDropPeriod=1, ...
MiniBatchSize=20, ...
MaxEpochs=300, ...
BatchNormalizationStatistics="moving", ...
ExecutionEnvironment="auto", ...
Shuffle="every-epoch", ...
VerboseFrequency=25, ...
ValidationFrequency=100, ...
ValidationData=dsVal, ...
ResetInputNormalization=false, ...
OutputNetwork="best-validation-loss", ...
GradientThreshold=30, ...
L2Regularization=5e-4);

Train Detector(訓練檢測器)

這邊訓練300個Epochs,使用RTX3090的24GB VRAM的GPU約花六個小時訓練完畢。

doTraining = true;
if doTraining
%dsTrain.UnderlyingDatastores{1,1}.UnderlyingDatastores{1,1}.ReadSize = 2;
%dsTrain.UnderlyingDatastores{1,1}.UnderlyingDatastores{1,2}.ReadSize = 2;
[detector,info] = trainYOLOXObjectDetector(dsTrain,detectorIn,options,"FreezeSubNetwork","none");
modelDateTime = string(datetime("now",Format="yyyy-MM-dd-HH-mm-ss"));
save(fullfile(tempdir,"trainedPCBDefectDetectorYoloX"+modelDateTime+".mat"), ...
"detector");
else
load("trainedPCBDefectDetectorYOLOX.mat");
end

Evaluate Detector(驗證訓練完畢的檢測器效果如何)

detectionResults = detect(detector,dsTest);

metrics = evaluateObjectDetection(detectionResults,dsTest);
precision = metrics.ClassMetrics.Precision;
recall = metrics.ClassMetrics.Recall;
averagePrecision = cell2mat(metrics.ClassMetrics.AP);

classNames = replace(classNames,"_"," ");
table(classNames,averagePrecision)
Model = tiny-coco, Epochs = 300訓練六小時
Model = tiny-coco, Epochs = 650 訓練十三小時
Model = small-coco, Epochs = 500 訓練十一小時

PR curve

class = 3;
plot(recall{class},precision{class})
title(sprintf("Average Precision for '" + classNames(class) + "' Defect: " + "%.2f",averagePrecision(class)))
xlabel("Recall")
ylabel("Precision")
grid on

Evaluate Object Size-based Detection Metrics

testSetObjects = dsTest.UnderlyingDatastores{2};
objectLabels = readall(testSetObjects);
boxes = objectLabels(:,1);
boxes = vertcat(boxes{:});
boxArea = prod(boxes(:,3:4),2);
histogram(boxArea)
title("Bounding Box Area Distribution")
xlabel("Box Area");
ylabel("Count")
boxPrctileBoundaries = prctile(boxArea,100*[1/3,2/3]);
metricsByArea(metrics,[0, boxPrctileBoundaries, inf])

最終效果檢測出來是蠻不錯的,尤其是在檢測框的部分都抓的還蠻準的,有可能是Anchor free的效果還不錯,在檢測小物件上面的精準度也不錯,可以在訓練參數上再進行一些調整,測試看能不能讓整體的效果往上提升。

--

--