马赛克效果实现
马赛克效果就是将图像分成大小一致的图像块,每一个图像块都是一个正方形,并且在这个正方形中所有的像素值都相等,然后将这个正方形看做一个模板窗口,模板中对应的所有图像像素值都等于该模板左上角第一个像素的像素值,正方形模板的大小则决定了马赛克块的大小,即图像马赛克化的程度。
马赛克效果实现原理
(1)将需要马赛克的图像部位,全部赋值为该区域左上角的第一个像素的像素值。
(2)将需要马赛克的图像部位像素随机打乱
(3)随机用某一点代替需要马赛克区域内的所有像素值
代码
# 将图片制作成马赛克效果
def mosaic_effect(img):
new_img = img.copy()
h, w, n = img.shape
size = 10#马赛克大小
for i in range(size, h - 1 - size, size):
for j in range(size, w - 1 - size, size):
i_rand = random.randint(i - size, i)
j_rand = random.randint(j - size, j)
new_img[i - size:i + size, j - size:j + size] = img[i_rand, j_rand, :]
return new_imgif __name__ == "__main__":
img = cv2.imread("49.jpg")
cv2.imshow("0", img)
cv2.imshow("1", mosaic_effect(img))
cv2.waitKey()
cv2.destroyAllWindows()
上述代码将马赛克大小设置为10px。
视频马赛克
# 将视频中的人脸替换成马赛克
def mosaic_video_effect(img):
height, width, n = img.shape
new_img = img.copy()
size = 20
faceCascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = faceCascade.detectMultiScale(gray, scaleFactor=1.15, minNeighbors=2, minSize=(5, 5))
for (x, y, w, h) in faces:
for i in range(x + size, (x + w) - 1 - size, size):
for j in range(y + size, (y + h) - 1 - size, size):
if i - size > 0 and j + size < width and i + size < height and j - size > 0:
i_rand = random.randint(i - size, i)
j_rand = random.randint(j - size, j)
new_img[i - size:i + size, j - size:j + size] = img[i_rand, j_rand, :]
else:
new_img[x:x + w, y:y + h] = [255, 255, 255]
return new_img
if __name__ == "__main__":
cap = cv2.VideoCapture(0)
while (cap.isOpened()):
ret, frame = cap.read()
frame = mosaic_video_effect(frame)
cv2.imshow('video', frame)
c = cv2.waitKey(1)
if c == 27:
break
cap.release()
cv2.destroyAllWindows()
这里需要注意,由于值遮挡了人脸,因此不是全屏马赛克。当人脸移动到摄像头边缘时,i-size和j-size可能会小于0,j+size和i+size的值可能大于视频窗口的宽度和高度,这将导致数组越界报错。为了避免人脸在摄像头边缘时越界赋值,当人脸移动到摄像头边缘时,直接遮挡白色马赛克。
马赛克拼接效果实现
一幅图像由多个像素组成。为了生产马赛克拼图,将原始图像的每一小部分区域替换为与其颜色相似的图像,从而生成马赛克风格的图像。
项目思路大概分成分成以下三个步骤:
(1)计算素材库中每幅图像的平均色
(2)把目标图像切分成平均的色块,与素材库图像进行替换
(3)全部替换完之后,再与原始图像进行融合
如何计算图像的颜色相似度?这里使用RGB和HSV的概念。RGB色彩空间的三个分量都与亮度密切相关,只要亮度改变,三个分量就会改变,因此RGB对人来说是一种均匀性较差的色彩空间。因此要使用HSV色彩空间——Hue(色调)、Sayuration(饱和度)、Value(明度)
首先要导入库,使用Python中的pillow(PIL)库来处理图像,使用numpy包来计算数值。
import os import sys import time import math import numpy as np from PIL import Image,ImageOps from multiprocessing import Pool from colorsys import rgb_to_hsv,hsv_to_rgb from typing import List ,Tuple,Union
首先遍历这幅图像的每个像素点,获得每个像素点的RGB值,然后通过rgb_to_hsv()函数,将RGB值转化为HSV,再计算H、S、V的平均值。
class mosaic(object): def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int, OUT_SIZE: int) -> None: self.IN_DIR = IN_DIR self.OUT_DIR = OUT_DIR self.SLICE_SIZE = SLICE_SIZE self.REPATE = REPATE self.OUT_SIZE = OUT_SIZE def resize_pic(self,in_name:str,size:int)->Image: """转换图像大小""" img = Image.open(in_name) img = ImageOps.fit(img,(size,size),Image.LANCZOS) return img def get_avg_color(self,img:Image)->Tuple[float,float,float]: """计算图像的HSV平均值""" width,height = img.size pixels = img.load() if type(pixels) is not int: data = [] for x in range(width): for y in range(height): cpixel = pixels[x,y] #存储图像的像素值 data.append(cpixel) h = 0 s = 0 v = 0 count = 0 for x in range(len(data)): r = data[x][0] g = data[x][1] b = data[x][2] #获取一个点的RGB值 count += 1 hsv = rgb_to_hsv(r / 255.0,g / 255.0,b / 255.0) h += hsv[0] s += hsv[1] v += hsv[2] hAvg = round(h/count, 3) sAvg = round(s/count, 3) vAvg = round(v/count, 3) if count > 0: #像素点个数大于0 return (hAvg,sAvg,vAvg) else: raise IOError("读取图像数据失败") else: raise IOError("PIL 读取图像数据失败")
将素材图片全放在images文件夹中,然后对素材进行统一处理,使用resize_pic()将原始素材转化为统一的格式,再将转化后的图像的HSV平均值,并将其作为新的文件名进行保存。
class create_image_db(mosaic): """创建所需要的数据""" def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int, OUT_SIZE: int) -> None: super(create_image_db, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE, REPATE, OUT_SIZE) def make_dir(self)->None: os.makedirs(os.path.dirname(self.OUT_DIR),exist_ok=True) def get_image_paths(self)->List[str]: """获取文件夹中图像的地址""" paths = [] suffixs = ['png','jpg'] for file_ in os.listdir(self.IN_DIR): suffix = file_.split('.',1)[1] if suffix in suffixs: paths.append(self.IN_DIR + file_) else: print("非图像:%s " % file_) if len(paths)>0: print("一共找到了%s "% len(paths) + "幅图像") else: raise IOError("未找到任何图像") return paths def convert_image(self,path): """转换图像大小,同时计算一幅图像的HSV平均值""" img = self.resize_pic(path,self.SLICE_SIZE) color = self.get_avg_color(img) img.save(str(self.OUT_DIR)+str(color) + ".png") def convert_all_images(self)->None: """将所有图像进行转换""" self.make_dir() paths = self.get_image_paths() print("正在生产马赛克块...") pool = Pool() #对已有图像进行处理,转换为对应的色块 pool.map(self.convert_image,paths) pool.close() pool.join()
运行以上代码之后,会在当前文件夹下生产一个outputImage文件夹,下面开始生成马赛克图像。
(1)遍历生成的素材文件夹,获取该文件夹中所有图像的HSV平均值,保存在一个listy文件中
(2)将原始图像分为一个个小块,并计算每个小块的HSV的平均值
(3)将上文二者进行对比,找到HSV最相近的进行替换
(4)以此对图像进行上文操作,就可以生成一幅新的画
(5)通过Image.blend()函数将生成的图像与原始图像重合
class create_mosaic(mosaic): """创建马赛克图片 """ def __init__(self, IN_DIR: str, OUT_DIR: str, SLICE_SIZE: int, REPATE: int, OUT_SIZE: int) -> None: super(create_mosaic, self).__init__(IN_DIR, OUT_DIR, SLICE_SIZE, REPATE, OUT_SIZE) def read_img_db(self) -> List[List[Union[float, int]]]: """读取所有的图片 """ img_db = [] # 存储color_list for file_ in os.listdir(self.OUT_DIR): if file_ == 'None.png': pass else: file_ = file_.split('.png')[0] # 获得文件名 file_ = file_[1:-1].split(',') # 获得hsv三个值 file_ = [float(i) for i in file_] file_.append(0) # 最后一位计算图像使用次数 img_db.append(file_) return img_db def find_closiest(self, color: Tuple[float, float, float], list_colors: List[List[Union[float, int]]]) -> str: """寻找与像素块颜色最接近的图像""" FAR = 10000000 cur_closer = None # 初始化cur_closer为None,以便在找不到匹配项时捕获错误 for cur_color in list_colors: # list_colors是图像库中所有图像的平均hsv颜色 if len(cur_color) < 4: # 如果cur_color没有第四个元素,则跳过它 continue if cur_color[3] > self.REPATE: # 同一个图片使用次数不能太多 continue n_diff = np.sum((np.array(color) - np.array(cur_color[:3])) ** 2) if n_diff < FAR: # 找到更接近的颜色 FAR = n_diff cur_closer = cur_color[:] # 使用[:]来复制列表,以防原始数据被修改 if cur_closer is None: # 如果没有找到任何匹配项,可以抛出一个异常或返回一个默认值 raise ValueError("No matching color found") cur_closer[3] += 1 # 增加使用次数 return "({}, {}, {})".format(cur_closer[0], cur_closer[1], cur_closer[2]) # 返回hsv颜色 def make_puzzle(self, img: str) -> bool: """制作拼图 """ img = self.resize_pic(img, self.OUT_SIZE) # 读取图片并修改大小 color_list = self.read_img_db() # 获取所有的颜色的list width, height = img.size # 获得图片的宽度和高度 print("Width = {}, Height = {}".format(width, height)) background = Image.new('RGB', img.size, (255, 255, 255)) # 创建一个空白的背景, 之后向里面填充图片 total_images = math.floor( (width * height) / (self.SLICE_SIZE * self.SLICE_SIZE)) # 需要多少小图片 now_images = 0 # 用来计算完成度 for y1 in range(0, height, self.SLICE_SIZE): for x1 in range(0, width, self.SLICE_SIZE): try: # 计算当前位置 y2 = y1 + self.SLICE_SIZE x2 = x1 + self.SLICE_SIZE # 截取图像的一小块, 并计算平均hsv new_img = img.crop((x1, y1, x2, y2)) color = self.get_avg_color(new_img) # 找到最相似颜色的照片 close_img_name = self.find_closiest(color, color_list) close_img_name = self.OUT_DIR + str( close_img_name) + '.png' # 图片的地址 paste_img = Image.open(close_img_name) # 计算完成度 now_images += 1 now_done = math.floor((now_images / total_images) * 100) r = '\r[{}{}]{}%'.format("#" * now_done, " " * (100 - now_done), now_done) sys.stdout.write(r) sys.stdout.flush() background.paste(paste_img, (x1, y1)) except IOError: print('创建马赛克块失败') # 保持最后的结果 background.save('out_without_background.jpg') img = Image.blend(background, img, 0.5) img.save('out_with_background.jpg') return True #这里的参数 REPATE 表示每一张图片可以最多重复的次数。 如果我们图片足够多,可以设置为 REPATE=1,此时每一张图片只能使用一次。
最后的主函数
if __name__ == "__main__": filePath = os.path.dirname(os.path.abspath(__file__)) # 获取当前的路径 start_time = time.time() # 程序开始运行时间, 记录一共运行了多久 # 创建马赛克块, 创建素材库 createdb = create_image_db(IN_DIR=os.path.join(filePath, 'images/'), OUT_DIR=os.path.join(filePath, 'outputImages/'), SLICE_SIZE=100, REPATE=20, OUT_SIZE=5000) createdb.convert_all_images() # 创建拼图 (这里使用绝对路径) createM = create_mosaic(IN_DIR=os.path.join(filePath, 'images/'), OUT_DIR=os.path.join(filePath, 'outputImages/'), SLICE_SIZE=100, REPATE=20, OUT_SIZE=5000) out = createM.make_puzzle(img=os.path.join(filePath, 'Zelda.jpg')) # 打印时间 print("耗时: %s" % (time.time() - start_time)) print("已完成")