文章目录
- OpenCV功能概要
- 图像的算数运算
- 性能测试和优化
- 改变颜色空间
- 对象追踪
OpenCV功能概要
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习库,提供了丰富的图像处理和计算机视觉算法。它支持多种编程语言,包括Python、C++和Java。以下是OpenCV的主要功能概要:
-
图像的算数运算:
OpenCV提供了多种算数运算函数,包括加法、减法、位运算等,可以用来处理图像。这些函数能够执行图像间的像素级别操作,例如图像加法和减法,以及与常数的算数运算。 -
性能测试和优化:
OpenCV提供了丰富的性能测试工具和优化技术,可以帮助开发者评估和提高图像处理算法的性能。使用OpenCV的性能测试功能,可以度量不同操作的执行时间,从而找出性能瓶颈。优化技术包括使用OpenCV的内置函数,以及利用硬件加速(如CUDA和OpenCL)来加速图像处理任务。 -
改变颜色空间:
OpenCV支持多种颜色空间的转换,例如RGB到灰度、RGB到HSV等。这些转换可以帮助开发者在不同颜色空间中进行图像处理,从而更好地理解和操作图像的颜色信息。
图像的算数运算
一些图像上的运算操作,就像加法,减法,位操作等等
函数:cv.add()、cv.addWeighted()等等
1.图像添加
图像的添加是图像处理中常见的操作之一,用于将两幅图像叠加在一起。在OpenCV中,你可以使用cv2.add()函数来实现图像的添加。另外,你也可以使用NumPy进行相似的操作,但需要注意OpenCV和NumPy在处理饱和运算方面的差异。
使用OpenCV的cv2.add()函数时,它会执行饱和运算(saturate operation),即当像素值超过255时,会被截断到255,不会溢出。这是因为图像的像素值通常是8位无符号整数(0到255之间的值),超出这个范围的值会被截断。
import cv2
import numpy as np
# 读取两幅图像
img1 = cv2.imread('img.png')
img2 = cv2.imread('img_1.png')
# 调整第二幅图像的尺寸与第一幅图像相同
img2_resized = cv2.resize(img2, (img1.shape[1], img1.shape[0]))
# 使用NumPy进行图像相加(饱和运算)
added_image_numpy = np.clip(img1.astype(int) + img2_resized.astype(int), 0, 255).astype(np.uint8)
# 输出结果
print("NumPy添加结果(饱和运算):", added_image_numpy)
cv2.imshow('Blended Image', added_image_numpy)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.add()函数执行了饱和运算,确保了结果图像的像素值不会超出255。而使用NumPy进行图像相加时,需要使用np.clip()函数来进行饱和运算,确保结果在合理范围内。
2.图片混合
这种图像叠加操作是通过赋予不同权重给两幅图像,从而实现混合或透明效果的一种方法。具体地,通过以下的线性组合公式:
g(x)=(1−α)f0(x)+αf1(x)g(x)=(1−α)f0(x)+αf1(x)
其中,f0(x)f0(x)和f1(x)f1(x)分别代表两幅输入图像的像素值,αα表示权重,可以调整从0到1,实现不同程度的混合。在OpenCV中,这种混合效果可以通过cv2.addWeighted()函数实现,其公式如下:
dst=α⋅img1+β⋅img2+γdst=α⋅img1+β⋅img2+γ
在这个公式中,img1和img2分别是两幅输入图像,αα和ββ是相应图像的权重,而γγ通常被设置为0。
以下是一个使用OpenCV进行两幅图像混合的例子:
import cv2
# 读取两幅图像
img1 = cv2.imread('img.png')
img2 = cv2.imread('img_1.png')
# 调整第二幅图像的尺寸与第一幅图像相同
img2_resized = cv2.resize(img2, (img1.shape[1], img1.shape[0]))
# 设置权重
alpha = 0.7
beta = 0.3
# 图像混合
blended_img = cv2.addWeighted(img1, alpha, img2_resized, beta, 0)
# 显示混合结果
cv2.imshow('Blended Image', blended_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
3.位操作
我想把OpenCV的图标放到一个图片上面。如果我直接相加两个图像,图标的颜色就会被改变。如果是选择混合他们,那么透明度就会受影响。但我希望它是不透明的。如果这是一个规则的区域,我就可以用上一个章节使用的ROI来操作。但是OpenCV图标并不是一个规则的形状。所以你可以通过下面的例子进行位运算。
使用位运算来处理两幅图像,将OpenCV的图标放置在另一幅图像的特定区域上,并确保它是不透明的。以下是代码的详细解释:
- 加载图片:
img1 = cv.imread('messi5.jpg')
img2 = cv.imread('opencv-logo-white.png')
2.创建ROI区域:
我们希望将img2(OpenCV的图标)放在img1的左上角。因此,我们创建一个ROI区域,其大小与img2相同。
rows, cols, channels = img2.shape
roi = img1[0:rows, 0:cols]
3.创建徽标蒙版和反向蒙版:
我们将img2转换为灰度图像,然后使用阈值将其二值化(变为黑白图像)。接着,我们通过cv.bitwise_not()函数创建反向蒙版。
img2gray = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
mask_inv = cv.bitwise_not(mask)
4.背景提取和前景提取:
使用cv.bitwise_and()函数,我们将ROI区域中的徽标区域涂黑,得到背景提取的图像(img1_bg)。然后,我们从徽标图像中提取徽标区域,得到前景提取的图像(img2_fg)。
img1_bg = cv.bitwise_and(roi, roi, mask=mask_inv)
img2_fg = cv.bitwise_and(img2, img2, mask=mask)
5.合并前景和背景:
将前景和背景图像相加,得到最终的结果图像。
dst = cv.add(img1_bg, img2_fg)
img1[0:rows, 0:cols] = dst
6.显示结果:
最终,通过cv.imshow()函数显示结果图像,你也可以在代码中插入其他步骤来查看中间结果。
cv.imshow('res', img1)
cv.waitKey(0)
cv.destroyAllWindows()
这个例子中使用位运算,将OpenCV的图标合并到了另一幅图像的特定区域上,同时确保了不透明度。
性能测试和优化
函数:cv.getTickCount,cv.getTickFrequency 等等
在OpenCV中,你可以使用cv.getTickCount()函数来获取一个时间戳,该时间戳表示自系统启动以来的时钟周期数。你还可以使用cv.getTickFrequency()函数来获取时钟周期的频率,即每秒的时钟周期数。通过这两个函数,你可以计算函数的运行时间。
具体步骤如下:
使用cv.getTickCount()获取函数开始时的时间戳。
e1 = cv.getTickCount()
在你的代码执行完毕后,再次调用cv.getTickCount()获取函数结束时的时间戳。
# 你的代码
e2 = cv.getTickCount()
计算函数的运行时间(以秒为单位)。
time = (e2 - e1) / cv.getTickFrequency()
这样,time变量将包含函数的运行时间(以秒为单位)。
OpenCV中默认的优化方法
OpenCV中的许多函数都利用了现代处理器的特殊指令集(如SSE2、AVX等)进行优化,以提高性能。OpenCV同时也包含了未经过优化的代码。因此,如果系统支持这些特殊指令集(几乎所有现代处理器都支持),应该充分利用它们。
在编译时,OpenCV默认会启用这些优化,因此在运行时,OpenCV会尽量使用经过优化的代码。只有在不支持这些指令集的情况下,OpenCV才会使用未优化的代码。可以使用cv.useOptimized()函数来检查优化是否启用,以及使用cv.setUseOptimized()函数来手动启用或禁用优化。
# 检查是否启用优化
print(cv.useOptimized()) # 输出: True
# 使用优化进行中值滤波,循环10次,计算平均每次循环的时间
%timeit res = cv.medianBlur(img, 49)
# 禁用优化
cv.setUseOptimized(False)
# 检查是否禁用了优化
print(cv.useOptimized()) # 输出: False
# 禁用优化后进行中值滤波,循环10次,计算平均每次循环的时间
%timeit res = cv.medianBlur(img, 49)
在IPython中的测量方法
在IPython中,你可以使用神奇的timeit命令来完成这个任务。timeit通过多次运行代码获取准确的结果,同样也适用于单行代码。
比如,你想知道以下几种操作哪个更快:x = 5; y = x**2,x = 5; y = x * x,x = np.uint8([5]); y = x * x,或者是 y = np.square(x)。我们可以在IPython中找到答案。
x = 5
%timeit y = x**2
# 输出结果:10000000 loops, best of 3: 73 ns per loop
%timeit y = x * x
# 输出结果:10000000 loops, best of 3: 58.3 ns per loop
z = np.uint8([5])
%timeit y = z * z
# 输出结果:1000000 loops, best of 3: 1.25 us per loop
%timeit y = np.square(z)
# 输出结果:1000000 loops, best of 3: 1.16 us per loop
你会发现,x = 5; y = x * x 的速度要比Numpy的快20倍。如果考虑到创建一个数组,这个差距可能会更大,这真的很酷(实际上,Numpy就是用来解决这类问题的)。
需要注意的是,Python的标量运算远比Numpy的快。所以,如果你只是对一两个元素进行操作,可能没有必要使用Numpy。Numpy的真正优势体现在大量数据的矩阵运算上。
接下来,我们尝试比较cv.countNonZero()和np.count_nonzero()函数在操作同一张图片时的性能表现。
%timeit z = cv.countNonZero(img)
# 输出结果:100000 loops, best of 3: 15.8 us per loop
%timeit z = np.count_nonzero(img)
# 输出结果:1000 loops, best of 3: 370 us per loop
可以看到,OpenCV的函数比Numpy的快了大约25倍。
需要注意的是,通常情况下,OpenCV的函数比Numpy的函数更快。因此,在进行相同操作时,OpenCV的性能会更好。不过,也有一些例外情况,具体取决于操作的性质,特别是当Numpy使用视图而不是副本时。
改变颜色空间
图像从一个颜色空间转换到另外一个,就像 BGR ↔ Gray, BGR ↔ HSV ,等等。
创建一个应用程序来提取视频中的色彩对象。
函数:cv.cvtColor(),cv.inRange()等等。
注意:Note 对于 HSV,色调范围为 [0,179],饱和度范围为 [0,255],值范围为 [0,255]。不同的软件使用不同的尺度。因此,如果您将 OpenCV 值与它们进行比较,则需要对这些范围进行归一化。
改变颜色空间
在OpenCV中,有超过150种颜色空间转换方法,但我们通常关注其中两种最常用的:BGR到灰度(BGR ↔ Gray)和BGR到HSV(BGR ↔ HSV)的转换。
在颜色转换中,我们使用cv.cvtColor(input_img, flag)函数,其中flag参数用于确定转换的类型。
对于BGR到灰度的转换,我们将cv.COLOR_BGR2GRAY传递给flag参数。类似地,对于BGR到HSV的转换,我们将cv.COLOR_BGR2HSV传递给flag参数。可以在Python中运行以下代码:
import cv2 as cv
flags = [i for i in dir(cv) if i.startswith('COLOR_')]
print(flags)
需要注意的是,在HSV颜色空间中,色调范围是0,1790,179,饱和度范围是0,2550,255,值范围是0,2550,255。不同的软件使用不同的尺度。因此,如果你想将OpenCV中的值与其他软件进行比较,你需要对这些范围进行归一化处理。
对象追踪
现在,我们已经学会如何将一幅BGR图像转换成HSV颜色空间。接下来,我们可以利用这个知识来提取特定颜色的对象。在这个例子中,我们将尝试提取蓝色对象的图像。
以下是具体的步骤:
获取每一帧图像: 从视频中捕获每一帧图像。
颜色空间转换: 将BGR图像转换为HSV颜色空间,这样更容易提取特定颜色的对象。
颜色阈值处理: 在HSV颜色空间中,指定蓝色的阈值范围(lower_blue和upper_blue)。这样,我们可以定位在该范围内的蓝色像素。
创建掩码: 使用cv.inRange()函数创建一个掩码,该掩码仅包含在指定蓝色范围内的像素。
提取蓝色对象: 将掩码应用到原始图像上,使用cv.bitwise_and()函数,这样就可以提取出蓝色对象的图像。
显示图像: 分别显示原始图像、掩码和提取出的蓝色对象的图像。
等待用户操作: 等待用户按下键盘上的ESC键(ASCII码为27)来退出循环。
释放资源: 当用户按下ESC键后,释放所有窗口资源。
import cv2 as cv
import numpy as np
cap = cv.VideoCapture(0)
while True:
# 获取每一帧
_, frame = cap.read()
# 从BGR转换到HSV
hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)
# 在HSV中定位蓝色范围
lower_blue = np.array([110, 50, 50])
upper_blue = np.array([130, 255, 255])
# 设置HSV图像仅获得蓝色图像
mask = cv.inRange(hsv, lower_blue, upper_blue)
# 按位与掩码和原始图像
res = cv.bitwise_and(frame, frame, mask=mask)
# 显示图像
cv.imshow('Original', frame)
cv.imshow('Mask', mask)
cv.imshow('Blue Object', res)
# 等待用户按下ESC键退出
k = cv.waitKey(5) & 0xFF
if k == 27:
break
# 释放资源
cap.release()
cv.destroyAllWindows()
Note 图像中有些噪声。我们将在后面演示如何去除它。这个是一个简单的项目追踪方法。一旦你学会了轮廓的函数,你就可以做一系列事情,就像是找到一个物体的质心并用它去跟踪物体,或者是只需要在相机前移动你的手就可以画出图表,或者其他有趣的事情。
要找到需要跟踪的颜色的HSV值,可以使用OpenCV中的cv.cvtColor()函数。不需要传递整个图像,只需传递所需的BGR值即可。以下是一个示例,假设想找到绿色的HSV值:
import numpy as np
import cv2 as cv
# 定义一个绿色的BGR值
green = np.uint8([[[0, 255, 0]]])
# 将BGR转换为HSV
hsv_green = cv.cvtColor(green, cv.COLOR_BGR2HSV)
print(hsv_green)
运行这段代码,会得到绿色的HSV值:[[[60 255 255]]]。现在,可以将这个HSV值作为跟踪绿色的下限(Lower Bound):[H-10, 100, 100],和上限(Upper Bound):[H+10, 255, 255]。在这个例子中,绿色的H值为60,所以下限为50,上限为70。这个范围可以根据实际情况微调。
另外,除了使用代码计算HSV值外,也可以使用图像编辑工具(如GIMP)或在线颜色转换器来找到这些值。但是请记住,确保在使用这些工具时调整HSV范围,因为不同的工具可能使用不同的HSV标准。