1. Mục tiêu

Tôi cần nhận dạng các vật thể căn bản sử dụng camera máy tính. Chương trình chạy viết bằng C++ để tối ưu tốc độ trên máy tính không có GPU. Số lượng vật thể cần nhận dạng khoảng 600 loại từ thư viện ảnh Open Images V7

Việc đầu tiên tôi nghĩ ngay đến là Yolo, thư viện nhận dạng rất nổi tiếng liên tục được nâng cấp phiên bản.
Yolo gần đây V8,9,11, 12 do Ultralytics ngôn ngữ lập trình giao tiếp là Python. Còn thư viện lõi bên trong là Pytorch. Pytorch được viết bằng C/C++ những phần quan trọng và sử dụng hầu hết các thư viện C++ tốc độ cao như C++ (ATen / LibTorch), CUDA (C++ + CUDA), Cython / Pybind11, Assembly. Như vậy Yolo có tốc độ xử lý rất tốt nếu chạy trên các thiết bị có GPU, thư viện CUDA xử lý ma trận song song.

Tuy nhiên khi bạn porting lên thiết bị di động không có GPU, và không thể cài Python thì việc dùng Yolo thuần sẽ có nhiều bất cập. Do đó chúng ta sẽ dùng một thư viện C/C++ khác để thực hiện suy luận dùng lại cấu trúc mạng neural và các tham số do Yolo đào tạo trước đó. Thư viện ncnn do Tencent phát triển được chọn vì nó viết bằng C/C++ tối ưu tốc độ cho các thiết bị chạy CPU Intel, ARM .
Tuy nhiên để ncnn chạy được, chúng cần chuyển đổi định dạng cấu trúc mạng neural và các tham số từ Yolo sang ncnn.

2. Một số khái niệm

🧠 YOLO (You Only Look Once)

Khái niệmGiải thích đơn giản
Bounding BoxHộp giới hạn đối tượng (tọa độ x, y, width, height).
Confidence ScoreMức tin cậy mô hình cho rằng có đối tượng tại bounding box.
Class ProbabilityXác suất đối tượng thuộc một lớp cụ thể (xe, người, chó…).
Anchor BoxMẫu hộp để mô hình học cách xác định kích thước đối tượng.
NMS (Non-Max Suppression)Loại bỏ các bounding box bị trùng lặp, chỉ giữ lại cái tốt nhất.
Input Size (imgsz)Kích thước ảnh đầu vào cho model (ví dụ: 320x320).

⚙️ ncnn (Inference Engine)

ncnn là một framework inference AI nhẹ (lightweight neural network inference framework) mã nguồn mở do Tencent phát triển, tối ưu để chạy mô hình deep learning trên thiết bị di động (Android, iOS), nhúng, và edgekhông cần GPU.

✅ Đặc điểm chính:

  • Viết bằng C++, không phụ thuộc vào hệ thống runtime bên ngoài.
  • Chạy tốt trên CPU ARM (và x86), đặc biệt tối ưu cho thiết bị di động.
  • Không cần thư viện bên ngoài như OpenBLAS hay MKL.
  • Tối ưu SIMD: hỗ trợ NEON (ARM), AVX (x86), và cả Vulkan (GPU nhẹ).
  • Hỗ trợ nhiều kiến trúc mô hình phổ biến: MobileNet, YOLO, ResNet, ShuffleNet, EfficientNet, v.v.
  • Có công cụ chuyển đổi từ PyTorch/ONNX sang NCNN (onnx2ncnn).

📦 Thành phần chính:

Thành phầnVai trò
ncnn::NetMô hình đã load, dùng để chạy inference.
ncnn::MatLưu ảnh hoặc tensor đầu vào / đầu ra.
ExtractorCông cụ lấy dữ liệu vào và trích xuất kết quả từ mô hình.
.param + .binFile cấu trúc mạng + trọng số đã được ncnn chuyển đổi.
Khái niệmGiải thích đơn giản
Param file (.param)Mô tả kiến trúc mạng.
Bin file (.bin)Trọng số của mạng.
ExtractorThành phần dùng để truyền dữ liệu vào và lấy kết quả ra từ mô hình.
ncnn::MatKiểu dữ liệu lưu ảnh hoặc tensor cho NCNN.
convert-to-ncnnQuá trình chuyển model từ PyTorch/ONNX sang định dạng .param và .bin.
set_num_threads(n)Thiết lập số luồng xử lý để tăng tốc.

🖼️ Open Images V7 (Dataset)

Khái niệmGiải thích đơn giản
Class LabelTên lớp đối tượng (ví dụ: Person, Car, Laptop…).
BBox CSVFile chứa toạ độ các bounding box và tên lớp.
Hierarchical LabelsMỗi class có thể có class cha (ví dụ: Vehicle > Car).
ImageIDMã định danh duy nhất cho mỗi ảnh trong tập dữ liệu.
Train/Test SplitBộ ảnh đã được phân tách để huấn luyện và kiểm thử.

📌 Dữ liệu OpenImages chủ yếu dùng để huấn luyện model, không dùng trực tiếp trong nhận dạng.


🔧 OpenCV (Tiền xử lý & Hậu xử lý)

📌 Hàm tiền xử lý ảnh

HàmMục đích
cv::imread(path)Đọc ảnh từ file.
cv::resize(img, size)Thay đổi kích thước ảnh.
cv::cvtColor(img, COLOR_BGR2RGB)Chuyển ảnh từ BGR sang RGB (nếu cần).
img.convertTo(img, CV_32F, 1/255.0)Chuyển ảnh về float và chuẩn hóa 0-1.
cv::Mat::ptr()Truy cập dữ liệu ảnh để copy vào NCNN.

📌 Hàm hậu xử lý (hiển thị và kết quả)

HàmMục đích
cv::rectangle(img, rect, color)Vẽ bounding box lên ảnh.
cv::putText(img, label, point, font, size, color)Ghi nhãn lớp lên ảnh.
cv::imshow("result", img)Hiển thị ảnh kết quả.
cv::waitKey(0)Đợi người dùng nhấn phím để đóng cửa sổ ảnh.

Quy trình nhận dạng cho người mới (Tóm tắt)

  1. Đọc ảnh bằng cv::imread.
  2. Tiền xử lý ảnh: resize, chuyển màu, chuẩn hóa.
  3. Nạp ảnh vào ncnn::Mat, chạy model.
  4. Nhận kết quả: bounding boxes, class, confidence.
  5. Áp dụng NMS, lọc kết quả tốt.
  6. Dùng OpenCV để vẽ kết quả lên ảnh và hiển thị.

3. Các bước cài đặt trên máy Mac M1 (Linux cũng gần tương tự)

3.1 Chuẩn bị môi trường Python

Vì ultralytics được viết bằng Python do đó cần cài đặt công cụ quản lý các thư viện và tạo môi trường Python là uv:

curl -LsSf https://astral.sh/uv/install.sh | sh
uv --version

Tạo môi trường ảo

uv venv
source .venv/bin/activate

3.2 Cài đặt thư viện ultralytics

Thư viện ultralytics dùng để chuyển đổi định dạng cấu trúc mạng, tham số từ Yolo sang ncnn.

uv pip install ncnn
uv pip install ultralytics

3.4 Tải model đã được huấn luyện sẵn

Tải model Google Open Images V7 Pretrained Models từ trang này https://docs.ultralytics.com/datasets/detect/open-images-v7/. Google Open Images V7 gồm 600 loại đồ vật đã được huấn luyện nhận dạng.

yolo export model=yolov8x-oiv7.pt format=ncnn imgsz=320 half=True

4. Lập trình C++

4.1 Cài bổ xung các thư viện C++ cần dùng

Cài thư viện opencv để lấy từng khung hình từ camera

brew install opencv

Cài thư viện ncnn viết bằng C++ dùng để nạp pretrained model được ra ở bước 3 rồi suy luận

brew install ncnn

thư viện glslang để tận dụng GPU trên máy thực hiện phép tính ma trận. Thư viện ncnn sẽ dùng glslang

brew install glslang

4.2 Lập trình ứng dụng C++

Mã nguồn dự án ở đây https://github.com/techmaster-vietnam/ObjectDetection

Đầu tiên mình phải nói về bản quyền tác giả code này. Mình giao cho một sinh viên thực tập code, bạn ý lên mạng tìm được đâu đó code, hoặc có thể hỏi AI để sinh mã. Ứng dụng chạy thử với model dataset Coco nhưng rất chậm. Mình tiếp nhận dự án với mục tiêu tăng tốc. Mình thực hiện một số cải tiến:

  1. Đọc danh sách các label từ file *.yaml
  2. Tăng tốc thực hiện nhận dạng từng khung hình. Mình phát hiện ra trong vòng lặp, code cũ liên tục đọc file model để nạp vào điều này tạo ra độ trễ rất lớn. Có lẽ code ban đầu chỉ là nhận dạng một file ảnh tĩnh xuất ra ảnh được vẽ các bounding box và label nhận dạng rồi ghi lên ảnh.
  3. Thay vì nhận dạng tất cả các vật thể thì chỉ tập trung nhận dạng vật thể gần tâm chính giữa màn hình nhất. Tại sao? Bởi dự án này chạy trên thiết bị di động, người dùng sẽ hướng camera về phía đối tượng cần nhận dạng cho đến khi khoanh vùng được vật thể mình quan tâm hơn là cố gắng nhận dạng 20 cái xe ô tô trong khung hình.
  4. Tiến tới loại bỏ thư viện OpenCV viết thay thế bằng những hàm tự viết để không cần phải cài đặt OpenCV vào thiết bị mobile. Ở dự án khác, bạn dev bên mình đã làm được.

4.2 Cách chạy thử mã nguồn của mình

Trong thư mục .vscode có file launch.json: Hãy chỉnh lại tham số args

   "args": ["/Users/cuong/Desktop/oiv7"],

sang đường dẫn đến thư mục chứa model Yolo đã được huấn luyện. Thư mục này cần có những file sau:

  • metadata.yaml
  • model_ncnn.py
  • model.ncnn.bin
  • model.ncnn.param

4.3 Kết quả chạy thử

Mình chỉ vẽ những hình này sau khi ứng dụng nhận dạng được hầu hết các vật dung trong phòng làm việc của mình.
Rùa
Thỏ
Xe tải
Thuyền
Mặt người
Nhận dạng nhầm cặp môi thành chim. Một phần vì ít khi ảnh huấn luyện chỉ chụp môi riêng lẻ
Môi

5. Kết luận

Thư viện ncnn kết hợp với Yolo nhận dạng vật thể rất nhanh, với 600 loại vật thể cũng không vấn đề gì.