【問題發現】本人在圖像處理項目過程中,經常需要將一幅jpg圖像疊加到另一幅背景jpg圖像上,來實現一些特定的需求。例如我們經常在抖音中看到一些視頻特效的疊加效果,貓耳朵等等特效在背景人臉圖像上的疊加。我們利用Python+OpenCV的方式可以很簡單的實現jpg圖像之間的疊加,但實際項目中更多需要png透明圖像在jpg圖像上疊加。這種情況下,仍然適用傳統的jpg疊加方式,就會出現原本透明的png圖像,疊加后直接變為不透明的jpg圖像,達不到我們想要的效果。本篇將主要講解如何利用Python+OpenCV來實現png透明圖像疊加到jpg圖像上的方法。
【解決方案】
1. 如何正常讀取4通道的png圖像
為了解決上述問題,首先我們要弄清楚jpg圖像和png圖像的區別。我們利用Python+OpenCV方式讀取的jpg圖像為BGR三通道圖像,每一個通道代表了一個色彩描述。而png圖像則是四通道圖像,除了BGR通道外,還有一個A通道,即Alpha通道,描述了圖像的透明程度。但需要注意的是:OpenCV提供的圖像讀取函數cv2.imread(),在默認情況下讀取png圖像會自動忽略Alpha通道,即png圖像直接變為jpg圖像。因此,在讀入png圖像時,我們需要特別注意。
import cv2 img_path = 'imgs/demo.png' # 設置透明png圖像路徑 #img_bgr0 = cv2.imread(img_path, cv2.IMREAD_COLOR) # 默認讀取BGR彩色圖像,忽略Alpha通道 #img_bgr1 = cv2.imread(img_path) # 默認讀取方式,結果同上 #img_gray = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) # 讀入灰度圖像 img_png = cv2.imread(img_path, cv2.IMREAD_UNCHANGED) # 正常讀入圖像,並保留其通道數不變,png圖像為4通道,jpg圖像為3通道
2. 為jpg圖像增加Alpha通道
首先,傳統jpg圖像之間的疊加是在背景圖像上確定一塊與待疊加圖像尺寸相同的區域進行疊加。例如,50*50*3尺寸的jpg圖像要疊加到100*100*3尺寸的jpg背景圖像上,需要在背景圖像上指定50*50*3的區域來放入前者。代碼如下:
import numpy as np import cv2 # 創建一張100*100*3尺寸的黑色背景圖像 img_bg = np.zeros((100,100), dtype=np.uint8) img_bg = cv2.cvtColor(img_bg, cv2.COLOR_GRAY2BGR) # 創建一張50*50*3尺寸的白色待疊加圖像 img_white = np.ones((50,50), dtype=np.uint8) img_white = cv2.cvtColor(img_white, cv2.COLOR_GRAY2BGR) # 將白色圖像疊加到黑色背景背景圖像上,疊加位置為左上角 img_bg[:50,:50,:] = img_white # 顯示圖像 cv2.imshow('result', img_bg) # 顯示疊加后的結果圖像 if cv2.waitKey(0) & 0xFF == 27: cv2.destroyAllWindows()
jpg圖像疊加時,它們的通道數都為3,不會出現問題。但當png4通道圖像往jpg3通道圖像上疊加時,則會出現圖像尺寸不匹配的問題。例如,在100*100*3尺寸的jpg圖像上無法划定出50*50*4的空間來讓png圖像放入。為了解決這一問題,我們需要為jpg圖像增加Alpha通道,可以理解成我們來增大jpg圖像的尺寸,使它足夠大能容下png圖像。代碼如下:
import cv2 import numpy as np def add_alpha_channel(img): """ 為jpg圖像添加alpha通道 """ b_channel, g_channel, r_channel = cv2.split(img) # 剝離jpg圖像通道 alpha_channel = np.ones(b_channel.shape, dtype=b_channel.dtype) * 255 # 創建Alpha通道 img_new = cv2.merge((b_channel, g_channel, r_channel, alpha_channel)) # 融合通道 return img_new
3. 將png透明圖像疊加到jpg圖像上
在疊加圖像前,我們需要確定在背景圖像的哪個位置進行疊加,這里我們在背景圖像中設2個坐標點:(x1, y1) 和 (x2, y2) 分別表示疊加位置的左上角坐標和右下角坐標。
圖像疊加代碼如下:
img_bg[y2:y1,x1:x2] = img_white
OK,了解了基礎原理后,我們現在開始進行正式的圖像疊加,代碼如下:
def merge_img(jpg_img, png_img, y1, y2, x1, x2): """ 將png透明圖像與jpg圖像疊加 y1,y2,x1,x2為疊加位置坐標值 """ # 判斷jpg圖像是否已經為4通道 if jpg_img.shape[2] == 3: jpg_img = add_alpha_channel(jpg_img) ''' 當疊加圖像時,可能因為疊加位置設置不當,導致png圖像的邊界超過背景jpg圖像,而程序報錯 這里設定一系列疊加位置的限制,可以滿足png圖像超出jpg圖像范圍時,依然可以正常疊加 ''' yy1 = 0 yy2 = png_img.shape[0] xx1 = 0 xx2 = png_img.shape[1] if x1 < 0: xx1 = -x1 x1 = 0 if y1 < 0: yy1 = - y1 y1 = 0 if x2 > jpg_img.shape[1]: xx2 = png_img.shape[1] - (x2 - jpg_img.shape[1]) x2 = jpg_img.shape[1] if y2 > jpg_img.shape[0]: yy2 = png_img.shape[0] - (y2 - jpg_img.shape[0]) y2 = jpg_img.shape[0] # 獲取要覆蓋圖像的alpha值,將像素值除以255,使值保持在0-1之間 alpha_png = png_img[yy1:yy2,xx1:xx2,3] / 255.0 alpha_jpg = 1 - alpha_png # 開始疊加 for c in range(0,3): jpg_img[y1:y2, x1:x2, c] = ((alpha_jpg*jpg_img[y1:y2,x1:x2,c]) + (alpha_png*png_img[yy1:yy2,xx1:xx2,c])) return jpg_img
這里我講上述所有代碼進行整合,png圖像疊加到jpg圖像上的完整代碼如下:
import cv2 import numpy as np def add_alpha_channel(img): """ 為jpg圖像添加alpha通道 """ b_channel, g_channel, r_channel = cv2.split(img) # 剝離jpg圖像通道 alpha_channel = np.ones(b_channel.shape, dtype=b_channel.dtype) * 255 # 創建Alpha通道 img_new = cv2.merge((b_channel, g_channel, r_channel, alpha_channel)) # 融合通道 return img_new def merge_img(jpg_img, png_img, y1, y2, x1, x2): """ 將png透明圖像與jpg圖像疊加 y1,y2,x1,x2為疊加位置坐標值 """ # 判斷jpg圖像是否已經為4通道 if jpg_img.shape[2] == 3: jpg_img = add_alpha_channel(jpg_img) ''' 當疊加圖像時,可能因為疊加位置設置不當,導致png圖像的邊界超過背景jpg圖像,而程序報錯 這里設定一系列疊加位置的限制,可以滿足png圖像超出jpg圖像范圍時,依然可以正常疊加 ''' yy1 = 0 yy2 = png_img.shape[0] xx1 = 0 xx2 = png_img.shape[1] if x1 < 0: xx1 = -x1 x1 = 0 if y1 < 0: yy1 = - y1 y1 = 0 if x2 > jpg_img.shape[1]: xx2 = png_img.shape[1] - (x2 - jpg_img.shape[1]) x2 = jpg_img.shape[1] if y2 > jpg_img.shape[0]: yy2 = png_img.shape[0] - (y2 - jpg_img.shape[0]) y2 = jpg_img.shape[0] # 獲取要覆蓋圖像的alpha值,將像素值除以255,使值保持在0-1之間 alpha_png = png_img[yy1:yy2,xx1:xx2,3] / 255.0 alpha_jpg = 1 - alpha_png # 開始疊加 for c in range(0,3): jpg_img[y1:y2, x1:x2, c] = ((alpha_jpg*jpg_img[y1:y2,x1:x2,c]) + (alpha_png*png_img[yy1:yy2,xx1:xx2,c])) return jpg_img if __name__ == '__main__': # 定義圖像路徑 img_jpg_path = 'imgs/0.jpg' # 讀者可自行修改文件路徑 img_png_path = 'imgs/0.png' # 讀者可自行修改文件路徑 # 讀取圖像 img_jpg = cv2.imread(img_jpg_path, cv2.IMREAD_UNCHANGED) img_png = cv2.imread(img_png_path, cv2.IMREAD_UNCHANGED) # 設置疊加位置坐標 x1 = 560 y1 = 180 x2 = x1 + img_png.shape[1] y2 = y1 + img_png.shape[0] # 開始疊加 res_img = merge_img(img_jpg, img_png, y1, y2, x1, x2) # 顯示結果圖像 cv2.imshow('result', res_img) # 保存結果圖像,讀者可自行修改文件路徑 cv2.imwrite('imgs/res.jpg', res_img) # 定義程序退出方式:鼠標點擊顯示圖像的窗口后,按ESC鍵即可退出程序 if cv2.waitKey(0) & 0xFF == 27: cv2.destroyAllWindows()
【結果顯示】
用我家主子的照片來試一下效果啦~
最后,希望本文能幫助到熱愛圖像處理的小伙伴們~
原文出處:https://blog.csdn.net/weixin_40065609/article/details/114886234