Skip to main content

Pha trộn ảnh - Image blending

1. Giới thiệu

Pha trộn ảnh (Image Blending) là thao tác ghép hai hoặc nhiều ảnh chồng lên nhau tại một hoặc nhiều vị trí với các mức độ trong suốt khác nhau. Dưới đây một số ví dụ về ảnh đã được pha trộn.

Để thực hiện được thao tác pha trộn ảnh, ta cần có một số kiến thức cơ bản về biểu diễn ảnhthao thác trên ma trận.

2. Pha trộn với ảnh hiệu ứng tĩnh

Có rất nhiều cách thức pha trộn ảnh với nhau. Bài viết này tiến hành pha trộn ảnh từ ba dữ kiện đầu vào như sau:

  • Ảnh foreground (tiền cảnh): chứa đối tượng chính cần tạo hiệu ứng lên. Trong ví dụ trên, tiền cảnh là người.
  • Ảnh mask (mặt nạ): cho biết phạm vi áp dụng và phạm vi không áp dụng hiệu ứng. Trên ảnh mask có hai loại vùng điểm ảnh: vùng điểm nằm ngoài hiệu ứng và vùng điểm nằm trong cần thực hiện hiệu ứng. Các loại vùng điểm này lần lượt được minh họa bằng điểm màu xanhmàu đỏ trong ảnh dưới đây.
  • Ảnh effect (hiệu ứng): chứa hiệu ứng cần áp dụng lên đối tượng tiền cảnh.

Các bước chính thực hiện thao tác trộn ảnh như sau:

Bước 1: Đọc các ảnh đầu vào

Đầu tiên, ta cần đọc ba ảnh từ file lần lượt là foreground, effectmask. Các ảnh foreground và effect biểu diễn bằng không gian màu RGB như bình thường. Riêng ảnh mask bao gồm 4 kênh màu: R (red), G (green), B (blue) và A (alpha). Trong đó R, G và B cho biết giá trị màu của điểm ảnh còn kênh A cho biết vùng nằm trong và nằm ngoài hiệu ứng. Kênh A sẽ có hai giá trị 0 và 255 lần lượt biểu diễn cho vùng không cần và cần áp hiệu ứng.

import cv2

# đọc ảnh foreground
fg = cv2.imread(r'Đường\dẫn\file\ảnh\foreground.png')
print('Kich thuoc theo tung kenh cua foreground: ', fg.shape)

# đọc ảnh effect
eff = cv2.imread(r'C:\Đường\dẫn\file\ảnh\eff.png')
print('Kich thuoc theo tung kenh cua eff: ', eff.shape)

# đọc ảnh mask
mask = cv2.imread(r'Đường\dẫn\file\ảnh\mask.png', cv2.IMREAD_UNCHANGED)
print('Kich thuoc theo tung kenh cua mask: ', mask.shape)

# Hiện thị hai ảnh lên màn hình
cv2.imshow('Foreground', fg)
cv2.imshow('Background', eff)
cv2.imshow('Mask', mask)
cv2.waitKey(0)

Kết quả sẽ hiện ra tương tự như sau:

Trong các bước tiếp theo ta sẽ tiến hành xử lý trộn cho một phần ảnh background sẽ hiện lên trong ảnh foreground như ảnh ví dụ ở đầu bài viết.

Bước 2: Chuẩn hóa kích thước ảnh

Để trộn các ảnh với nhau thì cả ba phải có cùng kích thước. Để thay đổi kích thước ảnh, ta sử dụng hàm cv2.resize() với cú pháp như sau:

ảnh chuẩn hóa = cv2.resize(ảnh gốc, (chiều rộng, chiều cao))

Nếu như kích thước của cả ba ảnh đều giống nhau rồi thì ta bỏ qua không thực hiện bước này. Các ảnh mẫu được sử dụng ở đây đều đã có kích thước chuẩn hóa giống nhau.

Bước 3: Pha trộn ảnh

Chúng ta sẽ có công thức tổng quát khi trộn ảnh như sau:

g(x,y)=αIfg(x,y)+(1α)Ieff(x,y)g(x,y) = \alpha I_{fg}(x,y) + (1−\alpha)I_{eff}(x,y)

trong đó,

  • α\alpha: trọng số pha trộn ảnh giữa foreground và effect
  • Ifg(x)I_{fg}(x): ảnh foreground chứa đối tượng chính
  • Ieff(x)I_{eff}(x): ảnh effect chứa hiệu ứng
  • g(x,y)g(x,y): ảnh kết quả sau pha trộn

Những điểm ảnh cần pha trộn trên ảnh mask có giá trị kênh alpha bằng 255. Ngược lại, những điểm không được sử dụng để pha trộn thì giá trị kênh alpha bằng 0. Do đó khi duyệt qua các điểm ảnh ta sẽ kiểm tra xem giá trị kênh alpha của ảnh mask có khác 0 hay không. Dưới đây là mã nguồn thuật toán pha trộn ảnh:

#Sao chép ảnh qua biến mới
result = fg.copy()
alpha = 0.6
for x in range(mask.shape[0]): # result.shape[0]: chiều cao ảnh
for y in range(mask.shape[1]): # result.shape[1]: chiều rộng ảnh
if (mask[x,y,3] != 0): # Kiểm tra điểm ảnh
result[x,y] = (alpha * fg[x,y] + (1 - alpha) * eff[x,y])

cv2.imshow('Result', result)
cv2.waitKey(0)

Cách lập trình trên sử dụng hai vòng for lồng nhau nên tốc độ rất chậm. Ta sẽ thay vòng lặp trên với một dòng code duy nhất như dưới đây.

result = fg.copy()
alpha = 0.6
result[mask[:,:,3] != 0] = fg[mask[:,:,3] != 0] * alpha \
+ eff[mask[:,:,3] != 0] * (1 - alpha)

cv2.imshow('Result', result)
cv2.waitKey(0)

Dưới đây là ảnh kết quả sau khi đã pha trộn ảnh foreground với một ảnh hiệu ứng tĩnh background:

3. Pha trộn ảnh với hiệu ứng động từ file GIF

Để tăng thêm tính sinh động của hiệu ứng, trong phần này ta sẽ tìm cách tạo hiệu ứng động như khói, lửa thay vì sử dụng một ảnh tĩnh.

Bước 1: Đọc ảnh foreground và mask

Đầu tiên, ta sẽ tiến hành đọc ảnh foreground và ảnh mask và lưu vào hai biến fgmask.

fg = cv2.imread('girl.jpg')
mask = cv2.imread('mask.png', cv2.IMREAD_UNCHANGED)

Bước 2: Đọc ảnh background chứa hiệu ứng động dạng GIF

Đối với ảnh hiệu ứng nền, ta sẽ sử dụng ảnh gif được lấy từ internet (dùng URL, thay vì file trong máy). Do đó cách thức đọc cũng khác so với ảnh foreground và mask.

Ta sẽ sử dụng thư viện imageio để đọc ảnh GIF từ đường dẫn. Nếu như máy bạn chưa cài thư viện này thì ta có thể cài đặt bằng cách:

pip install imageio

Để thực hiện việc đọc các file ảnh này ta thực hiện các lệnh sau:

url = "https://media0.giphy.com/media/2vmiW6mcYgKst3QVDK/giphy.gif"
frames = imageio.mimread(imageio.core.urlopen(url).read(), '.gif')

Bước 3: Chuẩn hóa ảnh background

Trong tình huống này, ảnh background có kích thước khác với ảnh foreground. Do đó ta cần thực hiện chuẩn hóa kích thước cho ảnh background giống với với ảnh foreground.

Ảnh sau minh họa việc chồng ảnh foreground lên ảnh background và việc tính toán các tọa độ lefttop để cắt ra cho vừa khớp với kích thước ảnh hiệu ứng (background) và ảnh chính với nhau.

Sau đây là đoạn code để cắt ảnh background cho khớp kích thước với ảnh foreground:

fg_h, fg_w, fg_c = fg.shape
bg_h, bg_w, bg_c = frames[0].shape
top = int((bg_h-fg_h)/2)
left = int((bg_w-fg_w)/2)
bgs = [frame[top: top + fg_h, left:left + fg_w, 0:3] for frame in frames]

Bước 4: Xử lý pha trộn ảnh và hiệu ứng

Trong phần trước, ta đã tiến hành trộn hai ảnh foreground và background bằng vòng lặp for. Tuy nhiên, cách làm này cực kỳ chậm. Chúng ta phải hạn chế tối đa sử dụng vòng for trong Python các bạn nhé.

Khi tiến hành trộn ảnh foreground với toàn bộ ảnh hiệu ứng, ta sẽ thực hiện vòng lặp trên từng điểm ảnh của biến bgs. Kết quả khi pha trộn từng ảnh sẽ được dồn (append) vào biến results.

results = []
alpha = 0.3
for i in range(len(bgs)):
result = fg.copy()
result[mask[:,:,3] != 0] = alpha * result[mask[:,:,3] != 0]
bgs[i][mask[:,:,3] == 0] = 0
bgs[i][mask[:,:,3] != 0] = (1-alpha)*bgs[i][mask[:,:,3] != 0]
result = result + bgs[i]
results.append(result)

Bước 5: Lưu kết quả dưới thành file GIF

Kết thúc Bước 4, các ảnh hiệu ứng sẽ được lưu ở biến results. Ta sẽ lưu danh sách các ảnh kết quả trên xuống file GIF tên là 'result.gif' như sau:

imageio.mimsave('result.gif', results)

Sau đây là kết quả pha trộn ảnh foreground và hiệu ứng từ ảnh GIF:

Tổng kết

Qua bài viết, bạn đã biết cách để pha trộn 2 ảnh với nhau, đồng thời tìm hiểu một thư viện mới là imageio để đọc và ghi file GIF. Đồng thời, chúng ta được trải nghiệm hai cách thức lập trình trên Python bao gồm và không bao gồm sử dụng vòng lặp for. Việc hạn chế sử dụng vòng for sẽ giúp tốc độc thực thi nhanh hơn một cách đáng kể.

Tác giả: Phạm Trần Anh Tiên