OpenCV-Python图像位运算详解:取反、与或非及浮点处理

桃夭
桃夭
发布时间:2026-10-15 | 4人浏览过
原创

本文详解OpenCV-Python中的图像位运算,包括按位取反、与、或、异或操作。通过具体代码示例展示uint8及浮点类型图像的处理差异,解析浮点数内存存储对位运算结果的影响,并介绍标量与多通道图像交互的技巧。

1、按位取反bitwise_not()

按位取反操作会将数值的每个二进制位进行反转,即1变为0,0变为1。例如,十六进制数0xf0取反后变为0x0f。对于uint8类型的数据,取反前后的数值相加结果通常为0xff(即255)。以下示例演示了对lena.jpg和opencv-logo.png两张图片分别执行按位取反操作的过程。


import cv2

print('cv2.__version__:',cv2.__version__)
 
img1 = cv2.imread('..\\lena.jpg') 
img2 = cv2.imread('..\\opencv-logo.png' ) 
 
img_ret1 = cv2.bitwise_not(img1)
print('img1[161,199]:    ',img1[161,199])
print('img_ret1[161,199]:',img_ret1[161,199])
cv2.imshow('lena-not-juzicode',img_ret1)
 
img_ret2 = cv2.bitwise_not(img2)
print('img2[100,200]:    ',img2[100,200])
print('img_ret2[100,200]:',img_ret2[100,200])
cv2.imshow('logo-not-juzicode',img_ret2)
 
cv2.waitKey(0)

运行结果:

cv2.__version__: 4.5.2 img1[161,199]: [109 105 201] img_ret1[161,199]: [146 150 54] img2[100,200]: [ 0 0 255] img_ret2[100,200]: [255 255 0]

以lena.jpg中坐标[161,199]的像素点为例,其B通道原始值为109(二进制0110 1101)。取反后该通道值变为146(二进制1001 0010)。通过对比二进制位可见,每一位都发生了翻转:原为0的变为1,原为1的变为0。

观察左侧lena图像取反后的效果,可能会让人联想到二十年前流行的胶片相机洗出的底片,这种负片效果具有鲜明的视觉特征。

按位取反在实际应用中十分广泛,例如在OCR文字识别领域。通常书籍为白纸黑字,背景为白色,文字为黑色。经过二值化处理后,目标文字为黑色,背景为白色。若直接进行图像切割,分离出的将是白色背景而非黑色文字。通过执行取反操作,可以将黑色文字转换为白色,从而便于切割提取目标文字。下图展示了“白纸黑字”取反前后的对比效果。

bitwise_not()函数仅接受一个图像实例作为输入。相比之下,后续介绍的按位与、或、异或等逻辑运算通常需要两个图像实例(numpy数组),或者一个图像实例与一个标量数据。与图像的加减乘除运算类似,当涉及两个图像实例时,要求这两个图像的行列尺寸必须一致。

2、按位与bitwise_and()、或bitwise_or()、异或bitwise_xor()

按位与、或、异或操作需要两个图像对象,或者一个图像对象和一个标量数据相互作用。其接口形式如下:

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

以下是两个图像执行按位与、或、异或运算的具体示例。


import cv2

print('cv2.__version__:',cv2.__version__)
 
img1 = cv2.imread('..\\lena.jpg' )[0:300,0:300]
img2 = cv2.imread('..\\messi5.jpg' )[0:300,0:300]
img_ret1 = cv2.bitwise_and(img1,img2)
print('img1[161,199]:    ',img1[161,199])
print('img2[161,199]:    ',img2[161,199])
print('img_ret1[161,199]:',img_ret1[161,199])
cv2.imshow('and-juzicode',img_ret1) 
 
img_ret2 = cv2.bitwise_or(img1,img2)
print('img_ret2[161,199]:',img_ret2[161,199])
cv2.imshow('or-juzicode',img_ret2) 
 
img_ret3 = cv2.bitwise_xor(img1,img2)
print('img_ret3[161,199]:',img_ret3[161,199])
cv2.imshow('xor-juzicode',img_ret3) 
 
cv2.waitKey(0)

运行结果:

cv2.__version__: 4.5.2 img1[161,199]: [109 105 201] img2[161,199]: [105 43 32] img_ret1[161,199]: [105 41 0] img_ret2[161,199]: [109 107 233] img_ret3[161,199]: [ 4 66 233]

与算术运算类似,两个图像进行按位运算时,要求它们的尺寸和通道数必须一致。不同于算术运算可以通过dtype参数声明新图像的数据类型,按位运算接口中不存在dtype参数,因此参与位运算的两个图像数据类型必须保持一致。

同样地,按位运算也可以是一个图像与一个标量进行交互。标量数据类型可以是一个单独的数值,也可以是包含四个数值的四元组,这与算术运算类似。但与算术运算不同的是,对于3通道图像,还可以使用包含三个数值的三元组与该图像进行标量位运算。

以下示例展示了3通道图像分别与四元组、三元组以及单个数值进行位运算的情况。


import cv2

print('cv2.__version__:',cv2.__version__)
 
img1 = cv2.imread('..\\lena.jpg' )[0:300,0:300]
img_ret1 = cv2.bitwise_and(img1,(0x3f,0x3f,0x3f,0))
print('img1[161,199]:    ',img1[161,199])
print('img_ret1[161,199]:',img_ret1[161,199])
cv2.imshow('and-juzicode',img_ret1) 
 
img_ret2 = cv2.bitwise_or(img1,(0x0f,0x0f,0x0f))
print('img_ret2[161,199]:',img_ret2[161,199])
cv2.imshow('or-juzicode',img_ret2) 
 
img_ret3 = cv2.bitwise_xor(img1,0xf0)
print('img_ret3[161,199]:',img_ret3[161,199])
cv2.imshow('xor-juzicode',img_ret3) 
 
cv2.waitKey(0)

运行结果:

cv2.__version__: 4.5.2 img1[161,199]: [109 105 201] img_ret1[161,199]: [45 41 9] img_ret2[161,199]: [111 111 207] img_ret3[161,199]: [157 105 201]

然而,当图像包含4通道时,由于处理标量数据时不会自动将第4通道填充为0,直接操作会报错。因此,处理4通道图像时必须使用四元组。良好的编程习惯是无论图像通道数多少,均使用四元组表示标量。若不想对某些通道进行位运算,可用相应的全0或全f值代替。例如,对一个3通道uint8类型图像,仅需对第2通道与0x33进行与运算,构造的四元组应为(0xff,0x33,0xff,0xff)。

3、浮点类型图像的位运算

前述示例均以uint8类型数据为例,16位或32位整型数据的处理方法类似。但如果图像数据类型为浮点型,位运算后的结果会如何表现?


import numpy as np
import cv2

print('cv2.__version__:',cv2.__version__)
 
img1 = np.array([0.7,0.8,0.9,1.0,1.1,1.2,1.3],dtype=np.float32).reshape(1,7)
img_ret1 = cv2.bitwise_not(img1)
print('img1:',img1)
print('img_ret1:',img_ret1)

运行结果:

cv2.__version__: 4.5.2 img1: [[0.7 0.8 0.9 1. 1.1 1.2 1.3]] img_ret1: [[-6.3999996 -5.5999994 -4.7999997 -3.9999998 -3.7999997 -3.5999997 -3.3999999]]

从运行结果可见,浮点数按位取反后,原始数据与结果数据的符号发生明显改变,但数值之间已无明显对应关系。

这是由浮点数据在内存中的存储方式决定的。单精度32位浮点数由1个符号位S、8个指数位E和23个有效数值位M构成。以0.7为例,Python中float.hex(0.7)的计算结果为0x1.6666666666666p-1。提取23个有效数值位M=0110 0110 0110 0110 0110 011,指数E=127-1=0x7E=0b 0111 1110,符号位S=0。因此完整二进制值为0b 0 0111 1110 0110 0110 0110 0110 0110 011,即0x3f333333。取反后变为0xc0cccccc,再转换回浮点数值即为-6.3999996。同理可计算其他浮点数的二进制表示。

由于Python中没有直接打印浮点数二进制数值的方法,我们可以利用C语言中的指针类型强制转换来观察和转换浮点数的二进制值。以下示例定义了一个float型数组,并在循环中依次处理每个元素:先将数值强制转换为(int*)指针,对该int类型数据执行取反操作,最后将取反后的int类型数据强制转换为(float*)指针并转换回float型。


#include "stdio.h"
int main(void)
{
    float arr_f[7] = { 0.7,0.8,0.9,1.0,1.1,1.2,1.3 }; //定义浮点数数组
    int arr_i[7];               //存储浮点数转换后的int型
    int arr_i_not[7];           //存储int型的按位取反
    float arr_f_not[7];         //arr_i_not转换为浮点数
    for (int i = 0; i < 7; i++) {
        int *t_i = (int*)(arr_f + i); //指针类型转换为int型
        arr_i[i] = *t_i;
        arr_i_not[i] = ~arr_i[i];
        float *t_f = (float*)(arr_i_not + i);
        arr_f_not[i] = *t_f;
    }
 
    for (int i = 0; i < 7;i++) {
        printf("%0.7f  ", arr_f[i]); 
        printf("%x  ", arr_i[i]);
        printf("%x  ", arr_i_not[i]);
        printf("%0.7f  \n", arr_f_not[i]);
    }
    return 0;
}

运行结果如下,第1列为原始数据,最后一列为按位取反后的数值,与OpenCV的bitwise_not()计算结果一致:

0.7000000 3f333333 c0cccccc -6.3999996 0.8000000 3f4ccccd c0b33332 -5.5999994 0.9000000 3f666666 c0999999 -4.7999997 1.0000000 3f800000 c07fffff -3.9999998 1.1000000 3f8ccccd c0733332 -3.7999997 1.2000000 3f99999a c0666665 -3.5999997 1.3000000 3fa66666 c0599999 -3.3999999

OpenCV内部对浮点类型图像的位运算实际上也是按照二进制数值进行转换。然而,这种转换方法缺乏明确的图像学含义,因此浮点类型的位运算在实际应用中极少使用。

到此这篇关于OpenCV-Python教程之图像的位运算详解的文章就介绍到这了,更多相关OpenCV-Python图像的位运算内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表