使用 Python 批量添加图片水印

使用 Python 批量添加图片水印

本文所给出的代码适用于为图片添加简单水印,实质上是通过程序实现两张图片的叠加,从而达到水印的效果。

文章前半段主要是介绍了几种 python 中图像合成的方式,如果需要代码,请直接查看文章后半部分。

Python 中关于图像合并的方式

这里主要借助了 PLI(Pillow) 库中的几个方法。合并图像的方式常用的有四种:

  1. Image.paste()
  2. Image.blend()
  3. Image.composite()
  4. Image.alpha_composite()

每种方法的特点各不相同,本文将简单介绍以上四种方法,最终的实现使用了 Image.paste() 进行处理。

这里可以参考:
Image.alpha_composite()——实现一张背景透明图像和一张背景不透明图像的合成

在正式了解这几个方法之前还有几个基础概念需要简单介绍一下:

Basic Concept

  1. Image Mode
    在 PIL 中对于图像有不同的表示模式,如常用的 RGB RGBA CMYK ,不同图像模式操作上会有所不同,也可能会导致意想不到的效果,因此在进行处理的时候需要注意图像模式的问题。
    关于 Python PIL 的 Image Mode ,可以参考这篇文章:
    【Python】PIL库中图像的mode参数

    想要改变图像的 Image Mode 可以使用 Image.convert() 对其进行转化

  2. Alpha – 透明度
    RGBA 通道中,A 表示了 Alpha ,在原有的 RGB 的基础上添加了透明度的描述。
    在图像文件的格式上,.jpg 是无法表示透明度的,因此需要 .png 图像来存储有透明度的图像。

  3. Mask – 掩码
    很容易想到一个名词 – “子网掩码”,这里图像掩码的作用也是一样,对粘贴的图像与掩码图像进行比较,从而确定保留哪一部分。更加通俗的可以理解为“蒙版”或者“遮罩”。
    可以参考文章:对图像处理中掩码的理解

    官方的解释为:

    If a mask is given, this method updates only the regions indicated by the mask. You can use either "1", "L", "LA", "RGBA" or "RGBa" images (if present, the alpha band is used as mask). Where the mask is 255, the given image is copied as is. Where the mask is 0, the current value is preserved. Intermediate values will mix the two images together, including their alpha channels if they have them.

    当图像像素值为 255 时,粘贴的图像与其运算原值不变,如果为 0 ,则恒为0,也就是不粘贴该像素;对于有透明度的图像则也会对透明通道进行运算(混合在一起)。

Image.paste() 方法

paste 可以被翻译为“粘贴”,因此也很好理解它的效果逻辑,即图层的覆盖,因此 paste 的顺序是很重要的。

该方法需要通过一个实例对象进行调用。

函数原型

在 PIL 中的函数原型为:def paste(self, im, box=None, mask=None): ,共三个参数(包含两个可选参数)

self box mask
必选参数 可选参数 可选参数
要粘贴的图像 需要粘贴的位置,可以为 2 元组或 4 元组,不填写则默认为(0,0) 掩码图像,不填写默认全部覆盖

对于掩码,可以使用的图像模式有:1, L, LA, RGBA

测试示例

这里提供了几个测试:

  1. rightPaste
    正确的粘贴图像,即得到我们想要的效果
  2. noBox
    不添加 Box 参数,默认从左上角进行粘贴
  3. noMask
    不添加 Mask 参数,不使用遮罩,导致水印透明部分直接覆盖了 origin ,在转换为 RGB 之后显示为黑色
  4. swapOrder
    交换粘贴的顺序,先粘贴水印再粘贴原图,完全看不到水印,类似图层覆盖的效果
  5. saveWithNoConvert
    保存时不更改图像模式,该示例效果并不明显,在某些情况下会导致图片出现异常
  6. noConvert
    读取图像时不更改图像模式,编译器报错,无法进行处理。

具体效果如下:
paste-all

测试代码

from PIL import Image
from matplotlib import pyplot as plt
import os

def rightPaste(waterMark,origin):
    waterMark = waterMark.convert('RGBA');
    origin = origin.convert('RGBA');

    new_width = (int)(origin.width/2);
    new_height = (int)(waterMark.height*(new_width/waterMark.width));# 等比缩放

    wMark = waterMark.resize((new_width,new_height))

    # 创建底图
    baseImg = Image.new('RGBA',origin.size);

    # paste 原图和水印
    baseImg.paste(origin,(0,0),origin);
    baseImg.paste(wMark,((int)(origin.width/4),(int)((origin.height-wMark.height)/2)),wMark);

    baseImg.convert('RGB').save("Blog/paste/paste-rightPaste.png");


def noConvert(waterMark,origin):
    new_width = (int)(origin.width/2);
    new_height = (int)(waterMark.height*(new_width/waterMark.width));# 等比缩放

    wMark = waterMark.resize((new_width,new_height))

    # 创建底图
    baseImg = Image.new('RGBA',origin.size);

    # paste 原图和水印
    baseImg.paste(origin,(0,0),origin);
    baseImg.paste(wMark,((int)(origin.width/4),(int)((origin.height-wMark.height)/2)),wMark);

    baseImg.convert('RGB').save("Blog/paste/paste-noConvert.png");


def noMask(waterMark,origin):
    waterMark = waterMark.convert('RGBA');
    origin = origin.convert('RGBA');

    new_width = (int)(origin.width/2);
    new_height = (int)(waterMark.height*(new_width/waterMark.width));# 等比缩放

    wMark = waterMark.resize((new_width,new_height))

    # 创建底图
    baseImg = Image.new('RGBA',origin.size);

    # paste 原图和水印
    baseImg.paste(origin,(0,0),origin);
    baseImg.paste(wMark,((int)(origin.width/4),(int)((origin.height-wMark.height)/2)));

    baseImg.convert('RGB').save("Blog/paste/paste-noMask.png");

def noBox(waterMark,origin):
    waterMark = waterMark.convert('RGBA');
    origin = origin.convert('RGBA');

    new_width = (int)(origin.width/2);
    new_height = (int)(waterMark.height*(new_width/waterMark.width));# 等比缩放

    wMark = waterMark.resize((new_width,new_height))

    # 创建底图
    baseImg = Image.new('RGBA',origin.size);

    # paste 原图和水印
    baseImg.paste(origin,(0,0),origin);
    baseImg.paste(wMark, box=None, mask=wMark);

    baseImg.convert('RGB').save("Blog/paste/paste-noBox.png");

def swapOrder(waterMark,origin):
    waterMark = waterMark.convert('RGBA');
    origin = origin.convert('RGBA');

    new_width = (int)(origin.width/2);
    new_height = (int)(waterMark.height*(new_width/waterMark.width));# 等比缩放

    wMark = waterMark.resize((new_width,new_height))

    # 创建底图
    baseImg = Image.new('RGBA',origin.size);

    # paste 原图和水印
    baseImg.paste(wMark,((int)(origin.width/4),(int)((origin.height-wMark.height)/2)),wMark);
    baseImg.paste(origin,(0,0),origin);

    baseImg.convert('RGB').save("Blog/paste/paste-swapOrder.png");

def saveWithNoConvert(waterMark,origin):
    waterMark = waterMark.convert('RGBA');
    origin = origin.convert('RGBA');

    new_width = (int)(origin.width/2);
    new_height = (int)(waterMark.height*(new_width/waterMark.width));# 等比缩放

    wMark = waterMark.resize((new_width,new_height))

    # 创建底图
    baseImg = Image.new('RGBA',origin.size);

    # paste 原图和水印
    baseImg.paste(origin,(0,0),origin);
    baseImg.paste(wMark,((int)(origin.width/4),(int)((origin.height-wMark.height)/2)),wMark);

    baseImg.save("Blog/paste/paste-saveWithNoConvert.png");

def merge():
    image = Image.open("Blog/paste/paste-rightPaste.png")
    plt.subplot(231),plt.imshow(image,'gray'), plt.title('rightPaste')
    plt.axis('off')
    image = Image.open("Blog/paste/paste-noBox.png")
    plt.subplot(232),plt.imshow(image,'gray'), plt.title('noBox')
    plt.axis('off')
    image = Image.open("Blog/paste/paste-noMask.png")
    plt.subplot(233),plt.imshow(image,'gray'), plt.title('noMask')
    plt.axis('off')
    image = Image.open("Blog/paste/paste-swapOrder.png")
    plt.subplot(234),plt.imshow(image,'gray'), plt.title('swapOrder')
    plt.axis('off')
    image = Image.open("Blog/paste/paste-saveWithNoConvert.png")
    plt.subplot(235),plt.imshow(image,'gray'), plt.title('saveWithNoConvert')
    plt.axis('off')
    image = Image.open("Blog/paste/paste-noConvert.png")
    plt.subplot(236),plt.imshow(image,'gray'), plt.title('noConvert')
    plt.axis('off')

    plt.savefig("Blog/paste/paste-all.png",dpi = 400)

waterMark = Image.open("Blog/logo.png")
origin = Image.open("Blog/origin3.jpg")

if(os.path.exists("Blog/paste") == False):
    os.mkdir("Blog/paste")

rightPaste(waterMark,origin)
noBox(waterMark,origin)
noMask(waterMark,origin)
swapOrder(waterMark,origin)

saveWithNoConvert(waterMark,origin)
# noConvert(waterMark,origin) 
"""
    因为不转换图像模式程序无法正常运行
    这里为了程序正常运行将其注释
    并在对应的位置添加了一张报错的截图
"""

merge()
# By SDUT Bulbul
# In 2022.Nov.

Image.blend() 方法

该方法会使用插值生成一张新图片,因此在调用的时候为直接使用 Image.blend() 而不是创建实例对象后通过实例对象调用。

注意!该方法要求两张图片拥有相同的 图像模式 和 尺寸!

函数原型

在 PIL 中的函数原型为:def blend(im1, im2, alpha): ,共三个参数

im1 im1 alpha
必选参数 必选参数 必选参数
第一个图像 第二个图像 插值的 Alpha 因子,范围为 0~1的一个浮点数

函数的差值规则为:
$$
out = image_1 \times \left( 1.0 – alpha \right) + image_2 \times alpha
$$

测试示例

这里提供了几个测试:

  1. alphaZero
    透明度为 0,仅可看到第一张图
  2. alphaHalf
    透明度为 0.5,两张照片融合显示, 均有 50% 的透明效果
  3. alphaOne
    透明度为 1,仅可看到第二张图
  4. swapImageAlphaZero
    交换图片位置, 透明度为 0,与 3 的效果相同
  5. swapImageAlphaHalf
    交换图片位置, 透明度为 0.5,与 2 的效果相同 (这里选取的 0.5 不具有代表性)
  6. swapImageAlphaOne
    交换图片位置, 透明度为 1,与 1 的效果相同
  7. diffImageMode
    两张图片使用不同的图像模式,编译器报错,无法进行处理。
  8. diffSize
    两张图片使用不同的大小,编译器报错,无法进行处理。

具体效果如下:
blend-all

测试代码

from PIL import Image
from matplotlib import pyplot as plt
import os

def alphaZero(waterMark, origin):
    waterMark = waterMark.resize(origin.size);
    newImg = Image.blend(waterMark, origin,0);
    newImg.convert('RGB').save("Blog/blend/alphaZero.png")

def alphaHalf(waterMark, origin):
    waterMark = waterMark.resize(origin.size);
    newImg = Image.blend(waterMark, origin,0.5);
    newImg.convert('RGB').save("Blog/blend/alphaHalf.png")

def alphaOne(waterMark, origin):
    waterMark = waterMark.resize(origin.size);
    newImg = Image.blend(waterMark, origin,1);
    newImg.convert('RGB').save("Blog/blend/alphaOne.png")

def swapImageAlphaZero(waterMark, origin):
    waterMark = waterMark.resize(origin.size);
    newImg = Image.blend(origin,waterMark,0);
    newImg.convert('RGB').save("Blog/blend/swapImageAlphaZero.png")

def swapImageAlphaHalf(waterMark, origin):
    waterMark = waterMark.resize(origin.size);
    newImg = Image.blend(origin,waterMark,0.5);
    newImg.convert('RGB').save("Blog/blend/swapImageAlphaHalf.png")

def swapImageAlphaOne(waterMark, origin):
    waterMark = waterMark.resize(origin.size);
    newImg = Image.blend(origin,waterMark,1);
    newImg.convert('RGB').save("Blog/blend/swapImageAlphaOne.png")

def diffImageMode(waterMark, origin):
    wMark = waterMark.convert('RGBA')
    newOrigin = origin.convert('RGB')
    waterMark = waterMark.resize(origin.size);
    newImg = Image.blend(wMark,newOrigin,0.5);
    newImg.convert('RGB').save("Blog/blend/diffImageMode.png")


def diffSize(waterMark, origin):
    newImg = Image.blend(waterMark,origin,0.5);
    newImg.convert('RGB').save("Blog/blend/diffSize.png")

def merge():
    image = Image.open("Blog/blend/alphaZero.png")
    plt.subplot(421),plt.imshow(image,'gray'), plt.title('alphaZero')
    plt.axis('off')
    
    image = Image.open("Blog/blend/alphaHalf.png")
    plt.subplot(423),plt.imshow(image,'gray'), plt.title('alphaHalf')
    plt.axis('off')
    
    image = Image.open("Blog/blend/alphaOne.png")
    plt.subplot(425),plt.imshow(image,'gray'), plt.title('alphaOne')
    plt.axis('off')
    
    image = Image.open("Blog/blend/diffImageMode.png")
    plt.subplot(427),plt.imshow(image,'gray'), plt.title('diffImageMode')
    plt.axis('off')
    
    image = Image.open("Blog/blend/swapImageAlphaZero.png")
    plt.subplot(422),plt.imshow(image,'gray'), plt.title('swapImageAlphaZero')
    plt.axis('off')
    
    image = Image.open("Blog/blend/swapImageAlphaHalf.png")
    plt.subplot(424),plt.imshow(image,'gray'), plt.title('swapImageAlphaHalf')
    plt.axis('off')
    
    image = Image.open("Blog/blend/swapImageAlphaOne.png")
    plt.subplot(426),plt.imshow(image,'gray'), plt.title('swapImageAlphaOne')
    plt.axis('off')

    image = Image.open("Blog/blend/diffSize.png")
    plt.subplot(428),plt.imshow(image,'gray'), plt.title('diffSize')
    plt.axis('off')

    plt.tight_layout(pad=0.4, w_pad=1, h_pad=1) # 调整绘图布局
    plt.savefig("Blog/blend/blend-all.png",dpi = 400)


waterMark = Image.open("Blog/logo.png").convert('RGBA')
origin = Image.open("Blog/origin3.jpg").convert('RGBA')

if(os.path.exists("Blog/blend") == False):
    os.mkdir("Blog/blend")

alphaZero(waterMark,origin)
alphaHalf(waterMark,origin)
alphaOne(waterMark,origin)
swapImageAlphaZero(waterMark,origin)
swapImageAlphaHalf(waterMark,origin)
swapImageAlphaOne(waterMark,origin)
# diffImageMode(waterMark,origin)
# diffSize(waterMark,origin)
"""
    不更改图像模式和大小会造成程序错误
    因此这里将其注释并添加了两张报错图片
"""
merge()

# By SDUT Bulbul
# In 2022.Nov.

Image.composite() 方法

该方法实际上是调用了 paste() 方法,在源码中先对 image2 进行了复制,进行了 paste()
但由于需要传入两个图像参数,因此不需要实例对象来调用,直接使用 Image.composite() 即可。

# PIL 库中的部分源码
def composite(image1, image2, mask):
    image = image2.copy()
    image.paste(image1, None, mask)
    return image

函数原型

在 PIL 中的函数原型为:def composite(image1, image2, mask): ,共三个参数

image1 image2 mask
必选参数 必选参数 必选参数
第一个图像 第二个图像 图像掩码

对于掩码,可以使用的图像模式有:1, L, RGBA

测试示例

  1. simple
    正常调用
  2. simpleChangeMask
    1 的基础上更改 Mask ,这里需要注意 Mask 的大小,如果超出了 image1 的大小则会报错
  3. swapImage
    交换了两个图像参数的位置
  4. swapImageChangeMask
    交换位置之后更改 Mask ,这里不需要调整大小是因为此时的 image1 对应着 origin ,而从 originwaterMark 中任选一个永远不会出现尺寸比 image1 小的情况
  5. noMask
    编译器报错,无法进行处理。

这里要注意添加的位置,默认是左上角(能否改变位置没有进行研究),不同的图片尺寸以及遮罩种类也会有不同的效果,有的甚至是出现了奇怪的错误。

具体效果如下:
composite-all

测试代码

from PIL import Image
from matplotlib import pyplot as plt
import os

def simple(waterMark, origin):
    newImg = Image.composite(waterMark, origin,waterMark);
    newImg.convert('RGB').save("Blog/composite/composite-simple.png")

def simpleChangeMask(waterMark,orign):
    wMark = waterMark.resize(origin.size)
    # 不调整大小会报错
    newImg = Image.composite(wMark,origin,origin);
    newImg.convert('RGB').save("Blog/composite/composite-simpleChangeMask.png")


def swapImage(waterMark, origin):
    wMark = waterMark.resize(origin.size)
    newImg = Image.composite(origin,waterMark,wMark);
    newImg.convert('RGB').save("Blog/composite/composite-swapImage.png")

def swapImageChangeMask(waterMark, origin):
    newImg = Image.composite(origin, waterMark,origin);
    newImg.convert('RGB').save("Blog/composite/composite-swapImageChangeMask.png")

def noMask(waterMark,origin):
    newImg = Image.composite(waterMark, origin);
    newImg.convert('RGB').save("Blog/composite/composite-noMask.png")

def merge():
    image = Image.open("Blog/composite/composite-simple.png")
    plt.subplot(231),plt.imshow(image,'gray'), plt.title('simple')
    plt.axis('off')

    image = Image.open("Blog/composite/composite-waterMarkInSimple.png")
    plt.subplot(232),plt.imshow(image,'gray'), plt.title('waterMarkInSimple')
    plt.axis('off')
    
    image = Image.open("Blog/composite/composite-simpleChangeMask.png")
    plt.subplot(233),plt.imshow(image,'gray'), plt.title('simpleChangeMask')
    plt.axis('off')
    
    image = Image.open("Blog/composite/composite-swapImage.png")
    plt.subplot(234),plt.imshow(image,'gray'), plt.title('swapImage')
    plt.axis('off')
    
    image = Image.open("Blog/composite/composite-swapImageChangeMask.png")
    plt.subplot(235),plt.imshow(image,'gray'), plt.title('swapImageChangeMask')
    plt.axis('off')
    
    image = Image.open("Blog/composite/composite-noMask.png")
    plt.subplot(236),plt.imshow(image,'gray'), plt.title('noMask')
    plt.axis('off')
    
    plt.tight_layout(pad=0.4, w_pad=1, h_pad=1) # 调整绘图布局
    plt.savefig("Blog/composite/composite-all.png",dpi = 400)


waterMark = Image.open("Blog/logo.png").convert('RGBA')
origin = Image.open("Blog/origin3.jpg").convert('RGBA')

if(os.path.exists("Blog/composite") == False):
    os.mkdir("Blog/composite")

simple(waterMark, origin)
simpleChangeMask(waterMark, origin)
swapImage(waterMark, origin)
swapImageChangeMask(waterMark, origin)
noMask(waterMark, origin)
"""
    不添加 Mask 属性会报错
    因此这里将其注释并添加了一张报错图片
"""
merge()

# By SDUT Bulbul
# In 2022.Nov.

Image.alpha_composite() 方法

该方法只需要两个含有透明度的图像即可完成融合。
由于需要传入两个图像参数,因此不需要实例对象来调用,直接使用 Image.alpha_composite() 即可。

因为图像包含透明度,因此不需要图像掩码就可以直接进行融合。

注意!该方法要求两张图片拥有相同的尺寸!且图像模式必须为 RGBA

函数原型

在 PIL 中的函数原型为:def alpha_composite(im1, im2): ,共三个参数

im1 im2
必选参数 必选参数
第一个图像,必须为RGBA 第二个图像,必须为RGBA

测试示例

  1. simple
    正常调用,类似粘贴的效果 image2 覆盖了 image1
  2. swapSimple – 改变两张图片的顺序
    调换了顺序
  3. noRGBA
    使用非 RGBA 图像模式的图片进行合成,编译器报错,无法进行处理。

具体效果如下:
alpha_composite-all

测试代码

from PIL import Image
from matplotlib import pyplot as plt
import os

def simple(waterMark, origin):
    newImg = Image.alpha_composite(waterMark, origin);
    newImg.convert('RGB').save("Blog/alpha_composite/alpha_composite-simple.png")

def swapSimple(waterMark, origin):
    newImg = Image.alpha_composite(origin,waterMark)
    newImg.convert('RGB').save("Blog/alpha_composite/alpha_composite-swapSimple.png")

def noRGBA(waterMark, origin):
    wMark  = waterMark.convert('RGB')
    newOrigin = origin.convert('RGB')
    newImg = Image.alpha_composite(wMark,newOrigin)
    newImg.convert('RGB').save("Blog/alpha_composite/alpha_composite-noRGBA.png")

def merge():
    image = Image.open("Blog/alpha_composite/alpha_composite-simple.png")
    plt.subplot(131),plt.imshow(image,'gray'), plt.title('simple')
    plt.axis('off')
    
    image = Image.open("Blog/alpha_composite/alpha_composite-swapSimple.png")
    plt.subplot(132),plt.imshow(image,'gray'), plt.title('swapSimple')
    plt.axis('off')
    
    image = Image.open("Blog/alpha_composite/alpha_composite-noRGBA.png")
    plt.subplot(133),plt.imshow(image,'gray'), plt.title('noRGBA')
    plt.axis('off')
    
    plt.tight_layout(pad=0.4, w_pad=1, h_pad=1) # 调整绘图布局
    plt.savefig("Blog/alpha_composite/alpha_composite-all.png",dpi = 400)


waterMark = Image.open("Blog/logo.png").convert('RGBA')
origin = Image.open("Blog/origin3.jpg").convert('RGBA')

waterMark = waterMark.resize(origin.size)

if(os.path.exists("Blog/alpha_composite") == False):
    os.mkdir("Blog/alpha_composite")

# simple(waterMark,origin)
# swapSimple(waterMark,origin)
# noRGBA(waterMark,origin)
"""
    不转换为 RGBA 会报错
    因此这里将其注释并添加了一张报错图片
"""
merge()

# By SDUT Bulbul
# In 2022.Nov.

添加水印的解决思路

思路很简单:

  1. 获取指定路径下的所有图片
  2. 读取一张原图
  3. 获取原图的横纵比
  4. 按照指定的横纵比对水印图片进行缩放
  5. 创建一个与原图同等大小的底图
  6. 将原图 paste 到底图上
  7. 将水印图片 paste 到底图上
  8. 保存粘贴了两次的底图
  9. 重复 2-7 步,直到所有图片遍历结束

image-process-flow-chart

代码实现

from PIL import Image
import os

# 寻找水印图片,并提示信息
if(os.path.exists("./logo.png") == False):
    print("没有找到水印图片,请检查命名是否为‘logo.png’!")
    os.system("pause")
    os._exit(0);

waterMark = Image.open("./logo.png").convert('RGBA')

    # 合并函数,将目标图片与水印结合并且保存为一张新图片
def merge(imgPath,times):
    print("第 "+ str(times) +" 张图片,图片名:\t"+ imgPath)

    # 打开一张新图片,并且转化为 RGBA 模式
    newImg = Image.open("./origin/"+imgPath).convert('RGBA')

    # 获取宽高,用于缩放水印
    new_width = (int)(newImg.width/10)
    new_height = (int)(waterMark.height*(new_width/waterMark.width)) # 等比缩放

    # 对水印进行缩放
    wMark = waterMark.resize((new_width,new_height))

    # 创建底图
    baseImg = Image.new('RGBA',newImg.size);

    # paste 原图和水印
    baseImg.paste(newImg,(0,0),newImg);
    baseImg.paste(wMark,(newImg.width - new_width-20,newImg.height-new_height-20),wMark);

    # 保存最终图片,需要先转换模式为 RGB
    baseImg = baseImg.convert('RGB');
    if(os.path.exists("./result/")==False):     
        os.mkdir("./result/")
    baseImg.save("./result/" + imgPath);

imgList= os.listdir('./origin');
print("共找到 " + str(imgList.__len__()) + " 张图片")

cnt = 0 # 记录第几张图片
# 循环遍历每张图片并调用函数处理
for img in imgList:
    cnt+=1
    merge(img,cnt)

# 提示处理完毕信息
print("处理完毕!")
os.system("pause")

# By SDUT Bulbul
# In 2022.Nov.

本程序的一些解释

本程序的水印缩放是按照原图宽度的 $\frac{1}{10}$ 进行缩放的,同时添加了 20px,因此在分辨率较小的图片上,水印会有些糊,同时会显得不是那么“靠边”。
diffImage-all

程序完成之后还使用了 pyinstaller 将其打包成了 exe 文件,在发给“甲方”的同时做了一个 pdf 的文档,这里放两个关键的截图吧:

use-guide1

use-guide2

关于水印,还可以做什么?

记得早些年,有一则阿里员工抢月饼被开除的新闻,新闻中提到了一种“隐水印”,隐隐约约记得在知乎看到一篇解析,似乎是和快速傅里叶变换(FFT)有关。

最早跟媒体中心的小伙伴交流的时候想到的就是这个,可以做到不影响图片效果,又可以进行防伪处理。

原理是通过 2D-FFT 将图片的时域转换为频域,然后在频域上添加水印,再通过 2D-IFFT(逆变换) 转换为原图。
经过了多次尝试后我也并没有实现预期的效果,因此暂时放弃了这个方向,只能做一个简易的替代品(就是本文的传统水印)。

关于 2D-FFT 制作“隐水印”(或者叫 盲水印),可以了解一下下面的几篇文章:
从傅立叶变换到盲水印(中)——图片盲水印实现
如何给图片加盲水印?盲水印和图片隐写术实现及原理

参考

[1] PRIS-SCMonkey . Image.alpha_composite( )——实现一张背景透明图像和一张背景不透明图像的合成.
[2] cugzyc . Python用image.paste进行图像处理,粘贴原图的裁剪区域,是黑色的一块.
[3] LXYnizhan . 对图像处理中掩码的理解
[4] mjiansun . 【Python】PIL库中图像的mode参数
[5] 温柔则刚 . python实现两个图片的叠加融合.
[6] 澪同学 . 从傅立叶变换到盲水印(中)——图片盲水印实现
[7] 老刘博客 . 如何给图片加盲水印?盲水印和图片隐写术实现及原理

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇