前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >OpenCV 教程 02: OpenCV 的核心操作

OpenCV 教程 02: OpenCV 的核心操作

作者头像
somenzz
发布2022-10-25 20:52:54
6200
发布2022-10-25 20:52:54
举报
文章被收录于专栏:Python七号Python七号

在本文中,你将学习图像的基本操作,如像素编辑、几何变换、代码优化、一些数学工具等。

图像的基本操作

学习读取和编辑像素值,使用图像 ROI 和其他基本操作。主要是以下四点:

  • 访问像素值并修改它们
  • 访问图像属性
  • 设置感兴趣区域 (ROI)
  • 拆分和合并图像

本节中几乎所有的操作都主要与 Numpy 相关,而不是 OpenCV。使用 OpenCV 编写更好的优化代码需要良好的 Numpy 知识。

访问像素值并修改它们
代码语言:javascript
复制
>>> import numpy as np
>>> import cv2 as cv
>>> img = cv.imread('messi5.jpg')

接下来就可以通过其行和列坐标访问像素值。对于 BGR 图像,它返回一个包含蓝色、绿色、红色值的数组。对于灰度图像,只返回相应的强度:

代码语言:javascript
复制
>>> px = img[100,100] #访问坐标(100,100)处的像素值
>>> print( px ) #打印出来的是BGR,也就是蓝、绿、红、对应的值
[157 166 200]
# #访问B通道像素值,那么传入索引 0,相应的访问 R 通道,就是 2
>>> blue = img[100,100,0]
>>> print( blue )
157
>>> red = img[100,100,2]
>>> print( red )
200

我们可以直接修改某一坐标的像素值:

代码语言:javascript
复制
>>> img[100,100] = [255,255,255]
>>> print( img[100,100] )
[255 255 255]

Numpy 是一个用于快速数组计算的优化库。因此,简单地访问每个像素值并对其进行修改将非常慢,上述代码仅用于演示,不是推荐的做法。

更优雅的访问并修改像素的做法是这样的:

代码语言:javascript
复制
# 访问坐标10,10 出的 R 值
>>> img.item(10,10,2)
59
# 修改坐标10,10 出的 R 值
>>> img.itemset((10,10,2),100)
>>> img.item(10,10,2)
100
访问图像属性

获取图片的形状:

代码语言:javascript
复制
>>> print( img.shape )
(342, 548, 3)

342 是高,也就是有多少行像素值,548 是宽,也就是有多少列像素值,而 3 代表 3 通道,表示这是个彩色图而不是灰度图。如果是灰度图,那么返回的结果只有高和宽。

获取总的像素数:342*548*3 = 562248

代码语言:javascript
复制
>>> print( img.size )
562248

获取图片的数据类型:

代码语言:javascript
复制
>>> print( img.dtype )
uint8

因为像素的最大值就是 255,因此,8 位够用了。

设置感兴趣区域 ROI(ROI-Region of Interest)

有时,我们将不得不使用某些图像区域。比如,对于图像中的眼睛检测,首先对整个图像进行人脸检测。当获得人脸时,我们只选择人脸区域并在其中搜索眼睛,而不是搜索整个图像。它提高了准确性(因为眼睛总是在脸)和性能(因为我们在一个小区域内搜索)。

使用 Numpy 索引来获得 ROI。在这里,我选择了球并将其复制到图像中的另一个区域:

代码语言:javascript
复制
>>> ball = img[280:340, 330:390]
>>> img[273:333, 100:160] = ball

效果图如下:

拆分和合并图像

有时你需要单独处理图像的 B、G、R 通道。在这种情况下,你需要将 BGR 图像拆分为单个通道。在其他情况下,你可能需要加入这些单独的频道来创建 BGR 图像。你可以通过以下方式简单地做到这一点:

代码语言:javascript
复制
>>> b,g,r = cv.split(img)
>>> img = cv.merge((b,g,r))

但是,cv.split 效率没有下面使用索引的方式高:

代码语言:javascript
复制
>>> b = img[:,:,0]

修改也可以用索引,比如你想把所有的红色值设为 0:

代码语言:javascript
复制
>>> img[:,:,2] = 0

有时候,我们想为图片加上边框,比如相框,可以使用 cv.copyMakeBorder()。但它在卷积运算、零填充等方面有更多应用。此函数采用以下参数:

下面是一个示例代码,演示了所有这些边框类型,以便你可以更好地理解:

代码语言:javascript
复制
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
BLUE = [255,0,0]
img1 = cv.imread('opencv-logo.png')
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()

效果如下所示:

图像的算术运算

对图像执行算术运算,如加法、减法、按位运算等。主要使用这些函数:cv.add()、cv.addWeighted() 等。

图片加法

你可以使用 OpenCV 函数 cv.add() 或简单地通过 numpy 操作 res = img1 + img2 添加两个图像。两个图像应该具有相同的深度和类型,或者第二个图像可以只是一个标量值。

OpenCV 加法和 Numpy 加法是有区别的。OpenCV 加法是饱和运算,而 Numpy 加法是模运算。

代码语言:javascript
复制
>>> x = np.uint8([250])
>>> y = np.uint8([10])
>>> print( cv.add(x,y) ) # 250+10 = 260 => 255
[[255]]
>>> print( x+y )          # 250+10 = 260 % 256 = 4
[4]
图片混合

这也是图像添加,但为图像赋予不同的权重,以便给人一种混合或透明的感觉。根据以下等式添加图像:

α 从 0→1。。在这里我们把两张图像混合在一起。第一张图片的权重为 0.7,第二张图片的权重为 0.3。cv.addWeighted() 将以下等式应用于图像,这里 γ 是 0。

代码语言:javascript
复制
img1 = cv.imread('ml.png')
img2 = cv.imread('opencv-logo.png')
dst = cv.addWeighted(img1,0.7,img2,0.3,0)
cv.imshow('dst',dst)
cv.waitKey(0)
cv.destroyAllWindows()

效果如下:

位运算

这包括按位与、或、非和异或运算。它们在提取图像的任何部分(我们将在接下来的章节中看到)、定义和使用非矩形 ROI 等时非常有用。下面我们将看到一个如何更改图像特定区域的示例。

比如将 OpenCV logo 放在图像上方。如果我添加两个图像,它会改变颜色。如果我混合它们,我会得到透明的效果。但我希望它是不透明的。如果它是一个矩形区域,我可以使用 ROI。但是 OpenCV 标志不是一个矩形。因此,你可以使用按位运算来完成,如下所示:

代码语言:javascript
复制
# 读取两个图片
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)
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)

# 从 logo 图像中仅获取 logo 区域
img2_fg = cv.bitwise_and(img2,img2,mask = mask)


# 放置 logo
dst = cv.add(img1_bg,img2_fg)
img1[0:rows, 0:cols ] = dst


cv.imshow('res',img1)
cv.waitKey(0)
cv.destroyAllWindows()

效果图:

性能测试和改进

获得解决方案很重要。但是以最快的方式获得它更重要。

在图像处理中,由于你每秒处理大量操作,因此你的代码不仅要提供正确的解决方案,而且还要以最快的方式提供解决方案,这是必须的。接下来,我们看一下如何衡量代码的性能和一些提高代码性能的技巧。

会用到这些函数:cv.getTickCount、cv.getTickFrequency 等。

除了 OpenCV,Python 还提供了一个模块 time,有助于测量执行时间。另一个模块 profile 有助于获得关于代码的详细报告,例如代码中每个函数花费了多少时间,函数被调用了多少次等。

使用 OpenCV 测量性能

cv.getTickCount 函数返回从机器开启的那一刻到调用此函数的那一刻的时钟周期数。因此,如果你在函数执行之前和之后调用它,你将获得用于执行函数的时钟周期数。

cv.getTickFrequency 函数返回时钟周期的频率,或每秒的时钟周期数。因此,要以秒为单位查找执行时间,你可以执行以下操作:

代码语言:javascript
复制
e1 = cv.getTickCount()
# 这里放置你的代码
e2 = cv.getTickCount()
time = (e2 - e1)/ cv.getTickFrequency()

通过以下示例进行演示。下面的例子应用了中值过滤,核的大小从 5 到 49 不等:

代码语言:javascript
复制
img1 = cv.imread('messi5.jpg')
e1 = cv.getTickCount()
for i in range(5,49,2):
    img1 = cv.medianBlur(img1,i)
e2 = cv.getTickCount()
t = (e2 - e1)/cv.getTickFrequency()
print( t )
# 0.521107655 seconds

你也可以使用 time 模块来计时

OpenCV 中的默认优化

许多 OpenCV 函数都使用 SSE2、AVX 等进行了优化。它还包含未优化的代码。因此,如果我们的系统支持这些功能,我们应该利用它们(几乎所有现代处理器都支持它们)。编译时默认启用。所以 OpenCV 如果启用则运行优化的代码,否则运行未优化的代码。你可以使用 cv.useOptimized() 检查它是否启用/禁用,并使用 cv.setUseOptimized() 启用/禁用它。让我们看一个简单的例子。

代码语言:javascript
复制
# 检查优化是否启用
In [5]: cv.useOptimized()
Out[5]: True
In [6]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 34.9 ms per loop
# 禁用优化
In [7]: cv.setUseOptimized(False)
In [8]: cv.useOptimized()
Out[8]: False
In [9]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 64.1 ms per loop
在 IPython 中测试时间
代码语言:javascript
复制
In [10]: x = 5
In [11]: %timeit y=x**2
10000000 loops, best of 3: 73 ns per loop
In [12]: %timeit y=x*x
10000000 loops, best of 3: 58.3 ns per loop
In [15]: z = np.uint8([5])
In [17]: %timeit y=z*z
1000000 loops, best of 3: 1.25 us per loop
In [19]: %timeit y=np.square(z)
1000000 loops, best of 3: 1.16 us per loop
性能优化技术

有几种技术和编码方法可以发挥 Python 和 Numpy 的最大性能。此处仅注明相关内容,并提供重要来源的链接。这里要注意的主要是,首先尝试以简单的方式实现算法。一旦它开始工作,就对其进行分析,找到瓶颈再对其进行优化。

  1. 尽可能避免在 Python 中使用循环,尤其是双/三循环等。它们天生就很慢。
  2. 尽可能将算法/代码矢量化,因为 Numpy 和 OpenCV 针对矢量运算进行了优化。
  3. 利用缓存一致性。
  4. 除非必要,否则切勿复制数组。尝试改用视图。数组复制是一项昂贵的操作。

如果你的代码在执行完所有这些操作后仍然很慢,或者如果不可避免地使用大循环,请使用 Cython 等其他库来加快速度。

可以参考:

Python 优化技术[1]

Numpy 高级操作[2]

IPython 中的时序和分析[3]

参考资料

[1]

Python 优化技术: https://wiki.python.org/moin/PythonSpeed/PerformanceTips

[2]

Numpy 高级操作: https://scipy-lectures.github.io/advanced/advanced_numpy/index.html#advanced-numpy

[3]

IPython 中的时序和分析: https://pynash.org/2013/03/06/timing-and-profiling/

上一篇:

OpenCV 教程 01:简介与安装,图片与视频的基本操作

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python七号 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 图像的基本操作
    • 访问像素值并修改它们
      • 访问图像属性
        • 设置感兴趣区域 ROI(ROI-Region of Interest)
          • 拆分和合并图像
          • 图像的算术运算
            • 图片加法
              • 图片混合
                • 位运算
                • 性能测试和改进
                  • 使用 OpenCV 测量性能
                    • OpenCV 中的默认优化
                      • 在 IPython 中测试时间
                        • 性能优化技术
                        相关产品与服务
                        人脸识别
                        腾讯云神图·人脸识别(Face Recognition)基于腾讯优图强大的面部分析技术,提供包括人脸检测与分析、比对、搜索、验证、五官定位、活体检测等多种功能,为开发者和企业提供高性能高可用的人脸识别服务。 可应用于在线娱乐、在线身份认证等多种应用场景,充分满足各行业客户的人脸属性识别及用户身份确认等需求。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档