- 前言
- Day2.OpenCV核心基础
- 像素访问与修改
- 图像属性访问
- ROI提取
- 通道操作
- 边界填充
- 图像的算术操作与位操作
- 图像加法
- 图像减法
- 图像乘法
- 图像除法
- 位操作
- 运行计时
- 结语
按照OpenCV官方doc顺序来进行学习回忆和总结 本次的内容是opencv中的常用核心操作(core.hpp),包括
- 像素访问
- 图像属性访问
- ROI提取
- 通道操作
- 图像边界填充
- 图像的算术操作和位操作
- 运行时间计算
opencv在C++读入的图片是用Mat存储,而在python里则以numpy.ndarray进行存储,读取图片坐标(x, y)处的像素信息可以通过访问numpy数组进行,
img = cv2.imread('starry_night.png')
cv2.imshow('img', img)
pixel = img[100, 100]
print('(100, 100)处的三通道像素值', pixel)
# (100, 100)处的三通道像素值 [131 54 22]
需要注意图像坐标与numpy数组位置之间的对应关系,例如图像img坐标(20,30)处的像素,用数组的说法则是第30行的第20列,因此在numpy数组中对应的位置是img[30, 20]
如果要访问某一特定通道的像素(例如Blue通道),
pixel = img[100, 100, 0]
print('(100, 100)处的单通道(蓝)像素值', pixel)
# (100, 100)处的单通道(蓝)像素值 131
如果要修改某一位置的像素值,
img[100, 100] = [22, 54, 131]
print('修改后的像素值', img[100, 100])
# 修改后的像素值 [ 22 54 131]
这里有一个提升像素访问速度的小技巧,当需要遍历图像某个区域的像素点时,使用以上方法for循环遍历修改的效率较低。而numpy内置的item()方法能够快速遍历,
pixel = img.item(100, 100, 0)
print('快速访问(100, 100, 0)处的像素值', pixel)
img.itemset((100, 100, 0), 55)
print('快速访问修改后的(100, 100, 0)处的像素值', img.item(100, 100, 0))
# 快速访问(100, 100, 0)处的像素值 22
# 快速访问修改后的(100, 100, 0)处的像素值 55
图像属性访问
图像的基本属性包括图像的形状(W, H, C),像素数量,图像的数据类型,可分别通过以下方法访问,
print('图片shape', img.shape)
print('像素数量', img.size)
print('图片数据类型', img.dtype)
# 图片shape (600, 752, 3)
# 像素数量 1353600
# 图片数据类型 uint8
ROI提取
图像处理时经常需要将感兴趣区域ROI(Region of Interests)提取出来,可以通过numpy数组切片进行,
# ROI提取
ROI = img[120:240, 120:240]
# 将ROI移动到img的其它位置
img[240:360, 240:360] = ROI
cv2.imshow('img2', img)
cv2.waitKey(0)
结果
有时需要通过图像的通道操作来筛选颜色,可以通过opencv内置的函数cv2.split(),cv2.merge()或者numpy数组操作进行,
b, g, r = cv2.split(img) # split函数可用于通道分离,分离顺序是BGR
B = img[:, :, 0] # 也可以通过numpy数组进行通道分离
G = img[:, :, 1] # numpy操作速度相比split速度更快
R = img[:, :, 2] # 分离结果相同
cv2.imshow('b', b)
cv2.imshow('B', B)
img = cv2.merge([b, g, r]) # merge函数可用于图像融合,融合顺序是BGR
cv2.imshow('img3', img)
img[:, :, 0] = B # 也可以通过numpy数组进行通道融合
img[:, :, 1] = G # 融合结果相同
img[:, :, 2] = R
cv2.imshow('img4', img)
cv2.waitKey(0)
B分量结果
需要注意的是,split和merge函数的运行效率要低于直接操作numpy数组进行通道分离和融合
边界填充有时需要对图像进行填充(印象里比较深的是YOLOv5的黑边填充),opencv内置了cv2.copyMakeBorder()来实现,
cv2.copyMakeBorder(img, top, bottom, left, right, borderType, value=None)
其中,第二三四五个参数分别表示上下左右填充的长度,borderType表示填充方式,常用的有
- cv2.BORDER_CONSTANT 最常用的纯色填充,此时value需要有对应的颜色值
- cv2.BORDER_REFLECT 对称填充
- cv2.BORDER_REPLICATE 复制填充
- cv2.BORDER_WRAP 扭曲填充
img_border = cv2.copyMakeBorder(img, 20, 3, 10, 10, cv2.BORDER_CONSTANT, value=(255, 255, 255))
cv2.imshow('border', img_border)
cv2.waitKey(0)
opencv内置了算术操作函数与位操作函数,有助于实现图像融合、掩膜计算等实现
图像加法有两种方式,通过opencv内置的cv2.add()或numpy数组加法,
img = cv2.add(img1, img2)
# or
img = img1 + img2
但是这两种图像加法得到的结果却是不同的,运行以下代码,
img_add = cv2.add(img, img)
cv2.imshow('cv_add', img_add)
img_add = img + img
cv2.imshow('np_add', img_add)
cv2.waitKey(0)
cv2.add结果: numpy加法结果:
造成以上结果的原因是,uint8数据在使用cv2.add()在像素值相加时,如果结果大于255,就会保持为255;而numpy数组相加时则会取模运算,如下所示
print(cv2.add(np.uint8([200]), np.uint8([100])))
print(np.uint8([200]) + np.uint8([100]))
# [[255]]
# [44]
如果两张图片需要根据权重叠加,则可以使用cv2.addWeighted()函数,
img = cv2.addWeighted(img1, alpha, img2, beta, gamma)
# img = alpha * img1 + beta * img2 + gamma
其中,alpha和beta是两张图片的权重,而gamma则可用于增加图片的亮度
img_add = cv2.addWeighted(img, 0.4, img, 0.1, 0)
cv2.imshow('addweight', img_add)
cv2.waitKey(0)
结果:
与加法类似,也可以通过opencv内置cv2.subtract()或Numpy数组来做图像减法,
img_sub = cv2.subtract(img_add, img)
cv2.imshow('sub', img_sub)
img_sub = img - img_add
cv2.imshow('sub2', img_sub)
cv2.waitKey(0)
cv结果: np结果:
减法还有一个特殊函数,cv2.absdiff(),用来计算img = |img1-img2|,
img = cv2.absdiff(img1, img2)
img_diff = cv2.absdiff(img_add, img)
cv2.imshow('absdiff', img_sub)
cv2.waitKey(0)
结果: absdiff()常用于背景减法、帧差法等算法中
以下结果可以更好表示cv2.add()、numpy减法与cv2.absdiff()之间的区别,
print(cv2.subtract(np.uint8([100]), np.uint8([120])))
print(np.uint8([100]) - np.uint8([120]))
print(cv2.absdiff(np.uint8([100]), np.uint8([120])))
# [[0]]
# [236]
# [[20]]
图像乘法
cv2.multiply()图像乘法,用的比较少,用于计算img = img1 * img2 * scale
img = cv2.multiply(img1, img2, dst, scale)
img_mul = cv2.multiply(img_add, img, scale=1)
cv2.imshow('mul', img_mul)
print(cv2.multiply(np.uint8([100]), np.uint8([120])))
print(np.uint8([100]) * np.uint8([120]))
cv2.waitKey(0)
# [[255]]
# [224]
结果:
cv2.divide()图像除法,但用的比较少,用于计算img = img1 * scale / img2或img = scale / img1
img = cv2.divide(img1, img2, dst, scale)
img = cv2.divide(scale, img1)
img_div = cv2.divide(img_add, img, scale=100)
cv2.imshow('img_div', img_div)
img_div = cv2.divide(1000, img_add)
cv2.imshow('div_scale', img_div)
print(cv2.divide(np.uint8([100]), np.uint8([120])))
print(np.uint8([100])/np.uint8([120]))
# [[1]]
# [0.83333333]
结果:
opencv中,为了方便掩膜操作,提供了几种像素位操作函数,
# 按位与
dst = cv2.bitwise_and(src1, src2, dst=None, mask=None)
# 按位或
dst = cv2.bitwise_or(src1, src2, dst=None, mask=None)
# 按位异或
dst = cv2.bitwise_xor(src1, src2, dst=None, mask=None)
# 按位取反
dst = cv2.bitwise_not(src, dst=None, mask=None)
首先要注意的是,按位与或非的计算方法,如下所示,根据二进制计算可知,首先将像素值转化为二进制数,然后进行位操作,再返回十进制结果
print(cv2.bitwise_and(np.uint8([100]), np.uint8([120])))
print(cv2.bitwise_not(np.uint8([100])))
# [[96]]
# [[155]]
因此,实际图像操作中,位操作通常是与纯白色或纯黑色的掩膜联合使用,完成抠图等,具体做法就是将需要抠图的地方利用图像处理置为255,将其它地方置0,然后位与
以下给出了一个抠图融合例程,
import cv2
img1 = cv2.imread('./data/messi5.jpg')
img2 = cv2.imread('./data/opencv-logo-white.png')
cv2.imshow('img2', img2)
rows, cols, channels = img2.shape
roi = img1[0:rows, 0:cols]
img2gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
img1_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)
img2_fg = cv2.bitwise_and(img2, img2, mask=mask)
dst = cv2.add(img1_bg, img2_fg)
img1[0:rows, 0:cols] = dst
cv2.imshow('res', img1)
cv2.waitKey(0)
python中内置的time包常用于程序运行计时,
import time
start = time.time()
# function
end = time.time()
print(end - time)
而opencv其实也内置了计时函数组合cv2.getgetTickCount()与cv2.getTickFrequency()
e1 = cv2.getTickCount()
# function
e2 = cv2.getTickCount()
time = (e2 - e1)/ cv2.getTickFrequency()
此外,opencv还提供了一些用于运行优化、设置多线程等功能的函数,
cv2.getNumThreads() # 获取opencv并行使用的线程数
cv2.setNumThreads() # 设置opencv并行线程数
cv2.useOptimized() # 查看opencv是否打开优化
cv2.setUseOptimized() # opencv打开优化
结语
首先给出本文的总代码,
import cv2
import numpy as np
'''
像素访问和更改
'''
img = cv2.imread('starry_night.png')
cv2.imshow('img', img)
pixel = img[100, 100]
print('(100, 100)处的三通道像素值', pixel)
pixel = img[100, 100, 0]
print('(100, 100)处的单通道(蓝)像素值', pixel)
img[100, 100] = [22, 54, 131]
print('修改后的像素值', img[100, 100])
pixel = img.item(100, 100, 0)
print('快速访问(100, 100, 0)处的像素值', pixel)
img.itemset((100, 100, 0), 55)
print('快速访问修改后的(100, 100, 0)处的像素值', img.item(100, 100, 0))
'''
图像属性访问
'''
print('图片shape', img.shape)
print('像素数量', img.size)
print('图片数据类型', img.dtype)
'''
图像ROI(感兴趣区域)提取
'''
ROI = img[120:240, 120:240]
img[240:360, 240:360] = ROI
cv2.imshow('img2', img)
cv2.waitKey(0)
'''
通道分离与合并
'''
b, g, r = cv2.split(img) # split函数可用于通道分离,分离顺序是BGR
B = img[:, :, 0] # 也可以通过numpy数组进行通道分离
G = img[:, :, 1] # numpy操作速度相比split速度更快
R = img[:, :, 2] # 分离结果相同
cv2.imshow('b', b)
cv2.imshow('B', B)
img = cv2.merge([b, g, r]) # merge函数可用于图像融合,融合顺序是BGR
cv2.imshow('img3', img)
img[:, :, 0] = B # 也可以通过numpy数组进行通道融合
img[:, :, 1] = G # 融合结果相同
img[:, :, 2] = R
cv2.imshow('img4', img)
cv2.waitKey(0)
'''
填充图像边界
'''
img_border = cv2.copyMakeBorder(img, 20, 3, 10, 10, cv2.BORDER_CONSTANT, value=(255, 255, 255))
cv2.imshow('border', img_border)
cv2.waitKey(0)
'''
图像加法
'''
img_add = cv2.add(img, img)
cv2.imshow('cv_add', img_add)
img_add = img + img
cv2.imshow('np_add', img_add)
cv2.waitKey(0)
print(cv2.add(np.uint8([200]), np.uint8([100])))
print(np.uint8([200]) + np.uint8([100]))
img_add = cv2.addWeighted(img, 0.4, img, 0.1, 0)
cv2.imshow('addweight', img_add)
cv2.waitKey(0)
'''
图像减法
'''
img_sub = cv2.subtract(img_add, np.ones_like(img_add) * 100)
cv2.imshow('cv_sub', img_sub)
img_sub = img_add - np.ones_like(img_add) * 100
cv2.imshow('np_sub', img_sub)
img_diff = cv2.absdiff(img_add, np.ones_like(img_add) * 100)
cv2.imshow('absdiff', img_diff)
print(cv2.subtract(np.uint8([100]), np.uint8([120])))
print(np.uint8([100]) - np.uint8([120]))
print(cv2.absdiff(np.uint8([100]), np.uint8([120])))
cv2.waitKey(0)
'''
图像乘法
'''
img_mul = cv2.multiply(img_add, img, scale=1)
cv2.imshow('mul', img_mul)
print(cv2.multiply(np.uint8([100]), np.uint8([120])))
print(np.uint8([100]) * np.uint8([120]))
cv2.waitKey(0)
'''
图像除法
'''
img_div = cv2.divide(img_add, img, scale=100)
cv2.imshow('img_div', img_div)
img_div = cv2.divide(1000, img_add)
cv2.imshow('div_scale', img_div)
print(cv2.divide(np.uint8([100]), np.uint8([120])))
print(np.uint8([100])/np.uint8([120]))
cv2.waitKey(0)
'''
图像位操作
'''
print(cv2.bitwise_and(np.uint8([100]), np.uint8([120])))
print(cv2.bitwise_not(np.uint8([100])))
本次总结回顾了基本的OpenCV核心操作,包括像素读写、通道分离、填充边界、算术和位操作等,下次将进入图像处理章节~