Gán nhãn dữ liệu ảnh “tự động” với pretrain model

Hôm nay chúng ta sẽ cùng nhau tìm hiểu về việc Gán nhãn dữ liệu ảnh “tự động” với pretrain model YOLOv4.

Lâu lắm rồi mình mới lại viết bài trên Mì AI. Lý do là anh em dạo này thích xem video hơn và yêu cầu mình làm video nhiều hơn để anh em theo dõi cho tiện (anh em có thể join kênh Youtube: https://youtube.com/c/MiAIblog ). Hôm nay tranh thủ có bài mới, mình quay lại viết lách chút cho khỏi lụt nghề 😀

Và bây giờ thì….bắt đầu thôi!

Vấn đề hôm nay

Hôm nay chúng ta sẽ tìm hiểu một vấn đề đau đầu của các anh em kiểu gì cũng gặp phải khi xây dựng bài toán AI, khi train model AI. Đó là vấn đề về gán nhãn dữ liệu.

Khi train một model AI, kiểu gì anh em cũng phải có dữ liệu. Khi train một model supervised learning, thì kiểu gì cũng phải gán nhãn.

  • Ví dụ khi train model phân loại chó mèo, anh em sẽ phải có một tập ảnh cả chó, mèo và gán nhãn bằng tay xem đâu là chó, đâu là mèo.
  • Ví dụ khác khi train model phát hiện các loại chim, thì anh em sễ phải có một tập ảnh chim, sau đó gán bounding box cho từng ảnh trong đó. Cụ thể là dùng tool gán nhãn, ví dụ LabelMe, LblImage… để lướt qua từng ảnh, bôi một hình chữ nhật quanh vật thể (con chim á) và đánh nhãn cho vật thể đó (chim sẻ, chim cú, chim quạ….)
Nguồn: Tại đây

Việc gán nhãn là việc phải làm. Các công ty AI sẽ có team gán nhãn riêng, hoặc thuê ngoài outsource nên không có vấn đề gì. Nhưng điều gì sẽ xảy ra với các bạn làm cá nhân để demo, làm đồ án tốt nghiệp….? Đa phần là các bạn sẽ cực nản với vấn đề gán nhãn vì nó tiêu tốn quá nhiều thời gian và không mang lại value gì về mặt chuyên môn cả.

Do đó, hôm nay mình muốn giới thiếu cho anh em một pipeline cơ bản để anh em có thể áp dụng để giảm tải cho công tác gán nhãn. Mình nhấn mạnh đây là pipeline cơ bản của mình, anh em tham khảo để đưa ra các pipeline phức tạp hơn, phù hợp hơn với bài toán của anh em nhé.

Bài toán gán nhãn dữ liệu ảnh Chim

Bài toán giả định của mình là mình kiếm được 1 data khoảng 200 ảnh (ví dụ 200 thôi nhé, anh em tưởng tượng là 2K, 20K….) chim trên mạng gồm 03 loài chim: Black_footed_Albatross, Laysan_Albatross và Sooty_Albatross (tạm dịch theo Google Transalte: Chim hải âu chân đen, Chim hải âu Laysan, Chim hải âu đậu nành (WTH???, kệ nó nhé anh em)).

gán nhãn dữ liệu

Bây giờ mình muốn train một model phát hiện 3 loại chim trên. Khi đưa một ảnh vào, model phải khoanh được đâu là chim và hiện nhãn đó là chim gì. Và công tác đầu tiên là gán nhãn nào!

Pipeline gán nhãn bằng Pretrain Model

Ý tưởng

Chúng ta sẽ làm theo pipeline như sau:

pipeline gán nhãn dữ liệu

Cụ thể như sau:

  • Bước 1: Chuẩn bị data gồm 3 loài chim (chưa được gán nhãn) để vào trong folder “data”.
  • Bước 2: Sử dụng YOLOv4 (pretrained model) để nhận diện chim trong ảnh và ghi lại file nhãn. Chú ý ở bước này là model chỉ nhận diện và đóng khung chữ nhật được Chim, không phân biệt được Chim gì nhé.
  • Bước 3: Như vậy tại bước này chúng ta đã có các file nhãn nhưng chỉ có một class là Chim thôi. Cơ mà nhớ là nó đã đóng khung cho chúng ta rồi, đỡ mệt bao nhiêu đấy. Tại đây chúng ta sẽ có 2 cách đi C1 và C2
  • Bước 4: Cách C1. Với cách này chúng ta sử dụng các tool gán nhãn sẵn có như LabelMe, LabelImage…để gán lại nhãn. Cụ thể là sửa từ nhãn Bird sang các nhãn Black_footed_Albatross, Laysan_Albatross và Sooty_Albatross. Về tool này thì mình đã có nói nhiều trong các bài về YOLO trên Mì AI rồi, trong bài này mình sẽ không nói nữa.
  • Bước 5: Cách C2. Với cách này mình sẽ code một đoạn chương trình Python đọc các ảnh đã được gán nhãn ở Bước 2. Hiện lên màn hình và cho ta gán lại nhãn mới và ghi lại vào file nhãn. Cách này có ưu điểm so với Bước 4 là ta gán chỉ bằng phím bấm, ko cần chọn ảnh mà ảnh tự động load… Tất nhiên đó là đánh giá chủ quan của mình, nếu các bạn thấy cách C1 ngon hơn thì cứ xài nhé.

Rồi. Lý thuyết đã xong. Bắt tay vào làm!

Gán nhãn dữ liệu thô bằng YOLOv4

Chú ý,

Chúng ta sẽ lưu data vào thư mục và code đoạn lệnh để đọc từng ảnh, đưa qua model YOLO để gán nhãn:

# Bat dau doc tu thư mục
for file in os.listdir(data_path):

    if file[-3:] != "jpg":
        continue
    # Doc frame
    image = cv2.imread(os.path.join(data_path, file))
    
    # Resize va dua khung hinh vao mang predict
    image_width = image.shape[1]
    image_height = image.shape[0]

    blob = cv2.dnn.blobFromImage(image, scale, (416, 416), (0, 0, 0), True, crop=False)
    net.setInput(blob)
    outs = net.forward(get_output_layers(net))Code language: PHP (php)

Sau đó chúng ta sẽ thực hiện check kết quả đầu ra, lấy 5 kết quả có độ tin cậy Conf > 0.5 và thực hiện Non Max Supression (NMS) để loại bỏ các bounding box chồng lấn nhau:

    for out in outs:
        for detection in out:
            scores = detection[5:]
            class_id = np.argmax(scores)
            confidence = scores[class_id]
            if confidence > conf_threshold:
                center_x = int(detection[0] * image_width)
                center_y = int(detection[1] * image_height)
                w = int(detection[2] * image_width)
                h = int(detection[3] * image_height)
                x = center_x - w / 2
                y = center_y - h / 2
                class_ids.append(class_id)
                confidences.append(float(confidence))
                boxes.append([x, y, w, h])

    indices = cv2.dnn.NMSBoxes(boxes, confidences, conf_threshold, nms_threshold)

Tiếp theo chúng ta sẽ duyệt trong các bounding box còn lại và ghi chúng vào file nhãn. Chú ý rằng tên file nhãn giống với tên file ảnh (chỉ khác phần mở rộng).

# Ve cac khung chu nhat quanh doi tuong
label_file = os.path.join(data_path, file[:-3] + "txt")
with open(label_file, "w") as f:
for i in indices:
i = i[0]
box = boxes[i]
x = box[0]
y = box[1]
w = box[2]
h = box[3]
draw_prediction(image, class_ids[i], round(x), round(y), round(x + w), round(y + h))

# Ghi vào file nhãn
f.write("{} {} {} {} {}\n".format(0, x / image_width, y/image_height, w/image_width, h/image_height))

Bạn để ý một số điểm ở đây:

  • Mình ghi hết ClassID trong file nhãn là 0 vì mình sẽ thực hiện gán lại nhãn sau bằng tay.
  • Đây là bài toán train YOLO nên nhãn cũng theo cấu trúc của YOLO nhé. Các bạn xem lại các bài YOLO trên Mì AI để hiểu thêm về cấu trúc file nhãn này. Trong thực tế khi bạn làm bài toán riêng thì sẽ ghi file nhãn theo bài toán đó.

Okie! Sau bước này bạn sẽ có một folder có cả ảnh, có cả nhãn như này:

gán nhãn dữ liệu

Mã nguồn mình để trong file label_by_pretrain.py nhé anh em!

Code Python để gán lại nhãn

Bây giờ mình sẽ code một đoạn Python code để load các ảnh lên màn hình, vẽ bounding box và sau đó cho người dùng nhập Class Name để cập nhật lại nhãn.

Ví dụ: Load một bức ảnh có 1 con chim đang được gán nhãn thô, sau đó người dùng nhập vào là Black footed Albatros hay Black thôi chẳng hạn. Code sẽ ghi lại ID của nhãn vào file txt. Lưu ý là việc quy định nhập gì tuỳ bạn code nhé. Ở đây mình quy định là nhập B, L và S.

Đầu tiên là đoạn code lặp và load ảnh, đọc file nhãn để biết được bounding box:

for file in os.listdir(data_path):

    if file[-3:] != "jpg":
        continue
    
    print("File anh hien tai: ", file)

    image = cv2.imread(os.path.join(data_path, file))
    image_width = image.shape[1]
    image_height = image.shape[0]

    label_file = os.path.join(data_path, file[:-3] + "txt")

    boxes = None
    if os.path.exists(label_file):
        with open(label_file, "r") as f:
            boxes = f.readlines()Code language: JavaScript (javascript)

Okie rồi, bây giờ vẽ bounding box đó lên ảnh:

    if len(boxes)>0:
        with open(label_file, "w") as f:
            for box in boxes:
                box = box.replace("\n","").split()
                print(box)

                x = float(box[1]) * image_width
                y = float(box[2]) * image_height
                w = float(box[3]) * image_width
                h = float(box[4]) * image_height
                draw_prediction(image, round(x), round(y), round(x + w), round(y + h))

                cv2.imshow("object detection", image)
                cv2.waitKey(1)Code language: JavaScript (javascript)

Sau khi vẽ xong ta sẽ dùng lệnh Input của Python để cho người dùng nhập Class Name, từ Class Name đó ta map vào 1 dictionary để ra ID :


class_idx = {
"S": 0, "L":1,"B":2
}

re_class = input("Class: ")
if re_class == '!':
                    break
else:
                    f.write("{} {} {} {} {}\n".format(class_idx[re_class], box[1], box[2], box[3], box[4]))Code language: PHP (php)

Đoạn trên mình quy định nhập vào “!” là sẽ thoát.

gán nhãn dữ liệu

Mã nguồn mình để trong file relabel.py nhé anh em!

Kết quả gán nhãn dữ liệu

Sau khi thực hiện gán nhãn qua các ảnh. Chúng ta sẽ có các file Label được gán nhãn ngon lành, với các ClassID chuẩn chỉ và sẵn sàng train YOLO nhận diện các loài chim rồi.

Cách train YOLO thì đã có nhiều trên Mì AI. Các bạn chủ động tham khảo nhé.

Chú ý: Bài này minh hoạ việc triển khai một pipeline auto labeling và pipeline này chỉ đúng với nhu cầu train YOLOv4, nếu bạn làm các model khác, bạn cần đổi lại code chút xíu. Code trên là sample nên còn nhiều chỗ mình chưa handle hết các exception, các bạn sửa thêm khi sử dụng.

Mình xin chia sẻ cùng các bạn source của bài này tại đây: https://github.com/thangnch/MIAI_AutoLabel_Pipeline

Chúc các bạn thành công!

#MìAI

Fanpage: http://facebook.com/miaiblog
Group trao đổi, chia sẻ: https://www.facebook.com/groups/miaigroup
Website: https://miai.vn
Youtube: http://bit.ly/miaiyoutube

Related Post

Leave a Reply

Your email address will not be published. Required fields are marked *