核心操作
图像基本操作
前言
要使用OpenCV编写更好的优化代码,需要Numpy的丰富知识。
目标
学会:
- 访问像素值并修改它们
- 访问图像属性
- 设置感兴趣区域(ROI)
- 分割和合并图像
访问和修改像素值
import numpy as np
import cv2 as cv
img = cv.imread('myPicture.jpg') # 这里指定一个图片地址就可以了
# 返回坐标(128,64)的像素值
print(img[128,64])# 对于 BGR 图像,它返回一个由蓝色、绿色和红色值组成的数组。对于灰度图像,只返回相应的灰度。
# 仅访问蓝色像素
print(img[100,100,0]);
img[100,100] = [255,255,255] # 修改指定像素点像素值
# 更好的像素访问和编辑方法:
img.item(10,10,2) # 访问 RED 值
img.itemset((10,10,2),100) # 修改 RED 值
img.item(10,10,2) # 再次访问
访问图像的属性
图像属性包括行数,列数和通道数,图像数据类型,像素数等。
import numpy as np
import cv2 as cv
img = cv.imread('myPicture.jpg') # 这里指定一个图片地址就可以了
# 访问图像的形状
print(img.shape) # 返回行,列和通道数的元组(如果图像是彩色的)
# 访问图像数据类型
print( img.dtype) # 大部分情况是返回 uint8
# 访问图像的总像素数
print(img.size)
注意 img.dtype在调试时非常重要,因为OpenCV-Python代码中的大量错误是由无效的数据类型引起的。
图像感兴趣区域 ROI
有时候不得不处理一些特定区域的图像。对于图像中的眼睛检测,首先对整个图像进行人脸检测。在获取人脸图像时,我们只选择人脸区域,搜索其中的眼睛,而不是搜索整个图像。它提高了准确性(因为眼睛总是在面部上:D )和性能(因为我们搜索的区域很小)。
# 使用numpy索引获得ROI
ball = img[280:340, 330:390] # 获取280<=x<=340, 330<=y<=390 区域间的图像,赋值给ball
img[273:333, 100:160] = ball # 把ball图像粘贴到新的区域
拆分和合并图像通道
# 分别取出B,G,R的图像
b = img [:, :, 0]
g = img [:, :, 1]
r = img [:, :, 2]
# 可以对单个图像进行整体赋值
b = 0 # 把蓝色通道置零
# 也可以不拆分通道直接对原图处理
img[:, :, 1] = 0 # 把原图的绿色通道置0
设置图像边框
像是加个相框?常应用在卷积运算,零填充等方面
方法:
- cv2.copyMakeBorder()
参数:
-
src - 输入图像
-
top,bottom,left,right 边界宽度(以相应方向上的像素数为单位)
-
borderType - 定义要添加哪种边框的标志。它可以是以下类型:
-
cv.BORDER_CONSTANT - 添加恒定的彩色边框。该值应作为下一个参数给出。
-
cv.BORDER_REFLECT - 边框将是边框元素的镜像,如下所示: fedcba | abcdefgh | hgfedcb
-
cv.BORDER_REFLECT_101或 cv.BORDER_DEFAULT与上述相同,但略有变化,例如: gfedcb | abcdefgh | gfedcba
-
cv.BORDER_REPLICATE最后一个元素被复制,像这样: aaaaaa | abcdefgh | hhhhhhh
-
cv.BORDER_WRAP难以解释,它看起来像这样: cdefgh | abcdefgh | abcdefg
-
-
value 边框的颜色,如果边框类型为cv.BORDER_CONSTANT才需要传入此参数
# 这是示例代码,可以更好地理解上面的参数
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
BLUE = [255,0,0]
img1 = cv.imread('mypicture.jpg') # 图片路径需正确
replicate = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REPLICATE)
reflect = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT)
reflect101 = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT_101)
wrap = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_WRAP)
constant= cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_CONSTANT,value=BLUE)
plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL')
plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')
plt.show()
(图像与matplotlib一起显示。因此红色和蓝色通道将互换)
图像算数运算
加法
可以通过OpenCV的 cv.add() 函数或者 numpy 操作 res = img1 + img2 添加两个图像。两个图像应具有相同的深度和类型,或者第二个图像可以只是一个标量值
需要注意的是:OpenCV加法是饱和运算,而Numpy加法是模运算。即前者有防溢出,上限255;后者会溢出,超出255会归零
# 示例参考
x = np.uint8([250])
y = np.uint8([10])
print( cv.add(x,y) ) # 250+10 = 260 => 255
print( x+y ) # 250+10 = 260 % 256 = 4
图像融合
这也是图像加法,但是对图像赋予不同的权重,以使其具有融合或透明的感觉。
根据以下等式添加图像:
G(x)=(1−α)f0(x)+αf1(x)
具体函数 : cv.addWeighted() 。 **注:**融合的图片
# 示例参考
img1 = cv.imread('l.jpg')
img2 = cv.imread('2.jpg')
dst = cv.addWeighted(img1,0.7,img2,0.3,0) # dst = img1*0.7+img2*0.3+0
cv.imshow('dst',dst) # 将结果与显示出
cv.waitKey(0)
cv.destroyAllWindows()
按位运算
包括了 and or not xor 操作。 它们在提取图像的任何部分、定义和处理非矩形 ROI 等方面非常有用。
下面举一个例子,如何改变一个图像的特定区域。
我想把 OpenCV 的LOGO放在一个图像上面。
如果我添加两个图像,它会改变颜色。
如果我混合它,我得到一个透明的效果。但我希望它是不透明的。
如果是一个矩形区域,我可以使用 ROI,就像我们在上一章中所做的那样。但是 OpenCV 的 logo 不是长方形的。
所以可以使用如下的按位操作来实现:
知识补充:
- 颜色空间:指图像中每个像素的颜色信息的表示方式。例如,RGB颜色空间;灰度颜色空间,可以表示灰度图像;BGR颜色空间。
- 掩码:掩码是一种用来控制图像处理的区域或过程的方法。它可以用一个小于等于源图像的单通道道矩阵来表示,其中非零的元素表示要保留或修改的像素值,零的元素表示要忽略或删除的像素值。就像是一张挖好形状的膜。掩码即膜上挖好的形状。
- cv2.cvtColor() :将图像从一种颜色空间转换为另一种颜色空间。
- cv2.threshold():将图像进行二值化处理(常被用于获取掩码)
- cv2.bitwise_not():对图像进行按位取反操作,将图像中的每个像素的二进制值进行取反操作,常用于图像增强、边缘检测等。
- cv2.bitwise_and(src1, src2, dst=None, mask=None):对图像1和2进行按位与操作,
# 加载两张图片
img1 = cv.imread('messi5.jpg')
img2 = cv.imread('opencv-logo-white.png')
# 我想把logo放在左上角,所以我创建了ROI
rows,cols,channels = img2.shape
roi = img1[0:rows, 0:cols ]
# 现在创建logo的掩码,并同时创建其相反掩码
img2gray = cv.cvtColor(img2,cv.COLOR_BGR2GRAY) # 获取logo在灰度空间上的图片
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY) # 将图像进行二值化处理,获取掩码(纯白)
mask_inv = cv.bitwise_not(mask) # 按位取反,获取反掩码(纯黑)
# 现在将ROI中logo的区域涂黑
img1_bg = cv.bitwise_and(roi,roi,mask = mask_inv) # 按位与,将ROI中logo的区域涂黑
# 仅从logo图像中提取logo区域
img2_fg = cv.bitwise_and(img2,img2,mask = mask) # 按位与,通过纯黑背景获取去背景的 logo
# 将logo放入ROI并修改主图像
dst = cv.add(img1_bg,img2_fg) # 将logo放入已经将LOGO区置零的ROI
img1[0:rows, 0:cols] = dst # 修改主图像
cv.imshow('res',img1)
cv.waitKey(0)
cv.destroyAllWindows()
性能衡量和提升
在图像处理中,由于每秒要处理大量操作,因此必须使代码不仅提供正确的解决方案,而且还必须以最快的方式提供。
使用OpenCV衡量性能
- cv.getTickCount 函数返回从参考事件(如打开机器的那一刻)到调用此函数那一刻之间的时钟周期数。因此,如果在函数执行之前和之后调用它,则会获得用于执行函数的时钟周期数。
- cv.getTickFrequency 函数返回时钟周期的频率或每秒的时钟周期数。
OpenCV中的默认优化
知识补充:
-
SSE2:SSE2是一种SIMD(单指令多数据流)的指令集,它可以让CPU同时执行多个数据操作,提高计算效率。它比之前的MMX指令集增加了浮点和整数运算的指令,以及整数和浮点数据之间的转换。
-
AVX:AVX是在SSE2的基础上发展出来的,它的主要特点是将寄存器的位宽从128位扩展到256位,从而可以同时处理更多的数据。AVX还增加了一些新的指令和功能,比如三操作数指令、数据收集指令、FMA指令等。
许多 OpenCV 函数都是使用 SSE2、 AVX 等进行优化的。 它还包含未优化的代码。因此,如果我们的系统支持这些特性,我们就应该利用它们(几乎所有现代的处理器都支持它们)。
可以使用 cvUseoptimized 检查是否启用 / 禁用和 cvSetuseoptimized 以启用 / 禁用它。