OpenCV-Python教程:统计函数~L1、L2、无穷范数、汉明范数(norm,NORM_HAMMING2)

原文链接:http://www.juzicode.com/opencv-python-statistics-norm

返回Opencv-Python教程

1、什么是范数

下图是百度百科关于范数的定义:

从定义可以看到L1范数是所有元素的绝对值的和;L2范数是所有元素(绝对值)的平方和再开方;无穷范数是所有元素取绝对值后再取最大值;在OpenCV中所有元素展开成一个集合构成了上述x1,x2……xn;

汉明范数不在上述定义中,汉明范数又称汉明距离,最开始用于数据传输的差错控制编码,表示两个相同长度的二进制数值其对应bit位不同的数量。统计两个数对应bit位的差异,需要对两个数值进行异或运算,统计异或结果中1的个数就是它们的汉明范数(汉明距离)。统计单个数值的汉明范数可以看做将这个数和0进行异或运算后,统计异或结果中1的个数,因为一个数值和0进行异或得到的是其自己,所以统计单个数值的汉明范数就是统计自身bit位为1的个数。计算一个数据集合的汉明范数就是计算这些单个元素汉明范数的总和。

2、接口

norm()用来计算图像的L1,L2,无穷范数以及汉明范数。OpenCV中提供了2种接口形式,第1种接口只需一个输入图像,第2种接口有2个输入图像。

第1种接口形式:

cv2.norm(src1[, normType[, mask]]) ->retval
  • 参数含义:
  • src1:输入图像,如果是多通道时不区分通道计算;求汉明范数时必须是单通道8bit数据类型;
  • normType:范数类型;
  • mask:掩码;
  • retval:返回的范数值,浮点类型;

第2种接口形式:

cv2.norm(src1, src2[, normType[, mask]]) ->retval
  • 参数含义:
  • src1:输入图像,如果是多通道时不区分通道计算;求汉明范数时必须是单通道8bit数据类型;
  • src2:输入图像;
  • normType:范数类型;
  • mask:掩码;
  • retval:返回的范数值,浮点类型;

src1、src2作为输入图像可以是单通道也可以是多通道图像,当使用多通道图像时,并不分开通道计算,这点和很多函数是不同的。如果要计算单独通道的范数,则需要先将通道分离。另外在计算汉明范数时,限定了输入图像必须为CV_8U类型的。

normType可选的类型有:

nomrType名称输入图像只有src1输入图像有src1和src2
NORM_L1 L1范数所有元素的绝对值的和src1-src2的所有元素的绝对值的和
NORM_L2 L2范数所有元素平方和的开平方src1-src2的所有元素平方和的开平方
NORM_L2SQR L2范数平方所有元素平方和src1-src2的所有元素平方和
NORM_INF 无穷范数元素中绝对值最大的数值src1-src2的元素中绝对值最大的数值
NORM_HAMMING 汉明范数计算src1和0的汉明距离,也即所有元素中1的位数的总和src1和src2异或后元素中1的位数的总和
NORM_HAMMING2 汉明范数2单个元素从右到左相邻的两个bit,如果不全0记为一个1,然后统计新的1的位数src1和src2异或后元素按照左侧的方法计算1的位数

注意这里当有2个输入图像src1和src2时,L1,L2和无穷范数的计算是先将2个图像相减,而计算汉明范数时则是将2个图像异或。

3、L1,L2和无穷范数

下面先来看下L1,L2和无穷范数:

import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)

img_src = cv2.imread('..\\samples\\data\\lena.jpg')#,cv2.IMREAD_GRAYSCALE) 
val = cv2.norm(img_src,cv2.NORM_L2)
print('lena图像L2范数:',val )
val = cv2.norm(img_src,cv2.NORM_L2SQR)
print('lena图像L2范数平方:',val )
val = cv2.norm(img_src,cv2.NORM_L1)
print('lena图像L1范数:',val )
val = cv2.norm(img_src,cv2.NORM_INF)
print('lena图像无穷范数:',val )

arr = np.eye(5) 
arr[1][0]=-5
val = cv2.norm(arr) #默认是L2范数
print('改造后np.eye(5)的L2范数:', val)
val = cv2.norm(arr,cv2.NORM_L2SQR)
print('改造后np.eye(5)的L2范数平方:',val )
val = cv2.norm(arr,cv2.NORM_L1)
print('改造后np.eye(5)的L1范数:',val )
val = cv2.norm(arr,cv2.NORM_INF)
print('改造后np.eye(5)的无穷范数:',val )

运行结果如下,从改造的5×5对角阵里可以很好的验证L1,L2和无穷范数,这个“矩阵”对角线上有5个数值1外加arr[1][0]=-5,L2范数平方就是5个1的平方加-5的平方等于30,L1范数就是5个1和-5的绝对值相加为10,无穷范数就是绝对值最大的数-5取绝对值后的5:

lena图像L2范数: 125059.42229996106
lena图像L2范数平方: 15639859106.0
lena图像L1范数: 100844698.0
lena图像无穷范数: 255.0
改造后np.eye(5)的L2范数: 5.477225575051661
改造后np.eye(5)的L2范数平方: 30.0
改造后np.eye(5)的L1范数: 10.0
改造后np.eye(5)的无穷范数: 5.0

4、汉明范数

接下来我们来看下汉明范数,需要注意的是汉明范数的计算要求输入图像只能是CV_8U类型的单通道数据。

NORM_HAMMING比较好理解,就是将每个元素用二进制表示,然后数其中的bit为1的数目,最后加起来有多少个1就是其NORM_HAMMING范数。

NORM_HAMMING2稍微复杂些,它需要先考察相邻的2个bit位,如果2个bit位不全为0记做一个1,即使2个bit都是1也只记做1。比如数值0x30=0b0011 0000,从右向左每2个bit位划分为1组,这样就划分为4组: 00 11 00 00 ,这样只有第2组中有2个bit位为1,所以这个时候0x30的NORM_HAMMING2就为1,再比如0x18 = 0b00011000,相邻的2bit划分后就是 00 01 10 00,这样有2组的bit位中有1,所以0x18的NORM_HAMMING2就为2。

下面的例子我们手动构造一个简单的numpy数组,这是一个2×2的数组,其中1个元素按照上表取值,其他3个元素为0,这样每次只变化1个数值来计算其汉明范数,对上面这个图表进行验证:

import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)

arr = np.array([[0,0],[0,0x01]],dtype=np.uint8)
print('0x01的NORM_HAMMING:',cv2.norm(arr,cv2.NORM_HAMMING) )
print('0x01的NORM_HAMMING2:',cv2.norm(arr,cv2.NORM_HAMMING2) )
arr = np.array([[0,0],[0,0x03]],dtype=np.uint8)
print('0x03的NORM_HAMMING:',cv2.norm(arr,cv2.NORM_HAMMING) )
print('0x03的NORM_HAMMING2:',cv2.norm(arr,cv2.NORM_HAMMING2) )
arr = np.array([[0,0],[0,0x0f]],dtype=np.uint8)
print('0x0f的NORM_HAMMING:',cv2.norm(arr,cv2.NORM_HAMMING) )
print('0x0f的NORM_HAMMING2:',cv2.norm(arr,cv2.NORM_HAMMING2) )
arr = np.array([[0,0],[0,0x30]],dtype=np.uint8)
print('0x30的NORM_HAMMING:',cv2.norm(arr,cv2.NORM_HAMMING) )
print('0x30的NORM_HAMMING2:',cv2.norm(arr,cv2.NORM_HAMMING2) )
arr = np.array([[0,0],[0,0x18]],dtype=np.uint8)
print('0x18的NORM_HAMMING:',cv2.norm(arr,cv2.NORM_HAMMING) )
print('0x18的NORM_HAMMING2:',cv2.norm(arr,cv2.NORM_HAMMING2) )
arr = np.array([[0,0],[0,0x1C]],dtype=np.uint8)
print('0x1C的NORM_HAMMING:',cv2.norm(arr,cv2.NORM_HAMMING) )
print('0x1C的NORM_HAMMING2:',cv2.norm(arr,cv2.NORM_HAMMING2) )
arr = np.array([[0,0],[0,0x1E]],dtype=np.uint8)
print('0x1E的NORM_HAMMING:',cv2.norm(arr,cv2.NORM_HAMMING) )
print('0x1E的NORM_HAMMING2:',cv2.norm(arr,cv2.NORM_HAMMING2) )
arr = np.array([[0,0],[0,0xFF]],dtype=np.uint8)
print('0xFF的NORM_HAMMING:',cv2.norm(arr,cv2.NORM_HAMMING) )
print('0xFF的NORM_HAMMING2:',cv2.norm(arr,cv2.NORM_HAMMING2) )

运行结果:

VX公众号: 桔子code / juzicode.com
cv2.__version__: 4.5.3
0x01的NORM_HAMMING: 1.0
0x01的NORM_HAMMING2: 1.0
0x03的NORM_HAMMING: 2.0
0x03的NORM_HAMMING2: 1.0
0x0f的NORM_HAMMING: 4.0
0x0f的NORM_HAMMING2: 2.0
0x30的NORM_HAMMING: 2.0
0x30的NORM_HAMMING2: 1.0
0x18的NORM_HAMMING: 2.0
0x18的NORM_HAMMING2: 2.0
0x1C的NORM_HAMMING: 3.0
0x1C的NORM_HAMMING2: 2.0
0x1E的NORM_HAMMING: 4.0
0x1E的NORM_HAMMING2: 3.0
0xFF的NORM_HAMMING: 8.0
0xFF的NORM_HAMMING2: 4.0

上面例子运行的结果和汉明范数示例中的结果是一致的。

接下来我们再进一步验证下NORM_HAMMING2所表示的“相邻2bit”的含义,考察一个8bit(np.unit8或CV_8U)的数值n,从右到左每2个bit如果不全为0,对应这2个bit就得到一个汉明距离1,否则就为0。我们就可以像下图这样设计4个掩码:0xC0,0x30,0x0C,0x03,如果这个数值n和这4个掩码进行位与,位与后的结果不为0就得到一个汉明距离1,否则就为0,再把这4个位与结果相加,就得到了这个8bit数值n的HAMMING2范数:

将上面的过程用代码表示,是下面这样的,其中输入数值n,返回HAMMING2范数:

def cal2bithamm(n):
    c = 0 
    for t in [0xc0,0x30,0x0c,0x03]:
        if t & n: c +=1 
    return c

有了计算单个数值HAMMING2范数计算方法,我们扩展到一个图像里,将一个图像先展开为一维数组,再计算每个元素的HAMMING2范数并累加:

img_src = cv2.imread('..\\samples\\data\\lena.jpg',cv2.IMREAD_GRAYSCALE) 
print('lena的NORM_HAMMING:',cv2.norm(img_src,cv2.NORM_HAMMING) )
print('lena的NORM_HAMMING2:',cv2.norm(img_src,cv2.NORM_HAMMING2) )

hamming2=0
img_src_flat = img_src.flatten() #展开成一维数组
for n in img_src_flat:
    hamming2 += cal2bithamm(n)   #单个计算,累加hamming2范数
print('lena的NORM_HAMMING2(验证计算):',hamming2)

运行结果:

lena的NORM_HAMMING: 1032143.0
lena的NORM_HAMMING2: 809303.0
lena的NORM_HAMMING2(验证计算): 809303

从运行结果可以看到手动计算的HAMMING2范数结果为809303,与OpenCV norm()函数计算的结果是一样的。

 

5、双图像输入

第2种接口形式中,有2路输入图像src1和src2,当使用该接口时,要保证输入的图像大小一样、图像的数据类型一样。下面这个例子中将lena图的bgr通道分离出来,用其中的b、g通道分别作为输入src1和src2,计算其L1、L2、无穷范数:

import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)

img_src_ = cv2.imread('..\\samples\\data\\lena.jpg')#,cv2.IMREAD_GRAYSCALE) 
b,g,r = cv2.split(img_src_) #分离通道作为2路图像输入
val = cv2.norm(b,g,cv2.NORM_L2)
print('lena图像b-vs-g L2范数:',val )
val = cv2.norm(b,g,cv2.NORM_L2SQR)
print('lena图像b-vs-g L2范数平方:',val )
val = cv2.norm(b,g,cv2.NORM_L1)
print('lena图像b-vs-g L1范数:',val )
val = cv2.norm(b,g,cv2.NORM_INF)
print('lena图像b-vs-g 无穷范数:',val )

运行结果:

VX公众号: 桔子code / juzicode.com
cv2.__version__: 4.5.3
lena图像b-vs-g L2范数: 13469.265421692455
lena图像b-vs-g L2范数平方: 181421111.0
lena图像b-vs-g L1范数: 5466267.0
lena图像b-vs-g 无穷范数: 123.0

OpenCV-Python教程:图像的减法运算、标量加减运算 一文我们知道图像的减法有cv2.subtract(),cv2.absdiff()以及numpy数组的减法,下面通过实验验证下norm()的第2种接口求L1、L2和无穷范数用的是哪一种等价减法:

#用absdiff减法验证,结果一样
val = cv2.norm(cv2.absdiff(b,g),cv2.NORM_L2)
print('lena图像cv2.absdiff(b,g) L2范数:',val )
val = cv2.norm(cv2.absdiff(b,g),cv2.NORM_L2SQR)
print('lena图像cv2.absdiff(b,g)L2范数平方:',val )
val = cv2.norm(cv2.absdiff(b,g),cv2.NORM_L1)
print('lena图像cv2.absdiff(b,g) L1范数:',val )
val = cv2.norm(cv2.absdiff(b,g),cv2.NORM_INF)
print('lena图像cv2.absdiff(b,g) 无穷范数:',val )

#用subtract验证,结果不一样
val = cv2.norm(cv2.subtract(b,g),cv2.NORM_L2)
print('lena图像cv2.subtract(b,g) L2范数:',val )
val = cv2.norm(cv2.subtract(b,g),cv2.NORM_L2SQR)
print('lena图像cv2.subtract(b,g)L2范数平方:',val )
val = cv2.norm(cv2.subtract(b,g),cv2.NORM_L1)
print('lena图像cv2.subtract(b,g) L1范数:',val )
val = cv2.norm(cv2.subtract(b,g),cv2.NORM_INF)
print('lena图像cv2.subtract(b,g) 无穷范数:',val )

#用numpy减法验证,结果不一样
val = cv2.norm(b-g,cv2.NORM_L2)
print('lena图像b-g L2范数:',val )
val = cv2.norm(b-g,cv2.NORM_L2SQR)
print('lena图像b-g L2范数平方:',val )
val = cv2.norm(b-g,cv2.NORM_L1)
print('lena图像b-g L1范数:',val )
val = cv2.norm(b-g,cv2.NORM_INF)
print('lena图像b-g 无穷范数:',val )

运行结果:

lena图像b-vs-g L2范数: 13469.265421692455           ######等价
lena图像b-vs-g L2范数平方: 181421111.0   
lena图像b-vs-g L1范数: 5466267.0
lena图像b-vs-g 无穷范数: 123.0
lena图像cv2.absdiff(b,g) L2范数: 13469.265421692455 ######absdiff等价第2种接口
lena图像cv2.absdiff(b,g)L2范数平方: 181421111.0     
lena图像cv2.absdiff(b,g) L1范数: 5466267.0
lena图像cv2.absdiff(b,g) 无穷范数: 123.0            
lena图像cv2.subtract(b,g) L2范数: 11262.058870384224
lena图像cv2.subtract(b,g)L2范数平方: 126833970.0
lena图像cv2.subtract(b,g) L1范数: 3498108.0
lena图像cv2.subtract(b,g) 无穷范数: 81.0
lena图像b-g L2范数: 82887.89184796535
lena图像b-g L2范数平方: 6870402615.0
lena图像b-g L1范数: 31595101.0
lena图像b-g 无穷范数: 255.0

从上面的实验可以看到,用cv2.absdiff()先计算出2个图像的结果,再用第1种接口计算出来的L1、L2和无穷范数,和用第2种接口直接求出来的L1、L2、无穷范数是一样的。

nomrType表格和前面的例子中我们看到第2种函数接口计算2个输入图像的L1,L2和无穷范数时,实际上是计算cv2.absdiff(src1,src2)的相应范数,接下来再看下汉明范数是不是也是如此。下面的例子首先根据第2种接口计算出汉明范数,再将2个图像异或后用第1种接口计算汉明范数,最后是3种减法得到的结果用第1种接口计算汉明范数:

import numpy as np
import cv2
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)

img_src_ = cv2.imread('..\\samples\\data\\lena.jpg')#,cv2.IMREAD_GRAYSCALE) 
b,g,r = cv2.split(img_src_) #分离通道
val = cv2.norm(b,g,cv2.NORM_HAMMING)
print('lena图像b-vs-g NORM_HAMMING范数:',val )
val = cv2.norm(b,g,cv2.NORM_HAMMING2)
print('lena图像b-vs-g NORM_HAMMING范数2:',val )
#进行异或运算
img_xor=cv2.bitwise_xor(b,g)
val = cv2.norm(img_xor,cv2.NORM_HAMMING)
print('lena图像xor-b g NORM_HAMMING范数:',val )
val = cv2.norm(img_xor,cv2.NORM_HAMMING2)
print('lena图像xor-b g NORM_HAMMING范数2:',val )
#absdiff(b,r)
val = cv2.norm(cv2.absdiff(b,g),cv2.NORM_HAMMING)
print('lena图像absdiff(b,g) NORM_HAMMING范数:',val )
val = cv2.norm(cv2.absdiff(b,g),cv2.NORM_HAMMING2)
print('lena图像absdiff(b,g) NORM_HAMMING范数2:',val )
#subtract(b,r)
val = cv2.norm(cv2.subtract(b,g),cv2.NORM_HAMMING)
print('lena图像subtract(b,g) NORM_HAMMING范数:',val )
val = cv2.norm(cv2.subtract(b,g),cv2.NORM_HAMMING2)
print('lena图像subtract(b,g) NORM_HAMMING范数2:',val )
#b-g通道
val = cv2.norm(b-g,cv2.NORM_HAMMING)
print('lena图像b-g NORM_HAMMING范数:',val )
val = cv2.norm(b-g,cv2.NORM_HAMMING2)
print('lena图像b-g NORM_HAMMING范数2:',val )

运行结果:

cv2.__version__: 4.5.3
lena图像b-vs-g NORM_HAMMING范数: 926420.0    ######等价异或
lena图像b-vs-g NORM_HAMMING范数2: 687029.0  
lena图像xor-b g NORM_HAMMING范数: 926420.0   ######等价第2种接口
lena图像xor-b g NORM_HAMMING范数2: 687029.0  
lena图像absdiff(b,g) NORM_HAMMING范数: 659236.0
lena图像absdiff(b,g) NORM_HAMMING范数2: 520833.0
lena图像subtract(b,g) NORM_HAMMING范数: 375979.0
lena图像subtract(b,g) NORM_HAMMING范数2: 298930.0
lena图像b-g NORM_HAMMING范数: 1047036.0
lena图像b-g NORM_HAMMING范数2: 710907.0

从运行结果可以看到,第2种接口计算2个图像的汉明范数和第1种接口计算2个图像异或后得到的汉明范数是一样的。

总结:图像的L1范数是所有元素的绝对值之和,L2范数是所有元素的平方和开方,无穷范数是绝对值最大的元素取绝对值;汉明范数约束在单通道8bit的图像类型,NORM_HAMMING统计的是所有元素bit位为1的总数,NORM_HAMMING2表示的范数则是相邻2bit不为0的总数。

第2种接口形式范数计算中,L1、L2、无穷范数可以看做是2个图像用absdiff()相减后得到的图像再用第1种接口形式计算的范数,汉明范数则可以看做是2个图像用bitwise_xor()异或后得到的图像再用第1种接口形式计算的范数。

 

扩展阅读:

  1. OpenCV-Python教程
  2. OpenCV-Python教程:图像的减法运算、标量加减运算

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注