Python OpenCV学习总结Day 2 像素访问,通道分离,边界填充,算术操作

前言

按照OpenCV官方doc顺序来进行学习回忆和总结
本次的内容是opencv中的常用核心操作(core.hpp),包括

  • 像素访问
  • 图像属性访问
  • ROI提取
  • 通道操作
  • 图像边界填充
  • 图像的算术操作和位操作
  • 运行时间计算

Day2.OpenCV核心基础

像素访问与修改

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核心操作,包括像素读写、通道分离、填充边界、算术和位操作等,下次将进入图像处理章节~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值