主要思想及代码来源于这篇老哥的文章:https://zhuanlan.zhihu.com/p/168667043
有兴趣的小伙伴可以去看看。
其实之前我在b站上就曾经看到过这样一个软件:
顺便贴出软件的下载地址:https://xttx.lanzous.com/id6euad
使用教程:https://www.bilibili.com/video/BV14g4y1i7Ey
虽然我们不会但是,我们其实也能够说出原理大体上是啥。其实主要步骤就是两步:
**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("预处理待填充图片已完成!")
我们一开始不懂的就以为是计算色差,看了源码之后才知道原来是计算他们的直方图就行了。真的是被自己蠢到了。
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
既然需要比较直方图信息,那么我们就必须也要计算原图各区域的直方图信息,然后循环与图片组的直方图进行比较,并且同时记住当前与其最相似的图片,再循环结束后将其替换。
这其中我们也是重点讲一下这个函数 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("图片替换已完成!")
效果就是这样的:
其实到上面一步,我们的马赛克图片就做出来了,我自己觉得这样就够了,但是那个老哥还加了这一步就是将原图与我们的马赛克图进行了混合,但是说实话混合之后的确更像了,但是说实话不怎么看的出来是有马赛克合成的。
这里我们还是重点讲一个函数即 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,如下图:
中间的计算过程与融合过程经过加速了。
一开始我们先看的图片组的原图,之后看的是图片组修改成马赛克大小后的图片:
之后我们看的是先是原图,接着是原图修改大小后的样子
接着就是原图的马赛克图,最后就是融合形成的图。
都看到这里了,如果觉得对你有帮助的话,可以关注博主的公众号,新人up需要你的支持。
在这里插入图片描述