第3章:图像运算

图像的 加法运算位运算 都是比较基础的运算。但是很多复杂的图像处理功能正是借助这些基础的运算来完成的。例如:位平面分解、图像异或加密、数字水印等。

one. 图像加法运算:

在图像处理过程中,经常需要对图像进行加法运算。可以通过加号运算符"+"对图像进行加法运算,也可以通过cv2.add()函数对图像进行加法运算。

  • 加号运算符"+"
  • cv2.add()

通常情况下,在灰度图像中,像素用8个比特位(一个字节)来表示,像素值的范围是[0, 255]。两个像素值进行加法运算时,求得的和很可能超过255。上述两种不同的加法运算方式,对超过255的数值的处理方式是不一样的。

1. 加号运算符"+":

使用加号运算符"+"对图像a(像素值为a)和图像b(像素值为b)进行求和运算,遵循以下规则:

a + b = { a + b , a + b ≤ 255 m o d ( a + b , 256 ) , a + b > 255 a+b = \begin{cases} a +b, \quad a+b≤255 \\ mod(a+b, 256), \quad a+b >255 \end{cases} a+b={ a+b,a+b255mod(a+b,256),a+b255

式中mod():取模运算mod(a + b, 256)表示计算"a+b"的和除以256取余数。

根据以上规则,两个像素在进行加法运算时:

  • 如果两个图像对应像素值的和小于或等于255,则直接相加得到运算结果。例如:28和36相加得到64
  • 如果两个图像对应像素值的和大于255,则将运算结果对256取模。例如:255+58 = 313,则计算得到的结果是(255+58) % 256 = 57。

例如: 使用随机数数组模拟灰度图像,观察使用"+"对像素值求和的结果。

注意:通过将数组的类型定义为dtype = np.uint8,可以保证数组值的范围在[0, 255]之间

import numpy as np

img1 = np.random.randint(0, 256, size=[3, 3], dtype=np.uint8)
img2 = np.random.randint(0, 256, size=[3, 3], dtype=np.uint8)

print('img1=\n', img1)
print('img2=\n', img2)
print('img1+img2=\n', img1+img2)

结果:

img1=
 [[241  85  79]
 [ 62  29 113]
 [ 33 129 150]]
img2=
 [[168  93 112]
 [ 32 246 228]
 [229  89 107]]
img1+img2=
 [[153 178 191]
 [ 94  19  85]
 [  6 218   1]]

注意:本例题中加法进行取模,是由数组类型dtype=np.uint8进行的。

2. cv2.add()函数:

函数cv2.add()可以用来计算图像像素值相加的和,语法格式:

  • result = cv2.add(a, b)

使用cv2.add()对像素值a, b进行求和运算时,遵循以下规则: a + b = { a + b a + b ≤ 255 255 a + b > 255 a + b = \begin{cases} a + b \quad a + b≤255 \\ 255 \quad a+b>255 \end{cases} a+b={ a+ba+b255255a+b255

cv2.add()的参数有一下3种形式:

  • resutl = cv2.add(图像1, 图像2):两个参数都是图像,此时参与运算的图像大小和类型必须保持一致
  • result = cv2.add(数值,图像):第一个参数是数值,第二个参数是图像,此时将超过图像饱和值得数值处理为最大值。
  • result = cv2.add(图像,数值):第一个参数是图像,第二个参数是数值,此时将超过图像饱和值得数值处理为最大值。

例题: 分别使用加号运算符和函数cv2.add()计算两幅灰度图像的像素值之和。

import cv2
a = cv2.imread('../lena.bmp', 0)
b = a
result1 = a + b
resutl2 = cv2.add(a, b)
cv2.imshow('original', a)
cv2.imshow('result1', result1)
cv2.imshow('result2', resutl2)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

从处理结果可以看出:

  • 使用加号运算符:会将和大于255的只进行取模运算,取模后大于255的这部分值变得更小了,导致本该更亮的点变得更暗了。
  • 使用函数cv2.add():将和大于255的值处理为饱和值255.图像像素值增大,图像整体变亮。

two. 图像加权和:

所谓图像加权和,就是在计算两幅图像的像素值之和时,将每幅图像的权重考虑进来,可以用公式表示为: d s t = s a t u r a t e ( s r c 1 × α + s r c 2 × β + γ ) dst = saturate(src1×\alpha + src2×\beta + \gamma) dst=saturate(src1×α+src2×β+γ)

式中,stautrate()表示取饱和值。图像进行加权和计算时,要求src1和src2必须大小、类型相同,但是具体是什么类型和通道没有特殊限制。他们可以是任意的数据类型,也可以有任意数量的通道。(灰度图像或者彩色图像)只要二者相同即可。

OpenCV中使用函数cv2.addWeighted() ,实现图像的加权和运算(混合、融合)。

语法格式:

  • dst = cv2.addWeighted(src1, alpha, src2, beta, gamma)

    其中,参数alpha和beta是src1和src2所对应的系数,它们的和可以等于1,也可以不等于1.该函数实现的功能是dat = src1 x alpha + src2 x beta + gamma。需要注意,式中参数gamma的可以是0,但是该参数是必选参数,不能省略。上式可以理解为 “result = 图像1 x 系数1 + 图像2 x 系数2 + 亮度调节量”

例1: 使用数组演示函数cv2.addWeighted()的使用。

import cv2
a = cv2.imread('../lena.bmp')
b = cv2.imread('../boat.512.tiff')
result = cv2.addWeighted(a, 0.6, b, 0.4, 0)
cv2.imshow('lena', a)
cv2.imshow('boat', b)
cv2.imshow('result', result)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

three. 按位逻辑运算:

逻辑运算是一种非常重要的运算方式,图像处理过程中经常要按照位进行逻辑运算,下面介绍OpenCV中按位进行逻辑运算,简称位运算。

OpenCV中常见的位运算函数:

在这里插入图片描述

1. 按位与运算:

在与运算中,当参与与运算的两个逻辑值都是真时,结果才为真。其逻辑关系可以类比下图串联电路,只有两个开关都闭合时才会亮。

在这里插入图片描述

在这里插入图片描述

在OpenCV中,可以使用cv2.bitwise_and()函数来实现按位与操作,其语法格式:

  • dst = cv2.bitwise_and(src1, src2[, mask])

式中:

  • dst:表示与输入值具有同样大小的array输出值
  • src1:表示第一个array或scalar类型的输入值。
  • src2:表示第二个array或scalar类型的输入值。
  • mask:表示可选操作掩码,8位单通道array。

注意: 按位与操作有如下特点:

  • 将任何数值N与数值0进行按位与操作,得到的都是数值0
  • 将任何数值N(这里仅考虑8位值)与数值255(8位2进制是1111111)进行按位与操作,都会得到数值N本身。

在这里插入图片描述

例1: 构造一个淹没图像,使用按位与运算保留图像中被掩膜指定的部分。

import cv2
import numpy as np
a = cv2.imread('../lena.bmp', 0)
b = np.zeros(a.shape, dtype=np.uint8)
b[100: 400, 200: 400] = 255
b[100: 500, 100: 200] = 255

c = cv2.bitwise_and(a, b)
cv2.imshow('a', a)
cv2.imshow('b', b)
cv2.imshow('c', c)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

注意:除了会对灰度图像进行掩膜操作,还经常需要针对BGR模式的彩色图像使用掩膜提取指定部分。由于按位与操作要求参与运算的数据有相同的通道,所以无法直接将彩色图像与单通道的掩膜图像进行按位与操作。一般情况下,可以通过将掩膜图像转换为BGR模式的彩色图像,让彩色图像与掩膜图像进行按位与操作,实现掩膜运算

import cv2
import numpy as np
a = cv2.imread('../lena512color.tiff', 1)
b = np.zeros(a.shape, dtype=np.uint8)
b[100: 400, 200: 400] = 255
b[100: 500, 100: 200] = 255

c = cv2.bitwise_and(a, b)
cv2.imshow('a', a)
cv2.imshow('b', b)
cv2.imshow('c', c)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

2. 按位或运算:

或运算的规则是,当参与运算的两个逻辑值中有一个为真时,结果就为真。如下图并联电路:

在这里插入图片描述

在这里插入图片描述

在OpenCV中,可以使用cv2.bitwise_or()函数来实现按位或运算,其语法格式为:

  • dst = cv2.bitwise_or(src1, src2, [, mask])

式中:

  • dst:表示与输入值具有同样大小的array输出值
  • src1:表示第一个array或scalar类型的输入值。
  • src2:表示第二个array或scalar类型的输入值。
  • mask:表示可选操作掩码,8位单通道array。

在这里插入图片描述

3.按位非运算:

非运算是取反操作:

  • 当运算数为真时,结果为假;
  • 当运算数为假时,结果为真

在这里插入图片描述

按位非运算是指将数值转换成2进制值后,在对应的位置上进行非运算。

在OpenCV中,可以使用函数cv2.bitwise_not()来实现按位取反操作,其语法格式为:

  • dst = cv2.bitwise_not(src, [mask])

式中:

  • dst:表示与输入值具有同样大小的array输出值
  • src1:表示第一个array或scalar类型的输入值。
  • mask:表示可选操作掩码,8位单通道array。

在这里插入图片描述

4. 按位异或运算:

异或运算也叫半加运算,其运算法则与不带二进制位的加法类似,其英文为"exclusive OR",因此函数通常表示为xor

在这里插入图片描述

按位异或运算是指将数值转换成2进制值后,在对应的位置上进行异或运算。

在这里插入图片描述

在OpenCV中,可以使用函数cv2.bitwise_xor()来实现按位取反操作,其语法格式为:

  • dst = cv2.bitwise_xor(src1, src2, [mask])

式中:

  • dst:表示与输入值具有同样大小的array输出值
  • src1:表示第一个array或scalar类型的输入值。
  • src2:表示第二个array或scalar类型的输入值。
  • mask:表示可选操作掩码,8位单通道array。

four. 掩膜:

在OpenCV中很多函数都会指定一个掩膜,也成掩码,例如:

result = cv2.add(参数1, 参数2, 掩膜)

当使用掩膜参数时,操作只会在掩膜值为非空的像素点上执行,将其他的像素点置为0.

例1:

i m a g e 1 = ∣ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ∣ i m a g e 2 = ∣ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 ∣ m a s k = ∣ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 1 ∣ image1 = \begin{vmatrix} 3 & 3 & 3 & 3 & 3 \\ 3 & 3 & 3 & 3 & 3 \\3 & 3 & 3 & 3 & 3 \\3 & 3 & 3 & 3 & 3 \\3 & 3 & 3 & 3 & 3 \end{vmatrix} \quad image2 = \begin{vmatrix} 5 & 5 & 5 & 5 & 5 \\ 5 & 5 & 5 & 5 & 5 \\5 & 5 & 5 & 5 & 5 \\5 & 5 & 5 & 5 & 5 \\5 & 5 & 5 & 5 & 5 \end{vmatrix} \quad mask= \begin{vmatrix} 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 \\0 & 0 & 0 & 0 & 0 \\0 & 0 & 0 & 1 & 1 \\0 & 0 & 0 & 1 & 1 \end{vmatrix} image1=3333333333333333333333333image2=5555555555555555555555555mask=0000000000000000001100011

经过img3 = cv2.add(img1, img2, mask=mask) 运算后得image3:

i m g 3 = ∣ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 8 0 0 0 8 8 ∣ img3= \begin{vmatrix} 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 \\0 & 0 & 0 & 0 & 0 \\0 & 0 & 0 & 8 & 8 \\0 & 0 & 0 & 8 & 8 \end{vmatrix} img3=0000000000000000008800088

在计算时,img3计算的是在掩膜mask控制下的"img1 + img2"结果。在计算时,掩码为1的部分对应"img1+img2",其他部分的像素值均为0。

注意:

​ 不仅是在图像的加法运算中会有使用掩膜的情况,在位运算等其他运算中也会有使用掩膜的情况。

​ 在之前的位运算中,将彩色的原始图像与掩膜图像进行计算,由于按位与操作要求参与运算的数据应该有相同的通道,所以无法直接将彩色图像与单通道的掩膜图像进行按位与操作。我们需要将掩膜图像转化为BGR模式的彩色图像,让彩色图像与(彩色)掩膜图像进行按位与操作,从而实现掩膜运算。

​ 实际上,在函数中所使用的掩膜参数可以是8位单通道图像。所以,可以将掩膜图像作为按位与函数cv2.bitwise_and(src1, src2 [, mask])中参数mask的值,完成掩膜运算。此时,让待处理的彩色图像同时作为函数cv2.bitwise_and(src1, src2 [, mask]) 的参数src1, src2,使用掩膜图像作为掩膜参数,完成按位与操作,即可得到由掩膜控制的彩色图像。

补充: 任何数值与滋生进行按位与计算,得到的仍是自身。

import cv2
import numpy as np

img1 = cv2.imread('../lena512color.tiff')
w, h, c = img1.shape
mask = np.zeros((w, h), dtype=np.uint8)
mask[100: 400, 200: 400] = 255
mask[100: 500, 100: 200] = 255
img2 = cv2.bitwise_and(img1, img1, mask=mask)
cv2.imshow('img1', img1)
cv2.imshow('mask', mask)
cv2.imshow('img2', img2)
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

five. 图像与数值运算:

在上述的加法运算和按位运算中,参与运算的的连个参数都是相同的,实际上既可以是两幅图像,也可以是一幅图像一个数值。

例如:如果想增加图像的整体亮度,可以将每一个像素值都加上一个特定的值。在实现时,可以个图像加上一个统一像素值的图像,也可以给图像加上一个固定值。

例1: img1 和 img2的原始值分别为:

i m a g e 1 = ∣ 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 ∣ i m a g e 2 = ∣ 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 ∣ image1 = \begin{vmatrix} 3 & 3 & 3 & 3 & 3 \\ 3 & 3 & 3 & 3 & 3 \\3 & 3 & 3 & 3 & 3 \\3 & 3 & 3 & 3 & 3 \\3 & 3 & 3 & 3 & 3 \end{vmatrix} \quad image2 = \begin{vmatrix} 5 & 5 & 5 & 5 & 5 \\ 5 & 5 & 5 & 5 & 5 \\5 & 5 & 5 & 5 & 5 \\5 & 5 & 5 & 5 & 5 \\5 & 5 & 5 & 5 & 5 \end{vmatrix} image1=3333333333333333333333333image2=5555555555555555555555555

  • img3 = cv2.add(img1, img2)运算后,得到img3为:

i m a g e 3 = ∣ 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 ∣ image3 = \begin{vmatrix} 8 & 8 & 8 & 8 & 8 \\ 8 & 8 & 8 & 8 & 8 \\8 & 8 & 8 & 8 & 8 \\8 & 8 & 8 & 8 & 8 \\8 & 8 & 8 & 8 & 8 \end{vmatrix} image3=8888888888888888888888888

  • img4 = cv2.add(img1, 6)运算后,得到img4为:

i m a g e 4 = ∣ 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 ∣ image4 = \begin{vmatrix} 9 & 9 & 9 & 9 & 9 \\ 9 & 9 & 9 & 9 & 9 \\9 & 9 & 9 & 9 & 9 \\9 & 9 & 9 & 9 & 9 \\9 & 9 & 9 & 9 & 9 \end{vmatrix} image4=9999999999999999999999999

  • img5 = cv2.add(6, img1)运算后,得到img5为:

i m a g e 5 = ∣ 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 ∣ image5 = \begin{vmatrix} 11 & 11 & 11 & 11 & 11 \\ 11 & 11 & 11 & 11 & 11 \\11 & 11 & 11 & 11 & 11 \\11 & 11 & 11 & 11 & 11 \\11 & 11 & 11 & 11 & 11 \end{vmatrix} image5=11111111111111111111111111111111111111111111111111

six. 位平面分解:

​ 将灰度图像中处于同一比特位上的二进制像素进行组合,得到一幅新的二进制图像,该图像称为灰度图像的一个位平面。这个过程称为位平面分解。例如,将一幅灰度图像内所有像素点上处于二进制位内最低为上的值进行组合,可以构成"最低有效位"位平面。

​ 在8位灰度图中,每一个像素都是使用8位二进制来表示,其值范围在[0, 255]之间,可以将其中值表示为:

在这里插入图片描述

式中, a i a_i ai 的可能只为0或1。可以看出,各个 a i a_i ai的权重是不一样的, a 7 a_7 a7的权重最高, a 0 a_0 a0 的权重最低。这代表 a 7 a_7 a7 的值对图像的影响最大,而 a 0 a_0 a0 的值对图像影响最小。

​ 通过提取灰度图像像素点二进制像素值的每一比特位的组合,可以得到多个位平面图像。图像中全部像素值的 a i a_i ai 值所构成的位平面,称为第i个位平面(第i层)。在8位灰度图中,可以组成8个二进制图像,即可以将原图分解成8个位平面。

​ 根据上述分析,像素值中各个 a i a_i ai的权重是不一样的:

  • a 7 a_7 a7的权重最高,所构成的位平面与原图像相关性最高,改位平面看起来通常原图像最类似
  • a 0 a_0 a0 权重最低,所构成的位平面与原图像相关性最低,该平面看起来通常是杂乱无章的。

借助按位与运算可以实现位平面分解。例如,有灰度图像O的像素值为:

在这里插入图片描述

其对应的二进制值为:

在这里插入图片描述

将所有像素的 a i a_i ai值进行组合,便会得到图像的8个位平面。a-h一次是 a 0 − a 7 a_0 - a_7 a0a7

在这里插入图片描述

​ 针对RGB图像,如果将R通道、G通道、B通道中的每一个通道对应的位平面进行合并,即可组成新的RGB彩色图像。例如,针对一幅RGB图像,将其R通道的第3个位平面、G通道的第3个位平面、B通道的第3个位平面进行合并,则可以构成一幅新的RGB彩色图像,我们称之为原始图像的第3个位平面。通过上述方式,可以完成对彩色图像的位平面分解。

以灰度图像为例,介绍位平面分解的具体步骤:

  1. 图像预处理:读取原始图像O,获取原始图像的宽M和高N

  2. 构造提取矩阵:使用按位与操作能够很方便的将一个数值指定位上的数字提取出来。例如,下图分别使用不同的提取因子F来提取数值N中的特定位。
    在这里插入图片描述

    根据上述分析结果来看,可以建立一个值均为 2 n 2^n 2n 的Mat作为提取矩阵,用来与原始图像进行按位与运算,以提取第n个位平面。

    提取矩阵Mat中的值可以是:
    在这里插入图片描述

  3. 提取位平面:

    将灰度图像与提取矩阵进行按位与运算,得到各个位平面。

    将像素值与一个值为 2 n 2^n 2n的数值进行按位与运算,能够保证像素值的第n位保持不变,而将其余各位均置0。因此,通过像素值与特定值的按位与运算,能够提取像素值的指定二进制位的值。同理,通过按位运算,能够提取图像的指定位平面。

    例如,有一个像素点的像素值为219,要提取其第4个二进制位的值,即提取该像素值的第4位信息(序号从0开始)。此时,需要借助的提取值是" 2 4 2^4 24 = 16"。
    在这里插入图片描述

    例如:

    针对下图图像O提取位平面:

    在这里插入图片描述

    其对应的二进制形式为:

    在这里插入图片描述

    提取其中第3个位平面,则需要建立元素值均为 2 3 2^3 23(8) 的提取矩阵:

    在这里插入图片描述

    将原图像与提取矩阵进行按位与运算得到:
    在这里插入图片描述

    补充: 提取位平面也可以通过将二进制像素值右移指定位,然后对2取模得到。例如要提取第n个位平面,可以将像素向右侧移动n位,然后对2取模,就可以得到第n个位平面。

  4. 阈值处理:

    ​ 通过计算得到的位平面是一个二值图像(即0值和特定值),如果直接将上述得到的位平面显示出来,则会得到一张近似黑色的图像。因为当前显示的是8位灰度图,当像素值较小时,显示的图像会近似黑色。

    ​ 也就是说,每次提取位平面后,要想让二值的位平面以黑白的颜色显示出来,就要将得到的二值位平面进行阈值处理,将其中不为0的数设置为255。

  5. 显示图像:

例题:提取一幅图像的各个位平面。

import cv2
import numpy as np

lena = cv2.imread('../lena.bmp', 0)
cv2.imshow('lena', lena)
r, c = lena.shape
x = np.zeros((r, c, 8), dtype=np.uint8)
for i in range(8):
    x[:, :, i] = 2**i
r = np.zeros((r, c, 8), dtype=np.uint8)
for i in range(8):
    r[:, :, i] = cv2.bitwise_and(lena, x[:, :, i])
    mask = r[:, :, i] > 0
    r[mask] = 255
    cv2.imshow(str(i), r[:, :, i])
cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

seven. 图像加密和解密:

通过对图像的按位异或运算可以实现图像的加密和解密。

通过对原始图像与密钥图像进行按位异或,可以实现加密;将加密后的图像与密钥图像再次进行按位异或,可以实现解密。

在这里插入图片描述

根据异或运算的规则,假设:

  • xor(a, b) = c

则,可以得到:

  • xor(c, b) = a

  • xor(c, a) = b

按位异或过程示例:

在这里插入图片描述

从上述结果看,a, b, c具有如下关系:

  • a:明文,原始数据
  • b:密钥
  • c:密文,通过xor(a, b)实现

则上诉数据的操作可以理解为:

  • 加密过程:将明文a与密钥b进行按位异或,完成加密,得到密文c
  • 解密过程:将密文c与密钥b进行按位异或,完成解密,得到明文a

位运算是指针对二进制位进行的运算,利用位运算即可实现对像素点的加解密。

在图像处理过程中,需要处理的图像像素点的值通常是灰度值,其方位是[0, 255]。例如,某个像素点的值是216(明文),则可以使用178(该值由加密者自定义),作为密钥对其进行加密,让这两个数的二进制值按位进行异或运算即完成加密,得到一个密文106。当需要解密时,将密文106与密钥178进行按位异或运算,即可得到原始像素点的值216。

  • bit_xor(216, 178) = 106
  • bit_xor(106, 178) = 216

加密过程:

在这里插入图片描述

解密过程:

在这里插入图片描述

对图像内的每一个像素点重复上述操作,即可完成对图像的加密、解密操作。

例题: 随机生成一幅图像作为密钥,对原始图像进行加密

import cv2
import numpy as np

lena = cv2.imread('../lena.bmp', 0)
r, c = lena.shape
key = np.random.randint(0, 256, size=[r, c], dtype=np.uint8)
encryption = cv2.bitwise_xor(lena, key)
decryption = cv2.bitwise_xor(encryption, key)
cv2.imshow('lena', lena)
cv2.imshow('key', key)
cv2.imshow('encryption', encryption)
cv2.imshow('decryption', decryption)

cv2.waitKey()
cv2.destroyAllWindows()

在这里插入图片描述

eight. 数字水印:

​ 最低有效位值二进制数值中第0位,即最低位。最低有效位信息隐藏指的是,将需要隐藏的二值图像信息,嵌入到载体图像的最低有效位,即将载体图像的最低有效位替换为当前需要隐藏的二值图像,从而实现二值图像隐藏的目的。由于二值图像处于载体的最低有效位上,所以对载体图像影响不明显,具有较高隐蔽性。

这种信息隐藏也被称为数字水印。通过该方式可以实现:

  • 信息隐藏
  • 版权认证
  • 身份认证

等功能,数字水印信息可以是文本、视频、音频等形式。

1. 原理:

从位平面的角度考虑,数字水印分为以下两部:

  • 嵌入过程:将数字水印信息(一幅二值图像)嵌入到载体图像的第0个位平面。

    例如,一幅灰度图像为:

    在这里插入图片描述

    其对应的二进制为:

    在这里插入图片描述

    有灰度二值图像W:

    在这里插入图片描述

    将一幅灰度二值水印图像W嵌其中得:

    在这里插入图片描述

    由于信息最低有效位对值的大小影响有限,因此图像不会发生明显变换,肉眼不可见

    为了便于理解这里仅介绍了载体图像为灰度图像的情况,在实际中可以根据需要在多个通道内嵌入相同的水印(提高鲁棒性),或在不同的通道嵌入不同的水印(提高嵌入量)等。

    • 注意:实际处理过程中,原始图像和水印图像是可以彩色图像,这时候需要先对他们进行通道分解,图层分解。然后在将数字水印嵌入到载体图像。
  • 提取过程:将载体图像的第0个位平面提取出来,得到数字水印。

    提取的过程就是嵌入的过程的逆运算。

2.实现方法:

最低有限位水印的实现 包括嵌入过程和提取过程,下面介绍其具体实现方法:

嵌入过程:

  1. 载体图像预处理:读取原始载体图像,并获取载体图像的行数M和列数N

  2. 建立提取矩阵:建立一个MxN大小、元素值均为254的提取矩阵。用来提取图像的高7位

  3. 通过按位与运算,提取载体图像的高7位,将最低位置0。

  4. 水印图像处理:某些情况下需要对水印图像进行简单处理,如当水印图像为8位灰度图的二值图像时,就需要将其转换为二进制的二值图像,以方便将其嵌入到载体图像的最低位。
    在这里插入图片描述

  5. 嵌入水印:将原始载体图像进行"保留高7位,最低位置零"操作后,我们能得到一幅新的图像,将新的图像与水印图像进行按位或运算,就能将水印信息嵌入到载体图像。

  6. 显示图像:完成上述操作后可以对原始图像、水印图像、含水印图像进行显示。
    在这里插入图片描述

提取过程:

  1. 含水印载体图像处理:读取含水印的载体图像,并获取含水印载体图像的大小MxN
  2. 建立提取矩阵:建议一个大小为MxN的,值均为1的提取矩阵。
  3. 提取水印信息:将含水印的载体图像与提取矩阵进行按位与操作,提取水印信息。
  4. 计算去除水印后的载体图像
  5. 显示图像
    在这里插入图片描述

注意:对像素值进行对2取模,可以获取像素值的最低有效位。因此可以通过对像素值进行对2取模的方式,来获取最低有效位的水印信息。

例如:模拟数字水印的嵌入和提取过程

import cv2
import numpy as np

lena = cv2.imread('../lena.bmp', 0)
watemark = cv2.imread('../boat.512.tiff', 0)
w = watemark[:, :] > 0
watemark[w] = 1

r, c = lena.shape
t254 = np.ones((r, c), dtype=np.uint8) * 254
lena_h7 = cv2.bitwise_and(lena, t254)
e = cv2.bitwise_or(lena_h7, watemark)
t1 = np.ones((r, c), dtype=np.uint8)
wm = cv2.bitwise_and(e, t1)

w = wm[:, :] > 0
wm[w] = 255
cv2.imshow('lena', lena)
cv2.imshow('watemake', watemark*255)
cv2.imshow('e', e)
cv2.imshow('wm', wm)
cv2.waitKey()
cv2.destroyAllWindows()

注意:对像素值进行对2取模,可以获取像素值的最低有效位。因此可以通过对像素值进行对2取模的方式,来获取最低有效位的水印信息。

例如:模拟数字水印的嵌入和提取过程

import cv2
import numpy as np

lena = cv2.imread('../lena.bmp', 0)
watemark = cv2.imread('../boat.512.tiff', 0)
w = watemark[:, :] > 0
watemark[w] = 1

r, c = lena.shape
t254 = np.ones((r, c), dtype=np.uint8) * 254
lena_h7 = cv2.bitwise_and(lena, t254)
e = cv2.bitwise_or(lena_h7, watemark)
t1 = np.ones((r, c), dtype=np.uint8)
wm = cv2.bitwise_and(e, t1)

w = wm[:, :] > 0
wm[w] = 255
cv2.imshow('lena', lena)
cv2.imshow('watemake', watemark*255)
cv2.imshow('e', e)
cv2.imshow('wm', wm)
cv2.waitKey()
cv2.destroyAllWindows()

该文章内容参考总结自《OpenCV轻松入门》这本书,详细内容可以参考这本书。

猜你喜欢

转载自blog.csdn.net/weixin_57440207/article/details/121103553
今日推荐