
表白利器,馬賽克拼貼照片制作
養成習慣,先贊後看!!!
@
目錄
- 1.前言
- 2.重點原理
- 3.實作步驟
- 3.1修改圖檔大小
- 3.2計算圖檔的直方圖
- 3.3比較直方圖差異,同時替換
- 3.4融合圖檔
- 4.效果示範
1.前言
主要思想來源于這篇老哥的文章:https://zhuanlan.zhihu.com/p/168667043
有興趣的小夥伴可以去看看。
其實之前我在b站上就曾經看到過這樣一個軟體:
順便貼出軟體的下載下傳位址:https://xttx.lanzous.com/id6euad
使用教程:https://www.bilibili.com/video/BV14g4y1i7Ey
當時我就測試了一下,發現這個軟體不是特别的友好,他對于你輸入的圖檔組的 要求是比較高的 ,意思就是你輸入的圖檔組最好本身的尺寸大小最好和你要替換的原圖的尺寸大小類似或者是成一定的比例,這樣他拼接出來效果的确是可以的,就像下圖:
如果尺寸不對或者說提供的圖檔數量不夠,或者說色差太過單一的話,就可能會出現下面的情況:
現在想想我尼瑪就是為了圖友善才用你的,你這還得讓我去找這樣那樣的圖檔,那不還是很麻煩嗎。是以之後也沒怎麼用這個軟體了。
但是這幾天看csdn的時候看到了這位老哥的部落格:https://blog.csdn.net/qq_43667130/article/details/107892516
但是他是用C++實作的,我就沒看,但是老哥人好,提供一篇用python實作的部落格,就是上面那篇部落格,于是我就開始嘗試讀懂并使用它。
2.重點原理
雖然我們不會但是,我們其實也能夠說出原理大體上是啥。其實主要步驟就是兩步:
1. 将原圖與圖檔組進行适當的分割
2. 進行色差比較,用色差最相似的圖檔進行替換
第一步我們通過畫圖就能看懂了,如下圖:
第二步大家也都能了解,我就不扯淡了,如下圖:
3.實作步驟
3.1修改圖檔大小
這裡主要包含兩方面,一是修改圖檔組的大小。因為我們提供的圖檔有的可能是橫版的,有的可能是豎版的,并且有的可能是3:4。有的可能是16:9,反正就是圖檔的大小肯定會出現差異,是以我們需要将圖檔組的大小固定下來。
這裡有個小bug: 如果data1後面的 “/ ”千萬不能少,少了他就是單純讀取這個檔案,而并非是讀取該檔案夾目錄下的檔案,并且所有的路徑名當中都不能包含中文,否則也會報錯
這裡圖檔組中的圖檔都是我之前爬蟲獲得的圖檔,爬蟲源碼在我之前的這篇部落格中:python爬蟲--批量下載下傳cosplay小姐姐圖檔
# 預處理填充圖檔
def before_handle_imgs():
print("正在預處理填充圖檔:")
# 讀取圖檔組的存儲路徑
readPath = "D:/test/data1/"
# 修改完後圖檔組的存儲路徑
savePath = "D:/test/data2/"
files = os.listdir(readPath)
for file in files:
imgPath = readPath + "/" + file
# 讀取圖檔
img = cv.imread(imgPath)
# 重新設定圖檔的大小,長160px,寬90px
img = cv.resize(img, (160, 90))
# 将修改完後的圖檔存儲到data2目錄下
cv.imwrite(savePath + "/" + file, img)
print("預處理填充圖檔已完成!")
二就是重新修改我們原圖的大小,這裡的大小就是要和上面的大小對應,長寬一定要是上面圖檔的倍數,否則就會出現一開始我們遇到的有黑色區域,當然了運作程式他是會 直接報錯的
# 預處理待填充圖檔
def before_handle_img():
print("正在預處理待填充圖檔:")
# 重新設定原圖的大小
width, height = 32000*n, 13500*n
# 讀取原圖
readPath = "D:/test/img1.jpg"
# 修改完後圖檔的存儲位置
savePath = "D:/test/img2.jpg"
img = cv.imread(readPath)
# 修改圖檔大小
img = cv.resize(img, (width, height))
# 存儲圖檔
cv.imwrite(savePath, img)
print("預處理待填充圖檔已完成!")
3.2計算圖檔的直方圖
我們一開始不懂的就以為是計算色差,看了源碼之後才知道原來是計算他們的直方圖就行了。真的是被自己蠢到了。
這裡我們主要講解一下 cv2.calcHist() 這個函數,大家就懂了。這個函數有5個參數
- image輸入圖像,傳入時應該用中括号[]括起來
- channels::傳入圖像的通道,如果是灰階圖像,那就不用說了,隻有一個通道,值為0,如果是彩色圖像(有3個通道),那麼值為0,1,2,中選擇一個,對應着BGR各個通道。這個值也得用[]傳入。
- mask:掩膜圖像。如果統計整幅圖,那麼為none。主要是如果要統計部分圖的直方圖,就得構造相應的炎掩膜來計算。
- histSize:灰階級的個數,需要中括号,比如[256]
- ranges:像素值的範圍,通常[0,256],有的圖像如果不是0-256,比如說你來回各種變換導緻像素值負值、很大,則需要調整後才可以。
這樣我們便能計算出圖檔組中各圖檔的直方圖參數了。
# 用字典存儲每一圖及其直方圖
def build_index():
print("正在計算各圖檔直方圖:")
# 修改完大小後圖檔的存儲路徑
readPath = "D:/test/data2/"
files = os.listdir(readPath)
dist = {}
for file in files:
imgPath = readPath + "/" + file
img = cv.imread(imgPath)
hist = []
# 因為有三個通道即RGB,是以一張圖檔要計算三次
for i in range(3):
ht = cv.calcHist([img], [i], None, [256], [0, 256])
hist.append(ht)
dist[file] = hist
print("各圖檔直方圖計算已完成!")
return dist
3.3比較直方圖差異,同時替換
既然需要比較直方圖資訊,那麼我們就必須也要計算原圖各區域的直方圖資訊,然後循環與圖檔組的直方圖進行比較,并且同時記住目前與其最相似的圖檔,再循環結束後将其替換。
這其中我們也是重點講一下這個函數 cv2.compareHist 他主要有三個參數:
- H1為其中一個直方圖
- H2為另一個圖像的直方圖
-
method為比較方式
相關性比較 (method= cv.HISTCMP_CORREL ) 值越大,相關度越高,最大值為1,最小值為0
卡方比較(method=cv.HISTCMP_CHISQR 值越小,相關度越高,最大值無上界,最小值0
巴氏距離比較(method=cv.HISTCMP_BHATTACHARYYA) 值越小,相關度越高,最大值為1,最小值為0
# 用最相近的圖代替原圖
def match_replace(dist):
print("正在替換圖檔:")
width, height = 32000* n, 13500* n
image = cv.imread("D:/test/img2.jpg")
for i in range(0, height, 90):
for j in range(0, width, 160):
img = image[i:i+90, j:j+160, 0:3]
hist = []
# 計算原圖一個馬賽克大小的三個通道的直方圖
for k in range(3):
ht = cv.calcHist([img], [k], None, [256], [0, 256])
hist.append(ht)
sim = 0.0
# 與整個圖檔組的直方圖進行比較
for key in dist:
# 分别比較三個通道的相似度
match0 = cv.compareHist(hist[0], dist[key][0], cv.HISTCMP_CORREL)
match1 = cv.compareHist(hist[1], dist[key][1], cv.HISTCMP_CORREL)
match2 = cv.compareHist(hist[2], dist[key][2], cv.HISTCMP_CORREL)
match = match0 + match1 + match2
# 沒計算過一次,就和之前的相似度進行比較,如果大于說明更加相似就替換,否則就不
if match > sim:
sim = match
rename = key
# 替換圖檔
image[i:i+90, j:j+160, 0:3] = cv.imread("D:/test/data2/" + rename)
cv.imwrite("D:/test/img3.jpg", image)
print("圖檔替換已完成!")
效果就是這樣的:
這裡我們放大看看:
可以發現的确替換了,并且照片的大小也是急劇增加:
3.4融合圖檔
其實到上面一步,我們的馬賽克圖檔就做出來了,我自己覺得這樣就夠了,但是那個老哥還加了這一步就是将原圖與我們的馬賽克圖進行了混合,但是說實話混合之後的确更像了,但是說實話不怎麼看的出來是有馬賽克合成的。
這裡我們還是重點講一個函數即 cv2.addWeighted() ,這個函數主要有五個參數:
- InputArray src1:表示需要權重的第一個數組,即第一張圖像
- alpha:表示第一個圖像的權重
- src2:表示第二個數組,和第一個具有相同的尺寸和通道數,即第二張圖像
- beta:表示第二個數組的權重值
-
gamma:一個加到權重總和上的标量值
上面兩者的權重和必須要為1
# 混合圖檔
def mix_image():
print("正在融合圖檔:")
# 馬賽克圖像
image1 = cv.imread("D:/test/img3.jpg")
# 原圖修改後的圖像
image2 = cv.imread("D:/test/img2.jpg")
# 圖像融合
dst = cv.addWeighted(image1, 0.6, image2, 0.4, 3)
# 融合後的圖像
cv.imwrite("D:/test/img4.jpg", dst)
print("圖檔融合已完成!")
image1為馬賽克圖,image2為修改大小後的原圖
image1權重為0.3,image2權重為0.7,如下圖:
image1權重為0.7,image2權重為0.3,如下圖:
這樣對比下來還是能看出融合的效果的
但是我自己還是覺得沒必要,感覺就沒怎麼經過馬賽克的過程,感覺基本上就是原圖了,是以不太懂這一步。
而且這裡大家可能根本看不出來這到底是不是我的圖檔組構成的啊,這裡我們放大之後可以看到:
這裡我們看到的确還是由我們的圖檔組構成的,并且融合之後的圖檔大小也會增大不少,但是相對于直接替換還是相對小了一點:
4.效果示範
中間的計算過程與融合過程經過加速了。
一開始我們先看的圖檔組的原圖,之後看的是圖檔組修改成馬賽克大小後的圖檔:
之後我們看的是先是原圖,接着是原圖修改大小後的樣子
接着就是原圖的馬賽克圖,最後就是融合形成的圖。
都看到這裡了,如果覺得對你有幫助的話,可以關注部落客的公衆号,新人up需要你的支援。
如果有什麼疑問或者的話,可以私聊部落客。
想要源碼可以關注公衆号回複 蒙太奇 自取哦。