Computer Vision

Add transparent overlay to image with OpenCV

Xin chào, hôm nay chúng ta sẽ tìm hiểu cách chèn một vài hiệu ứng nhỏ vào live video từ webcam.


3-CHANNEL IMAGE (24-BIT IMAGE)

Chúng ta đã quá quen thuộc với các bức ảnh loại này, nó gồm 3 kênh màu: RGB (hoặc HVS,…).

Việc chèn một bức ảnh 3-channel vào một background khá đơn giản, chúng ta chỉ cần chọn ra vùng background phù hợp và thay nó bằng bức ảnh mà chúng ta muốn chèn vào.


4-CHANNEL IMAGE (32-BIT IMAGE)

Các bức ảnh loại này có 4 kênh màu  gồm: RGB (hoặc HSV, …) và Alpha. Giá trị Alpha quy định độ trong suốt của pixel. Giá trị Alpha càng lớn thì màu sắc của pixel đó càng đục và pixel sẽ hoàn toàn trong suốt khi Alpha = 0.

Tuy nhiên, trong OpenCV giá trị Alpha sẽ bị loại bỏ. Để load một bức ảnh có 4 channels chúng ta sử dụng function cv2.imread(“ImageFile”, -1).

Để chèn một bức ảnh 4-channel vào một background, chúng ta cần tách kênh Alpha của nó. Kết quả nhận được sẽ là một ma trận có kích thước tương ứng với kích thước của bức ảnh. Ma trận này chứa giá trị Alpha của từng pixel trong bức ảnh, giá trị này nằm trong khoảng [0, 255]. Chia ma trận này cho 255.0, chúng ta sẽ nhận được một ma trận mô tả độ trong suốt của từng pixel, các giá trị này nằm trong khoảng [0, 1.0], trong đó 0 là hoàn toàn trong suốt, 1 là hoàn toàn đục. Ma trận này sẽ được sử dụng để trộn bức ảnh cần chèn và background nhằm tạo ra hiệu ứng trong suốt.


Trong bài viết này mình sẽ tìm cách gắn một cái mắt kính thật cool ngầu cho khuôn mặt của mình từ live video của webcam và chèn thêm 1 cái logo OpenCV ở góc trái.

Để chèn cái mắt kính vào vị trí đôi mắt ta cần thực hiện các bước sau:

  1. Tìm một cái mắt kính thật ngầu;
  2. Xác định khuôn mặt và vị trí của đôi mắt từ live video;
  3. Xử lý bức ảnh mắt kính cho phù hợp với kích thước và góc nghiêng của khuôn mặt;
  4. Áp dụng độ trong suốt của từng pixel vào tính toán để tạo hiệu ứng transparency và chèn vào frame của live video.

Để chèn logo OpenCV vào frame, ta cần thực hiện các bước sau:

  1. Tùy chỉnh lại kích thước bức ảnh chứa logo;
  2. Nhị phân hóa bức ảnh chứa logo và tách phần logo ra khỏi bức ảnh gốc;
  3. Chọn vùng cần chèn logo trên background với kích thước bằng với kích thước bức ảnh chứa logo;
  4. Cộng phần logo và vùng cần chèn logo.

Bắt đầu thôi!

Đây là cái mắt kính mà mình thấy rất ưng ý:

newglass
Figure 1 – Eyeglasses

Còn đây là logo của OpenCV:

logoopencv
Figure 2 – OpenCV logo

Bây giờ chúng ta sẽ áp dụng Face Detection để tìm vị trí khuôn mặt và đôi mắt trong từng frame của live video.

Source code: detect_faces.py

import argparse
import cv2
import numpy as np
import imutils

class FaceDetector:
	def __init__(self, faceCascadePath):
		self.faceCascade = cv2.CascadeClassifier(faceCascadePath)

	def detect(self, image, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30)):
		rects = self.faceCascade.detectMultiScale(image, scaleFactor=scaleFactor, minNeighbors=minNeighbors, minSize=minSize, flags=cv2.CASCADE_SCALE_IMAGE)
		return rects

class EyeDetector:
	def __init__(self, eyeCascadePath):
		self.eyeCascade = cv2.CascadeClassifier(eyeCascadePath)

	def detect(self, image, scaleFactor=1.3, minNeighbors=15, minSize=(20, 20)):
		rects = self.eyeCascade.detectMultiScale(image, scaleFactor=scaleFactor, minNeighbors=minNeighbors, minSize=minSize, flags=cv2.CASCADE_SCALE_IMAGE)
		return rects

fd = FaceDetector('haarcascades\\haarcascade_frontalface_default.xml')
ed = EyeDetector('haarcascades\\haarcascade_eye.xml')

camera = cv2.VideoCapture(0)

ap = argparse.ArgumentParser()
ap.add_argument("-g", "--glass")
ap.add_argument("-l", "--logo")
args = vars(ap.parse_args())

glass = cv2.imread(args["glass"], -1)
logo = cv2.imread(args["logo"])

logo = imutils.resize(logo, width=100)
logoGray = cv2.cvtColor(logo, cv2.COLOR_BGR2GRAY)
logoMask = cv2.threshold(logoGray, 200, 255, cv2.THRESH_BINARY)[1]
logoMask = cv2.erode(logoMask, None, iterations=1)
logo = cv2.bitwise_and(logo, logo, mask=cv2.bitwise_not(logoMask))

while True:
	grab, frame = camera.read()
	if not grab:
		continue

	gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

	faceRects = fd.detect(gray, scaleFactor=1.15, minNeighbors=5, minSize=(30, 30))

	for (fx,fy,fw,fh) in faceRects:
		# uncomment if need to display face rectangle
		#cv2.rectangle(frame, (fx,fy), (fx+fw,fy+fh), (0,255,0), 1)
		eyeRects = ed.detect(frame[fy:fy+fh, fx:fx+fw])
		eyeCenter = []
		angle = 0
		y = 0
		for (ex,ey,ew,eh) in eyeRects:
			# uncomment if need to display eye rectangle
			#cv2.rectangle(frame, (fx+ex,fy+ey), (fx+ex+ew,fy+ey+eh), (0,255,0), 1)
			eyeCenter.append((fx+ex+ew//2, fy+ey+eh//2))
			y += ey

		if len(eyeCenter) == 2:
			if eyeCenter[0][0] > eyeCenter[1][0]:
				angle = np.degrees(-np.arctan2(eyeCenter[0][1]-eyeCenter[1][1], eyeCenter[0][0]-eyeCenter[1][0]))
			else:
				angle = np.degrees(-np.arctan2(eyeCenter[1][1]-eyeCenter[0][1], eyeCenter[1][0]-eyeCenter[0][0]))

			y = y // 2

		newglass = imutils.resize(glass, width=fw)
		newglass = imutils.rotate(newglass, angle)
		alphaGlass = newglass[...,3] / 255.0
		alphaImage = 1.0 - alphaGlass

		alphaGlass.shape = (alphaGlass.shape[0], alphaGlass.shape[1], 1)
		alphaImage.shape = (alphaImage.shape[0], alphaImage.shape[1], 1)

		frame[fy+y//2:fy+y//2+newglass.shape[0], fx:fx+fw] = alphaGlass*newglass[...,0:3] \
								+ alphaImage*frame[fy+y//2:fy+y//2+newglass.shape[0], fx:fx+fw]

	ROI = frame[0:logo.shape[0], 0:logo.shape[1]]
	ROI = cv2.bitwise_and(ROI,ROI,mask=logoMask)
	frame[0:logo.shape[0], 0:logo.shape[1]] = cv2.add(ROI,logo)

	cv2.imshow("Frame", frame)

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

camera.release()
cv2.destroyAllWindows()

Giải thích:
– Dòng 48, 53: xác định vị trí khuôn mặt và  vị trí đôi mắt;
– Dòng 63 – 67: xác định góc nghiêng của đôi mắt, từ đó xoay cái kính cho phù hợp;
– Dòng 71, 72: điều chỉnh kích thước cái mắt kính cho phù hợp với khuôn mặt;
– Dòng 73, 74: chúng ta tính độ trong suốt của các pixel trong bức ảnh newglass bằng cách tách channel Alpha của nó và chia cho 255.
– Dòng 79, 80: chúng ta áp dụng ma trận độ trong suốt vừa tính được để trộn bức ảnh với background nhằm tạo hiệu ứng trong suốt;
– Dòng 35 – 39: tách phần logo OpenCV và tạo mask.
– Dòng 82 – 84: chèn logo OpenCV vào vị trí (0, 0) của frame.

Kết quả:

output

Với kết quả nhận được, chúng ta thấy rằng hiệu ứng trong suốt của chiếc mắt kính đã được thể hiện và logo của OpenCV đã được chèn ở góc trên bên trái.

Cảm ơn các bạn đã theo dõi bài viết.

Thân ái và quyết thắng.

Reference:
[1] overlay a smaller image on a larger image python OpenCV.
[2] How to overlay an PNG image with alpha channel to another PNG?.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s