部分III:核心操作
9. 图片
学习目标
- 获取像素值并修改
- 获取图像的属性
- 获取图像的 ROI
- 图像通道的拆分及合并
- 图像的填充
函数
- 学习使用 Numpy 的函数库处理图像
几乎所有这些操作与 Numpy 的关系都比与 OpenCV 的关系更加紧密,因此熟练 Numpy 可以帮助我们写出性能更好的代码。
9.1 获取并修改像素值
我们注意到,一张打开的图片就是 np.ndarray 对象,可以使用 Numpy 对图像进行任意的操作
img = cv2.imread('messi5.jpg')
print(img[10, 10])
img[100, 100] = 255, 255, 255【警告】 Numpy 是经过优化了的进行快速矩阵运算的软件包。所以我们不推荐 逐个获取像素值并修改,这样会很慢,能有矩阵运算就不要用循环。
更优雅的方法
img = cv2.imread('messi5.jpg')
assert isinstance(img, np.ndarray)
img.item(10, 10, 2)
img.itemset((10, 10, 2), 100)9.2 获取图像属性
可以参考 np.ndarray 属性
img.shape形状,即一个包含行、列和通道的元组img.size元素的数目,即像素乘通道的总数img.dtype元素类型
9.3 图像 ROI
ROI(Region Of Interest),即感兴趣区域,指的是对图像的特定区域进行操作。
ball = img[280:340, 330:390]
img[273:333, 100:160] = ball9.4 拆分及合并图像通道
b, g, r = cv2.split(img)
img = cv2.merge(b, g, r)使用索引更加快速、简单
b = img[:, :, 0]
img[:, :, 2] = 09.5 为图像扩边(填充)
如果你想在图像周围创建一个边,就像相框一样,你可以使用 cv2.copyMakeBorder() 函数。这经常在卷积运算或 0 填充时被用到。
语法: cv2.copyMakeBorder(src, top, bottom, left, right, borderType, dst=..., value=...)
这个函数包括如下参数:
src输入图像top, bottom, left, right对应边界的像素数目borderType要添加那种类型的边界,类型如下cv2.BORDER_CONSTANT添加有颜色的常数值边界,还需要下一个参数valuecv2.BORDER_REFLECT边界元素的镜像。比如fedcba|abcdefgh|hgfedcbcv2.BORDER_REFLECT_101或cv2.BORDER_DEFAULT跟上面一样,但稍作改动。例如gfedcb|abcdefgh|gfedcbacv2.BORDER_REPLICATE重复最后一个元素。例如aaaaaa|abcdefgh|hhhhhhhcv2.BORDER_WRAP就像这样cdefgh|abcdefgh|abcdefg
value边界颜色,如果边界的类型是cv2.BORDER_CONSTANT
为了更好的理解这几种类型请看下面的演示程序
import cv2
import numpy as np
import matplotlib.pyplot as plt
BLUE = 255, 0, 0
img = cv2.imread('messi5.jpg')
assert isinstance(img, np.ndarray)
replicate = cv2.copyMakeBorder(
img, 10, 10, 10, 10, cv2.BORDER_REPLICATE)
reflect = cv2.copyMakeBorder(
img, 10, 10, 10, 10, cv2.BORDER_REFLECT)
reflect_101 = cv2.copyMakeBorder(
img, 10, 10, 10, 10, cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(
img, 10, 10, 10, 10, cv2.BORDER_WRAP)
constant = cv2.copyMakeBorder(
img, 10, 10, 10, 10, cv2.BORDER_CONSTANT, value=BLUE)
plt.subplot(231)
plt.imshow(img[:, :, ::-1], 'gray')
plt.title('original')
plt.subplot(232)
plt.imshow(replicate[:, :, ::-1], 'gray')
plt.title('replicate')
plt.subplot(233)
plt.imshow(reflect[:, :, ::-1], 'gray')
plt.title('reflect')
plt.subplot(234)
plt.imshow(reflect_101[:, :, ::-1], 'gray')
plt.title('reflect_101')
plt.subplot(235)
plt.imshow(wrap[:, :, ::-1], 'gray')
plt.title('wrap')
plt.subplot(236)
plt.imshow(constant[:, :, ::-1], 'gray')
plt.title('constant')
plt.show()10 图像上的算术运算
学习目标
- 图像上的加法、减法、位运算等
- 图像混合
函数
cv2.add()加法cv2.addWeighted()带权加法,即图像混合cv2.bitwise_not()按位翻转cv2.bitwise_and()按位与
10.1 图像加法
【警告】 OpenCV 中的加法与 Numpy 的加法是有所不同的。OpenCV 的加法 是一种饱和操作,而 Numpy 的加法是一种模操作。
例如,在 dtype=np.uint8 的情况下
import cv2
import numpy as np
UINT8 = np.uint8
x = UINT8([250])
y = UINT8([10])
print(cv2.add(x, y)) # [[255]]
print(x + y) # [4]10.2 图像混合
图像混合,其实也是加法,但是不同的是两幅图像的权重不同,这就会给人一种混合或者透明的感觉。图像混合的计算公式如下
cv2.addWeighted() 的混合公式
import cv2
img1 = cv2.imread('t1.jpg')
img2 = cv2.imread('t2.jpg')
dst = cv2.addWeighted(img1, 0.7, img2, 0.3, 0)
cv2.imshow('dst', dst)
cv2.waitKey(0)
cv2.destroyAllWindows()10.3 按位运算
这里包括的按位操作有:AND,OR,NOT,XOR 等。当我们提取图像的一部分,选择非矩形 ROI 时这些操作会很有用。下面的例子就是教给我们如何改变一幅图的特定区域。
我想把 OpenCV 的标志放到另一幅图像上。如果我使用加法,颜色会改变,如果使用混合,会得到透明效果,但是我不想要透明。如果他是矩形我可以象上一章那样使用 ROI。但是他不是矩形。但是我们可以通过下面的按位运算实现:
import cv2
import numpy as np
# 加载图像
img1 = cv2.imread('roi.jpg')
img2 = cv2.imread('opencv_logo.png')
assert isinstance(img1, np.ndarray)
assert isinstance(img2, np.ndarray)
# 选择 logo 的 ROI
rows, cols, channels = img2.shape
roi = img1[0:rows, 0:cols]
# 创建一个遮罩 mask,并创建一个翻转的遮罩
img2gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 175, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
# 取 ROI 中与 mask 中不为零的值对应的像素的值,其他值为 0
# 其中的 mask= 不能忽略
img1_bg = cv2.bitwise_and(roi, roi, mask=mask)
# 取 ROI 中与 mask_inv 中不为零的值对应的像素的值,其他值为 0。
img2_fg = cv2.bitwise_and(img2, img2, mask=mask_inv)
dst = cv2.add(img1_bg, img2_fg)
img1[0:rows, 0:cols] = dst
cv2.imshow('res', img1)
cv2.waitKey(0)
cv2.destroyAllWindows()11 程序性能检测及优化
学习目标
- 检测程序的效率
- 一些能够提高程序效率的技巧
函数
cv2.getTickCountcv2.getTickFrequency
扩展
- IPython
- 模块
time和timeit
11.1 使用 OpenCV 检测程序效率
可用函数
cv2.getTickCount()函数返回从参考点到这个函数被执行的时钟数。所以当你在一个函数执行前后都调用它的话,你就会得到这个函数的执行时间cv2.getTickFrequency()返回时钟频率,或者说每秒钟的时钟数。所以你可以按照下面的方式得到一个函数运行了多少秒
import cv2
import numpy as np
e1 = cv2.getTickCount()
# 此处是代码
e2 = cv2.getTickCount()
time = (e2 - e1) / cv2.getTickFrequency()例如,测试代码
import cv2
import numpy as np
img1 = cv2.imread('t1.jpg')
e1 = cv2.getTickCount()
for i in range(5, 49, 2):
img1 = cv2.medianBlur(img1, i)
e2 = cv2.getTickCount()
t = (e2 - e1) / cv2.getTickFrequency()
print(t)11.2 OpenCV 中的默认优化
OpenCV 中的很多函数都被优化过(使用 SSE2,AVX 等)。你可以使用函数 cv2.useOptimized() 来查看优化是否被开启了,使用函数 cv2.setUseOptimized() 来开启优化。
使用 IPython 的魔术命令 %timeit 测试代码的
%timeit res = cv2.medianBlur(img, 49)中值滤波是被 SIMD 优化的,如果关闭优化进行测试,发现结果不同。
11.3 在 IPython 中检测程序效率
当你想知道哪些程序执行得更快,例如
x = 5; y = x ∗ ∗2
x = 5; y = x ∗ x
x = np.uint([5]); y = x ∗ x
y = np.squre(x)你可以使用上面的方法,%timeit 。
例如,可以比较 cv2.countNonZero() 和 np.count_nonzero() 的效率差别
%timeit z = cv2.countNonZero(img)
%timeit z = np.count_nonzero(img)一般情况下 OpenCV 的函数要比 Numpy 函数快。所以对于相同的操作最好使用 OpenCV 的函数。当然也有例外,尤其是当使用 Numpy 对视图(而非复制)进行操作时。
11.4 更多 IPython 的魔法命令
还有几个魔法命令可以用来检测程序的效率、内存使用等。
使用 %magic 可以直接调出有关于魔法命令的详细说明文档,这就等同于 vim 编辑器中的 :help 一样。
你只是想列出哪些魔法命令我们可以使用,那么可以直接调用 %lsmagic 进行输出。
如果只是查看具体某个魔法命令的用法,那么可以直接在魔法命令之后接上一个 ? 问号,类似于这样 %run? 。
11.5 效率优化技术
- 算法中尽量使用向量操作,因为 Numpy 和 OpenCV 都对向量操作进行了优化
- 利用高速缓存一致性
Python 速度优化技巧 官方文档
NumPy 优化技巧 scipy-lectures.org:NumPy 进阶
12 OpenCV 中的数学工具
参考 知乎:数字图像处理 中的内容。
其余的内容将整理到其他地方。