Huấn luyện mô hình phát hiện vật thể YOLO bằng ngôn ngữ C# và TorchSharp

Để xây dựng quy trình huấn luyện mô hình YOLO (You Only Look Once) hoàn toàn bằng ngôn ngữ C#, chúng ta có thể dựa vào nền tảng thư viện YoloSharp (phiên bản của IntptrMax). Cần lưu ý phân biệt phiên bản này với các thư viện cùng tên khác chỉ chuyên dùng cho suy luận (inference), vì IntptrMax/YoloSharp hỗ trợ đầy đủ tính năng training thông qua TorchSharp.

Kiến trúc mô hình YOLOv11 trong C#

Dưới đây là đoạn mã minh họa cách triển khai lớp mô hình YoloV11 kế thừa từ cấu trúc YoloV8, sử dụng các thành phần của TorchSharp để định nghĩa mạng nơ-ron.

public class CustomYoloV11 : YoloV8Base
{
    // Chỉ số các tầng đầu ra cần lấy đặc trưng
    protected override int[] LayerIndices => new int[] { 4, 6, 10, 13, 16, 19, 22 };

    public CustomYoloV11(int numClasses = 80, ModelSize size = ModelSize.Nano, torch.Device device = null, torch.ScalarType dtype = null, int[] kptShape = null) 
        : base(numClasses, size, device, dtype, kptShape: kptShape)
    {
    }

    internal override ModuleList<torch.Module> ConstructNetwork(int numClasses, ModelSize size, torch.Device device, torch.ScalarType dtype)
    {
        // Cấu hình thông số theo kích thước mô hình (Nano, Small, Medium, Large, X-Large)
        (float depthMult, float widthMult, int maxChans, bool useC3k) = size switch
        {
            ModelSize.Nano => (0.5f, 0.25f, 1024, false),
            ModelSize.Small => (0.5f, 0.5f, 1024, false),
            ModelSize.Medium => (0.5f, 1.0f, 512, true),
            ModelSize.Large => (1.0f, 1.0f, 512, true),
            ModelSize.XLarge => (1.0f, 1.5f, 768, true),
            _ => throw new ArgumentException("Unsupported model size")
        };

        // Tính toán số kênh và chiều sâu mạng
        this.channels = new int[] { 64, 128, 256, 512, 1024 }.Select(w => Math.Min((int)(w * widthMult), maxChans)).ToArray();
        int depthCount = (int)(2 * depthMult);
        int[] stageChannels = new int[] { channels[2], channels[3], channels[4] };

        var layers = new ModuleList<torch.Module>(
            new ConvBnAct(3, channels[0], 3, 2, device: device, dtype: dtype),
            new ConvBnAct(channels[0], channels[1], 3, 2, device: device, dtype: dtype),
            new C3k2Block(channels[1], channels[2], depthCount, useC3k, e: 0.25f, device: device, dtype: dtype),
            new ConvBnAct(channels[2], channels[2], 3, 2, device: device, dtype: dtype),
            new C3k2Block(channels[2], channels[3], depthCount, useC3k, e: 0.25f, device: device, dtype: dtype),
            new ConvBnAct(channels[3], channels[3], 3, 2, device: device, dtype: dtype),
            new C3k2Block(channels[3], channels[3], depthCount, c3k: true, device: device, dtype: dtype),
            new ConvBnAct(channels[3], channels[4], 3, 2, device: device, dtype: dtype),
            new C3k2Block(channels[4], channels[4], depthCount, c3k: true, device: device, dtype: dtype),
            new SPPF(channels[4], channels[4], 5, device: device, dtype: dtype),
            new C2PSA(channels[4], channels[4], depthCount, device: device, dtype: dtype),

            // Phần Neck: Upsampling và Concatenation
            new Upsample(scale_factor: new double[] { 2, 2 }, mode: UpsampleMode.Nearest),
            new Concat(),
            new C3k2Block(channels[4] + channels[3], channels[3], depthCount, useC3k, device: device, dtype: dtype),

            new Upsample(scale_factor: new double[] { 2, 2 }, mode: UpsampleMode.Nearest),
            new Concat(),
            new C3k2Block(channels[3] + channels[3], channels[2], depthCount, useC3k, device: device, dtype: dtype),

            new ConvBnAct(channels[2], channels[2], 3, 2, device: device, dtype: dtype),
            new Concat(),
            new C3k2Block(channels[3] + channels[2], channels[3], depthCount, useC3k, device: device, dtype: dtype),

            new ConvBnAct(channels[3], channels[3], 3, 2, device: device, dtype: dtype),
            new Concat(),
            new C3k2Block(channels[4] + channels[3], channels[4], depthCount, c3k: true, device: device, dtype: dtype),

            // Phần Head: Phát hiện
            new YoloV8DetectHead(numClasses, stageChannels, false, device: device, dtype: dtype)
        );
        return layers;
    }
}

Chuẩn bị dữ liệu huấn luyện

Dữ liệu đầu vào cho bài toán phát hiện vật thể bao gồm các cặp ảnh và nhãn tương ứng. Mỗi ảnh (ví dụ: image_001.jpg) sẽ đi kèm một file text (ví dụ: image_001.txt) chứa thông tin tọa độ.

Cấu trúc file nhãn tuân theo định dạng chuẩn YOLO với 5 giá trị trên mỗi dòng:

class_id center_x center_y width height

Trong đó:

  • class_id: Số nguyên đại diện cho chỉ số của lớp vật thể.
  • center_x, center_y: Tọa độ tâm của khung bounding box (đã được chuẩn hóa về khoảng [0, 1]).
  • width, height: Chiều rộng và chiều cao của khung bounding box (đã được chuẩn hóa về khoảng [0, 1]).

Việc sử dụng các công cụ hỗ trợ dán nhãn (labeling) giúp tăng tốc độ quy trình này. Người dùng cần đảm bảo tính đa dạng của dữ liệu để tránh việc mô hình bị overfitting vào các bối cảnh cụ thể.

Thiết lập và thực thi huấn luyện

Cấu hình tham số

Trước khi bắt đầu, ta cần khởi tạo các thông số về đường dẫn dữ liệu, cấu hình phần cứng (GPU/CPU) và siêu tham số (hyperparameters) cho quá trình tối ưu hóa.

// Cấu hình đường dẫn dữ liệu
string datasetRoot = @"C:\Datasets\CustomObject";
string trainingDir = @"\train_images";
string validationDir = @"\val_images";
string exportDir = @"C:\Models\Outputs";

// Tham số huấn luyện
int batchSize = 8;
int classCount = 2; // Số lượng lớp cần nhận diện
int totalEpochs = 100;
int inputDim = 640; // Kích thước ảnh đầu vào (resize)

// Ngưỡng phát hiện
float confThreshold = 0.25f;
float iouThreshold = 0.45f;

// Cấu hình thiết bị và mô hình
var deviceType = DeviceType.CUDA; // Sử dụng GPU
var modelSize = ModelSize.Nano;   // Yolo11n
var precision = ScalarType.Float32;
var taskMode = TaskType.ObjectDetection;

Quy trình huấn luyện

Đoạn mã sau thực hiện việc khởi tạo đối tượng huấn luyện, tải trọng số pre-trained (tùy chọn), chạy quá trình học và lưu lại mô hình.

// 1. Khởi tạo tác vụ huấn luyện
var detector = new YoloTask(
    taskMode, 
    classCount, 
    yoloType: YoloVersion.V11, 
    device: deviceType, 
    size: modelSize, 
    dtype: precision
);

// 2. (Tùy chọn) Tải权重 pre-trained để transfer learning
// detector.LoadCheckpoint(@"path\to\pretrained.pt", ignoreSizeMismatch: true);

// 3. Bắt đầu huấn luyện
detector.ExecuteTraining(
    rootPath: datasetRoot,
    trainFolder: trainingDir,
    valFolder: validationDir,
    learningRate: 0.001f,
    outputDir: exportDir,
    imageSize: inputDim,
    batchSize: batchSize,
    epochs: totalEpochs,
    augmentMode: AugmentType.Mosaic
);

// 4. Lưu mô hình đã huấn luyện
detector.ExportModel(@"path\to\custom_model.pt");

Suy luận (Inference)

Sau khi có mô hình, ta có thể sử dụng để dự đoán trên ảnh mới.

string testImagePath = @"C:\Test\sample.jpg";
using var image = Cv2.ImRead(testImagePath);

// Thực hiện dự đoán
var results = detector.Predict(image, confThreshold, iouThreshold);

// Hiển thị kết quả (vẽ bounding box lên ảnh)
foreach (var item in results)
{
    var rect = item.BoundingBox;
    Cv2.Rectangle(image, rect, Scalar.Red, 2);
    Cv2.PutText(image, $"{item.Label} {item.Confidence:F2}", 
                new OpenCvSharp.Point(rect.X, rect.Y - 10), 
                HersheyFonts.HersheySimplex, 0.5, Scalar.Green, 2);
}

Cv2.ImShow("Prediction", image);
Cv2.WaitKey(0);

Đánh giá hiệu năng mô hình

Dưới đây là kết quả kiểm thử trên hai bộ dữ liệu mẫu: viên thuốc (medical-pills) và nhận diện hành động hút thuốc (tùy chỉnh). Quá trình huấn luyện được thực hiện trên cấu hình GPU thông thường trong thời gian ngắn để đánh giá tính khả thi.

Bộ dữ liệu viên thuốc (Public Dataset)

Kích thước tập train Dung lượng Kích thước ảnh Model Size Thời gian train VRAM dùng
98 ảnh ~7 MB 1920x1080 Nano (11 MB) ~10 phút ~9 GB

Với tập dữ liệu chất lượng cao, đặc điểm hình học rõ ràng, mô hình đạt độ chính xác cao trên tập test. Tuy nhiên, khi test trên các ảnh thực tế khác biệt về ánh sáng hoặc góc chụp so với tập train, khả năng tổng quát hóa (generalization) giảm sút do dữ liệu train chưa đủ đa dạng.

Bộ dữ liệu tùy chỉnh (Custom Dataset - Nhận diện hút thuốc)

Kích thước tập train Dung lượng Kích thước ảnh Model Size Thời gian train VRAM dùng
50 ảnh ~4 MB Đa dạng Large (77 MB) ~30 phút ~16 GB

Trên tập dữ liệu nhỏ này, mô hình có xu hướng overfitting. Nó nhận diện được khuôn mặt trong điều kiện tương tự tập huấn luyện nhưng nhầm lẫn giữa các lớp "hút thuốc" và "không hút thuốc" do thiếu dữ liệu mẫu cho từng lớp và tỷ lệ mẫu không cân bằng.

Tóm lại, việc huấn luyện YOLO bằng C# hoàn toàn khả thi. Mặc dù hệ sinh thái Python linh hoạt hơn, nhưng C# mang lại lợi thế lớn trong việc tích hợp giao diện người dùng (UI) và các ứng dụng doanh nghiệp nhờ sự hỗ trợ mạnh mẽ từ Visual Studio và .NET ecosystem.

Thẻ: C# TorchSharp yolo deep learning Object Detection

Đăng vào ngày 16 tháng 6 lúc 21:15