Phát hiện và làm mờ mặt tự động như Japan AV

Hello anh em , hôm nay chúng ta sẽ cùng nhau thử nghiệm một bài toán phát hiện khuôn mặt và làm mờ mặt như trong các phim tư liệu Nhật Bản quen thuộc với anh em.

Bắt đầu thôi!

Bài này cũng khá đơn giản, mình làm theo yêu cầu của bạn sinh viên HaUI, hi vọng có thể giúp đỡ được bạn.

Phần 1 – Mở được webcam và show được ảnh lên màn hình

Trong bài này chúng ta sẽ làm bài toán đọc ảnh từ webcam và làm mờ. Mình cũng định làm mờ mấy cái phim Nhật cho nó trực quan đó mà sợ tải về bị hiểu lầm thì chết =)).

Đầu tiên các bạn tạo một project và tiến hành cài đặt OpenCV theo lệnh

pip install opencv-python

Okie sau đó tạo một file blurface.py và gõ các lệnh hết sức cơ bản sau để mở được camera lên:

import cv2

# Mở camera, số 0 là camera đầu tiên, nếu không mở được các bạn có thể thay bằng các số khác từ 1,2,3
cam = cv2.VideoCapture(0)
while True:
    # Đọc ảnh từ camera, tham số ret cho biết đọc thành công hay không
    ret, image = cam.read()
    
    # Nếu đọc thành công
    if ret:
        # hiển thị lên màn hình
        cv2.imshow("Output", image)
        
        # nếu bấm q thì thoát
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

cam.release()
cv2.destroyAllWindows()Code language: PHP (php)

Xong rồi! Đợt này có nhiều bạn hỏi về tham số ret trong lệnh đọc từ camera nên mình cũng giải thích kỹ trong phần comment bên trên rồi nhé.

Nếu bạn thấy camera hiện lên như hình dưới là đã okie con dê rồi!

Ngoài ra bạn nào muốn tìm hiểu chi tiết về OpenCV thì mình có video trên kênh Youtube: https://youtube.com/c/MiAIblog nhé!

Phần 2 – Tìm hiểu về SSD và phát hiện khuôn mặt bằng pretrain

Để làm mờ khuôn mặt được thì ta phải phát hiện được khuôn mặt trong ảnh. Để phát hiện khuôn mặt có nhiều cách lắm: Harrcascade, MTCNN, Dlib và SSD. Mấy cách đầu thì trên Mì AI đã dùng nhiều nên hôm nay mình demo cách dùng pretrain ResnetSSD để phát hiện khuôn mặt.

SSD viết tắt của Single Shot Multibox Detector (tạm dịch: một phát ăn ngay trong công tác detection ). Nó có nét tương đồng với YOLO (You Only Look Once). Nó chỉ thực một một pha duy nhất thay vì 2 pha như các thuật toán cũ hơn (chi tiết tại: https://miai.vn/2020/07/04/co-ban-ve-object-detection-voi-r-cnn-fast-r-cnn-faster-r-cnn-va-yolo/).

Kiến trúc của mạng SSD như sau:

ssd
Nguồn: Tại đây

Trước khi đi tiếp mình lại xin nhấn mạnh đây là giải thích kiểu Mì ăn liền, cho các bạn mới không có nhu cầu đi sâu về toán, mạng NN để tránh tàu hoả nhập ma. Các bạn biết rồi xin bỏ qua hoặc bổ sung giúp.

Nhìn qua chúng ta thấy có 2 phần. Phần đầu là trích đặc trưng từ ảnh đầu vào (màu trắng). Phần này có thể dùng VGG16, Resnet…(nhưng được lược bỏ đi phần Fully Connected). Sau đó nối vào phần 2 là các SSD Layers. Bằng cách sử dụng các filter, các layer SSD này có kích thước khác nhau (giảm dần) để có thể nhận diện được vật thể ở các kích thước to, nhỏ khác nhau.

Sau khi qua các lớp SSD thì có tổng cộng 8732 khung dự đoán được output ra cho mỗi lớp (ví dụ: lớp chó, mèo…). Sau đó đi qua thuật toán NMS (Non Max Supression) để cô đọng lại 200 khung dự đoán tốt nhất (dựa vào probality, IoU…)

ssd
Nguồn: Tại đây

Đó, và chúng ta sẽ for loop trong 200 output này để tìm ra các khuôn mặt trong ảnh.

Tất nhiên, giống như YOLO, muốn nhận diện gì thì ta phải train cái đó (ví dụ: chó, mèo, lợn, gà…). Bài này cũng vậy, muốn nhận diện được đâu là mặt thì ta cũng phải train chứ. Nhưng để đơn giản bài này mình sẽ dùng pretrain sẵn có trên mạng. Mình xin hẹn các bạn bài train SSD vào một bài khác, sẽ có video để các bạn dễ làm theo hơn.

Pretrain mình tải sẵn cho các bạn trên github luôn nhé. Chúng ta sẽ bỏ pretrain models vào thư mục models và load nó lên như sau:


def load_face_models():

	# Link: https://github.com/LZQthePlane/Face-detection-base-on-ResnetSSD

	txt_file = os.path.join("models", "deploy.prototxt.txt")
	weight_file = os.path.join("models", "res10_300x300_ssd_iter_140000.caffemodel")

	model = cv2.dnn.readNet(txt_file, weight_file)
	return modelCode language: PHP (php)

Phần này mình tham khảo từ github trên mạng nên cũng để nguyên link để các bạn tham khảo thêm nếu cần.

Sau khi load xong model, để nhận diện khuôn mặt chúng ta sẽ tiến hành đưa ảnh từ camera vào và sau đó tiến hành predict, vẽ khung chữ nhật màu xanh lên mặt.

Đầu tiên load model nào:

model  = load_face_models()

Tiếp theo, chúng ta sẽ viết thêm đoạn code đưa ảnh vào mạng, nhận diẹn, lấy đầu ra, lấy toạ độ và vẽ khung hình chữ nhật:

 # Lấy kích thước ảnh
        (h, w) = image.shape[:2]

        # Đưa vào mạng để phát hiện khuôn mặt
        blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), (104.0, 177.0, 123.0))
        model.setInput(blob)
        detections = model.forward()

        # Lap qua ket qua dau ra
        for i in range(0, detections.shape[2]):

            # Lay confidence
            confidence = detections[0, 0, i, 2]
            print(detections[0, 0, i])
            # Neu confiden > 0.5 moi xu ly
            if (detections[0, 0, i, 1] == 1) and (confidence > 0.5):
                # Lay toa do that
                box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
                (startX, startY, endX, endY) = box.astype("int")
                
                # Vẽ khung hình chữ nhật
                cv2.rectangle(image,(startX,startY),(endX,endY),(0,255,0),5)
Code language: PHP (php)

Đoạn này các bạn chú ý một số điểm sau:

  • Thứ nhất, Do kiến trúc mạng SSD input là 300,300 nên ảnh cần resize về kích thước đó trước khi đưa vào. Chú ý đoạn (300,300) trong lệnh blobfromImage
  • Thứ hai, do mạng này pretrain với ImageNet nên chúng ta cũng phải mean subtraction để ảnh đầu vào của chúng ta tương tự như khi họ train. Đoạn này nè: (104.0, 177.0, 123.0). Bạn nào cần tìm hiểu thêm thì có link đây
  • Thứ ba, mình chỉ xử lý với các predict có prob > 0.5 nhé. Ngưỡng này tuỳ các bạn cài đặt sao cho phù hợp với bài toán của các bạn.

Nếu các bạn chạy ra như này là thành công nè:

ssd face detection

Phần 3 – Thuật toán làm mờ mặt pixelate

Đến đây. Chúng ta hoàn toàn có thể dùng thuật toán làm mờ Gaussian Blur để làm mờ mặt với đoạn code:

                # Lay phan khuon mat
                face = image[startY:endY, startX:endX]

                # Blur
                face = cv2.blur(face,ksize=(45,45))

                # Ve de phan blur len ảnh gốc
                image[startY:endY, startX:endX] = faceCode language: PHP (php)

Và ta sẽ có kết quả như sau:

làm mờ mặt ssd

Tạm thời cũng che được mặt đó nhưng mà không chuyên nghiệp như mấy anh Nhật nên mình vẫn chưa khoái lắm. Cải tiến chút, đây là lúc mình cần thuật toán làm mờ theo kiểu Pixelate như này:

làm mờ mặt
Nguồn: Youtube

Rồi bây giờ ta sẽ nói qua về cách làm mờ:

  • Đầu tiên, các bạn xác định sẽ chia vùng mặt ra thành một lưới chữ nhật N x M ô vuông. Tưởng tượng thôi nhé, ko vẽ ra đâu.
  • Tiếp theo, chúng ta lặp qua từng ô vuông ảo trên lưới ảo đó. Với mỗi ô vuông chúng ta sẽ tính toán trung bình cộng các điểm ảnh thuộc ô vuông đó để ra một cái màu X nào đó.
  • Cuối cùng ta điền màu X đó vào ô vuông trên lưới. Cứ làm như vậy với tất cả các ô vuông ta sẽ có một vùng mặt N x M được xoá nhoà 😀

Việc chọn N x M sẽ quyết định mặt bị xoá nhiều hay ít. NxM càng nhỏ, mặt càng nhoè và ngược lại.

Trong bài này, mình chọn NxM dựa vào kích thước ảnh đầu vào, ảnh to thì NxM to và ngược lại. Đây là đoạn chương trình để làm mờ một vùng ảnh:

def pixelate_image(image, grid_size):
	# Chia anh thanh block x block o vuong
	(h, w) = image.shape[:2]
	xGridLines = np.linspace(0, w, grid_size + 1, dtype="int")
	yGridLines = np.linspace(0, h, grid_size + 1, dtype="int")

	# Lap qua tung o vuong
	for i in range(1, len(xGridLines)):
		for j in range(1, len(yGridLines)):

			# Lay toa do cua o vuong hien tai
			cell_startX = xGridLines[j - 1]
			cell_startY = yGridLines[i - 1]
			cell_endX = xGridLines[j]
			cell_endY = yGridLines[i]

			# Trich vung anh theo toa do ben tren
			cell = image[cell_startY:cell_endY, cell_startX:cell_endX]

			# Tinh trung binh cong vung anh va ve vao o vuong hien tai
			(B, G, R) = [int(x) for x in cv2.mean(cell)[:3]]
			cv2.rectangle(image, (cell_startX, cell_startY), (cell_endX, cell_endY),
				(B, G, R), -1)

	return image
Code language: PHP (php)

Okie rồi, bây giờ ta ghép vào nào!

Phần 4 – Lắp ghép để làm bài toán làm mờ mặt

Lắp ghép tất cả vào chúng ta có source đầy đủ, khuôn mật sẽ được xoá mờ trên camera:

while True:
	ret, image = cam.read()
	# Load image
	(h, w) = image.shape[:2]

	# Phat hien khuon mat
	blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), (104.0, 177.0, 123.0))
	model.setInput(blob)
	detections = model.forward()

	# Lap qua ket qua dau ra
	for i in range(0, detections.shape[2]):

		# Lay confidence
		confidence = detections[0, 0, i, 2]
		print(detections[0, 0, i])
		# Neu confiden > 0.5 moi xu ly
		if (detections[0, 0, i, 1] == 1) and (confidence > 0.5):

			# Lay toa do that
			box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
			(startX, startY, endX, endY) = box.astype("int")
			# Lay phan khuon mat
			face = image[startY:endY, startX:endX]

			# Pixelate
			face = pixelate_image(face, grid_size=int(w * 0.01))
			# Ve de phan pixelate len
			image[startY:endY, startX:endX] = face

			cv2.imshow("Output", image)

	if cv2.waitKey(1) & 0xFF == ord('q'):
		break
Code language: PHP (php)

Rồi và kết quả mong muốn của chúng ta như sau:

làm mờ mặt

Okie rồi! Bài này cũng khá đơn giản, chắc các bạn có thể làm được ngay. Tuy nhiên mình cũng xin chia sẻ github tại đây: https://github.com/thangnch/MiAI_Blur_Face để bạn nào cần thì lấy source.

Các hướng có thể phát triển thêm là thay phát hiện mặt bằng các thứ khác bạn muốn làm mờ như: nhãn hàng, logo, và….các thứ nhạy cảm khác.

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 *