Deep Learning Based Human Pose Estimation Using OpenCV

Lời mở đầu

Để sử dụng được các mô hình trong bài viết này, bạn phải sử dụng phiên bản opencv > 3.4.1.

Pose Estimation là gì?

POST ESTIMATION EXAMPLE - Make by Phạm Duy Tùng

Post Estimation ( đôi khi được dùng với thuật ngữ Keypoint Detection) là một vấn đề khá phổ biến trong lĩnh vực xử lý ảnh khi chúng ta cần xác định vị trí và hướng của một đối tượng. Mức ý nghĩa ở đây là chúng ta phải rút ra được những đặc điểm chính, những đặc điểm đó là những đặc trưng của đối tượng ( có thể mô tả được đối tượng).

Ví dụ, trong bài toán face pose estimation ( có tên khác là facial landmark detection), chúng ta cần xác định được đâu là vị trí của những điểm landmark trên khuôn mặt người.

Một bài toán có liên quan đến bài toán trên là head pose estimation. Chúng ta cần xác định những điểm landmark để mô hình hoá lại được mô hình 3D của đầu người.

Ở trong bài viết này, chúng ta đề cập đến bài toán human pose estimation, công việc chính là xác định và chỉ ra được một phần/ toàn bộ các phần chính của cơ thể con người (vd vai, khuỷu tay, cổ tay, đầu gối v.v).

Trong bài viết này, chúng ta sẽ sử dụng mô hình được huấn luyện sẵn để chỉ ra các phần chính của cơ thể con người. Kết quả cơ bản của phần nhận diện này sẽ gần giống như hình bên dưới.

Hình ảnh rút trích những thành phần quan trọng trên cơ thể con người

Sử dụng pretrain model trong bài toán Pose Estimation

Vào nằm 2016, 2017, Phòng thí nghiệm Perceptual Computing của trường đại học Carnegie Mellon University đã công bố một bài báo có liên quan đến chủ đề Multi-Person Pose Estimation. Và đến nay, họ đã công bố mô hình huấn luyện cho chúng ta sử dụng. Các bạn có nhu cầu tìm hiểu sâu hơn có thể đọc kỹ nguồn dữ liệu của họ công bố ở link https://github.com/CMU-Perceptual-Computing-Lab/openpose.

Trong bài post này, mình sẽ không đề cập kỹ đến phần kiến trúc mạng neural net họ sử dụng bên dưới, thay vào đó, mình sẽ tập trung hơn vào cách thức sử dụng mô hình để thu được kết quả cần thiết.

Trước khi bắt đầu vào thực hành, mình sẽ mô tả một chút về mô hình pretrain có sẵn. Ở đây, họ cung cấp cho chúng ta 2 mô hình là MPII model và COCO model. Đó chính là tên của hai bộ database mà họ sử dụng để đào tạo mô hình. Kết quả trả về của mỗ bộ database là khác nhau hoàn toàn.

Với bộ COCO dataset, kết quả trả về là 18 đặc trưng gồm các thông tin:

1Nose  0, Neck  1, Right Shoulder  2, Right Elbow  3, Right Wrist  4,
2Left Shoulder  5, Left Elbow  6, Left Wrist  7, Right Hip  8,
3Right Knee  9, Right Ankle  10, Left Hip  11, Left Knee  12,
4LAnkle  13, Right Eye  14, Left Eye  15, Right Ear  16,
5Left Ear  17, Background  18

Với bộ MPII, kết quả trả về là 15 đặc trưng gồm các thông tin:

1Head  0, Neck  1, Right Shoulder  2, Right Elbow  3, Right Wrist  4,
2Left Shoulder  5, Left Elbow  6, Left Wrist  7, Right Hip  8,
3Right Knee  9, Right Ankle  10, Left Hip  11, Left Knee  12,
4Left Ankle  13, Chest  14, Background  15

Trong phần này, chúng ta sẽ tập trung vào mô hình MPII, mô hình COCO sử dụng tương tự, chỉ việc thay lại đường dẫn file mô hình là được.

Bắt đầu code.

Bước 1: Download mô hình.

Nhóm tác giả sử dụng caffe để huấn luyện mô hình, do đó, để sử dụng được, chúng ta cần download file mô hình ở đường dẫn http://posefs1.perception.cs.cmu.edu/OpenPose/models/pose/mpi/pose_iter_160000.caffemodel và file cấu hình ở đường dẫn http://posefs1.perception.cs.cmu.edu/OpenPose/models/pose/mpi/pose_deploy_linevec.prototxt. Các bạn có thể để đâu đó tuỳ thích, ở đây tôi để trong thư mục pose/mpi để dễ dàng nhận biết với các mô hình khác.

Bước 2: Load mô hình.

Để load mô hình lên bộ nhớ chính, đơn giản là chúng ta thực hiện câu lệnh sau trong python

1import cv2
2# Specify the paths for the 2 files
3protoFile = "pose/mpi/pose_deploy_linevec_faster_4_stages.prototxt"
4weightsFile = "pose/mpi/pose_iter_160000.caffemodel"
5
6# Read the network into Memory
7net = cv2.dnn.readNetFromCaffe(protoFile, weightsFile)

Đơn giản quá phải không các bạn :).

Bước 3: Đọc ảnh và đưa ảnh vào trong mô hình.

 1
 2# Read image
 3frame = cv2.imread("img2.jpg")
 4
 5frameCopy = np.copy(frame)
 6frameWidth = frame.shape[1]
 7frameHeight = frame.shape[0]
 8t = time.time()
 9# Specify the input image dimensions
10inWidth = 368
11inHeight = 368
12
13# Prepare the frame to be fed to the network
14inpBlob = cv2.dnn.blobFromImage(frame, 1.0 / 255, (inWidth, inHeight), (0, 0, 0), swapRB=False, crop=False)
15
16# Set the prepared object as the input blob of the network
17net.setInput(inpBlob)

Chắc không cần phải nói gì thêm, phần comment chú thích đã mô tả khá đầy đủ chức năng của từng phần trong này rồi.

Bước 4: Thu thập kết quả và trích xuất điểm đặc trưng

 1
 2frameCopy = frame.copy()
 3
 4output = net.forward()
 5print("time taken by network : {:.3f}".format(time.time() - t))
 6H = output.shape[2]
 7W = output.shape[3]
 8
 9nPoints = 15
10POSE_PAIRS = [[0,1], [1,2], [2,3], [3,4], [1,5], [5,6], [6,7], [1,14], [14,8], [8,9], [9,10], [14,11], [11,12], [12,13] ]
11
12
13threshold = 0.01
14# Empty list to store the detected keypoints
15points = []
16for i in range(nPoints):
17    # confidence map of corresponding body's part.
18    probMap = output[0, i, :, :]
19
20    # Find global maxima of the probMap.
21    minVal, prob, minLoc, point = cv2.minMaxLoc(probMap)
22
23    # Scale the point to fit on the original image
24    x = (frameWidth * point[0]) / W
25    y = (frameHeight * point[1]) / H
26
27    print(prob)
28
29    if prob > threshold :
30        cv2.circle(frame, (int(x), int(y)), 15, (0, 255, 255), thickness=-1, lineType=cv2.FILLED)
31        cv2.putText(frame, "{}".format(i), (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 1.4, (0, 0, 255), 2, lineType=cv2.LINE_AA)
32
33        # Add the point to the list if the probability is greater than the threshold
34        points.append((int(x), int(y)))
35    else :
36        points.append(None)
37
38# cv2.imshow("Output-Keypoints",frame)
39# cv2.waitKey(0)
40# cv2.destroyAllWindows()
41
42cv2.imwrite("dot_keypoint.png",frame)
43
44# Draw Skeleton
45for pair in POSE_PAIRS:
46    partA = pair[0]
47    partB = pair[1]
48
49    if points[partA] and points[partB]:
50        cv2.line(frameCopy, points[partA], points[partB], (0, 255, 255), 2)
51        cv2.circle(frameCopy, points[partA], 8, (0, 0, 255), thickness=-1, lineType=cv2.FILLED)
52        cv2.circle(frameCopy, points[partB], 8, (0, 0, 255), thickness=-1, lineType=cv2.FILLED)
53
54
55cv2.imwrite("line_keypoint.png",frameCopy)

Kết quả của giá trị output là một ma trận 4D, với ý nghĩa của mỗi chiều như sau:

  • Chiều đầu tiên là image ID (định danh ảnh trong trường hợp bạn truyền nhiều ảnh vào mạng)
  • Chiều thứ 2 là chỉ số của các điểm đặc trưng. Tập MPI trả về tập gồm 44 điểm dữ liệu, ta chỉ sử dụng một vài điểm dữ liệu tương ứng với vị trí các điểm đặc trưng mà chúng ta quan tâm.
  • Chiều thứ 3 là height của output map.
  • Chiều thứ 4 là width của output map. Một lưu ý ở đây là tôi có sử dụng đặt giá trị chặn dưới threshold để giảm thiểu sự sai sót do nhận diện sai. Và kết quả đạt được là hai hình bên dưới:

Hình nhữn điểm đặc trưng

Hình khung xương

Hẹn gặp lại các bạn ở những bài viết tiếp theo.

Comments