Để triển khai mô hình phát hiện đối tượng xoay (Oriented Bounding Box – OBB) dựa trên kiến trúc YOLOv8 trong môi trường C++, chỉ sử dụng thư viện cv::dnn của OpenCV, bước đầu tiên là có được mô hình ở định dạng ONNX — có thể từ mô hình tiền huấn luyện chính thức hoặc từ phiên bản đã huấn luyện riêng.
Mô hình OBB thường nhận đầu vào dạng tensor kích thước 1×3×H×W (ví dụ: 1×3×640×640), và trả về đầu ra dạng ma trận kích thước 1×7×N, trong đó N là số lượng đề xuất (thường là 8400). Mỗi hàng tương ứng với một hộp xoay, gồm 7 giá trị theo thứ tự: tọa độ tâm (cx, cy), chiều rộng w, chiều cao h, góc xoay θ (đơn vị radian, nằm trong khoảng [−π/4, 3π/4]), và hai giá trị xác suất phân lớp (hoặc một giá trị confidence + chỉ số lớp tùy cấu trúc).
Sau khi tải mô hình và ảnh, ta chuẩn bị blob đầu vào:
cv::Mat inputImage = cv::imread("input.jpg");
cv::Mat normalizedBlob;
cv::dnn::blobFromImage(
inputImage,
normalizedBlob,
1.0 / 255.0,
cv::Size(640, 640),
cv::Scalar(0, 0, 0),
true, // swapRB
false // crop
);
net.setInput(normalizedBlob);
std::vector inferenceOutputs;
net.forward(inferenceOutputs, net.getUnconnectedOutLayersNames());
cv::Mat detectionTensor = inferenceOutputs[0].reshape(1, inferenceOutputs[0].size[2]); // reshape to [N x 7]
Để quản lý kết quả một cách rõ ràng, định nghĩa cấu trúc dữ liệu phù hợp:
struct RotatedBoxResult {
int labelId;
std::string labelName;
float score;
cv::Scalar drawColor;
cv::Point2f center;
cv::Size2f size;
float angleRad;
cv::RotatedRect rotatedRect() const {
return cv::RotatedRect(center, size, angleRad * 180.0f / CV_PI);
}
};
Với mỗi dòng trong detectionTensor, trích xuất các thành phần tương ứng và suy ra nhãn lớp bằng cách tìm giá trị lớn nhất trong vector xác suất:
std::vector<RotatedBoxResult> results;
const float* rowPtr = detectionTensor.ptr<float>(0);
for (int i = 0; i < detectionTensor.rows; ++i) {
const float* row = detectionTensor.ptr<float>(i);
cv::Point2f center(row[0], row[1]);
cv::Size2f dims(row[2], row[3]);
float theta = row[4];
float conf = row[5];
int clsIdx = static_cast<int>(row[6]);
if (conf < 0.3f) continue; // ngưỡng lọc
RotatedBoxResult item;
item.center = center;
item.size = dims;
item.angleRad = theta;
item.score = conf;
item.labelId = clsIdx;
item.labelName = classNames.at(clsIdx);
item.drawColor = cv::Scalar(
(rand() % 156) + 100,
(rand() % 156) + 100,
(rand() % 156) + 100
);
results.push_back(item);
}
Cuối cùng, để trực quan hóa kết quả, duyệt qua results và vẽ từng cv::RotatedRect lên ảnh gốc bằng cv::polylines hoặc cv::drawContours sau khi chuyển đổi sang đa giác 4 đỉnh.