⚠️这个系列是自己瞎翻的,文法很丑,跳着跳着捡重要的部分翻,翻错了不负责,就这样。
⚠️基于3.4.3,Basic Operations on Images,附原文。
目标
学会:
- 获取像素并且修改他们
- 获取图像属性
- 设置感兴趣区域(ROI) (译者注:ROI啥意思?看这里。)
- 拆分以及合并图像(译者注:这里有歧义,看完这节,应该是指拆分合并图像的彩色信号通道而不是图像本身)
几乎这部分所有的操作和Numpy相关的程度都超过了和OpenCV本身相关的程度。要用OpenCV写出更优雅的代码,Numpy的知识是不可或缺的。
*( 既然大多数的代码都只有一行,所以示例就在Python终端上展示。(译者注:以下很多代码抬行有>>>符号,实际上它们并不是代码的一部分而是Python终端显示的一部分。) )*
获取并且修改像素值
让咱们先加载一张图像:
>>> import numpy as np
>>> import cv2 as cv
>>> img = cv.imread('messi5.jpg')
你可以通过这个像素点的行和列坐标获取像素值。它会为一个BGR图像返回一个蓝,绿,红数值的数组。为灰度图像只会返回一个对应强度。
>>> px = img[100,100]
>>> print( px )
[157 166 200]
# accessing only blue pixel
>>> blue = img[100,100,0]
>>> print( blue )
157
你可以用同样的方式修改像素值。
>>> img[100,100] = [255,255,255]
>>> print( img[100,100] )
[255 255 255]
警告
Numpy是一个为了快速计算数组而优化过的第三方库。所以简单的获取遍历每一个像素值并且修改它会非常的慢而且显得没劲。
提示
以上方法是通常用于选择一个数组表示的区域。比如说前5行和后3列。要获取一个单独的像素点,最好考虑使用Numpy的array方法、array.item() 和 array.itemset()。但他们总是返回一个数值,如果你想要获取所有的B、G、R值,你需要分别为他们全部调用array.item()方法。
更好的获取并且编辑像素点的方法:
# accessing RED value
>>> img.item(10,10,2)
59
# modifying RED value
>>> img.itemset((10,10,2),100)
>>> img.item(10,10,2)
100
获取图像属性
图像属性包括行(像素)数、列(像素)数、(色彩信号)通道、图像格式类型、像素点总数量等等。
图像的形状通过img.shape方法来获取。它返回一个由行(像素)数、列(像素)数、(色彩信号)通道(如果是彩图的话)组成的元组。
>>> print( img.shape )
(342, 548, 3)
提示
如果一张图像是灰度图像,这个元组只包括行(像素)数和列(像素)数,因此这是个很好的方法用于检测图像是灰度图片还是彩色图片。
像素点总数量可以通过img.size来获取:
>>> print( img.size )
562248
图像格式类型可以用img.dtype来获取:
>>> print( img.dtype )
uint8
提示
当你debug的时候,img.dtype这方法是非常重要的,因为OpenCV-Python代码中大量的错误都是由无效的数据类型引起的。
图像的感兴趣区域
有时候,你必须和图像的某个特定区域较劲。我们用肉眼看图的时候,首先咱们会扫一轮图片来个人脸检测,一旦当我们发现一张人脸,我们就仅仅选择人脸的区域来寻找眼睛,而不是在整张图里去找眼睛。这种方案提升了准确率(因为眼睛一定在脸上)也提升了执行效率(因为我们只需要在一小片区域搜索)。
ROI感兴趣区除此之外还用在Numpy索引上。现在我正在选择一个足球,并且把它拷贝到图像的另外一个区域。
>>> ball = img[280:340, 330:390]
>>> img[273:333, 100:160] = ball
看看下面的结果。
拆分以及合并图像的彩色信号通道
有时你需要使用一张图像拆分后的B,G,R通道。在这种情况下,你需要把一张BGR图像拆成单独的彩色信号通道。你可以通过以下的代码来轻松搞定:
>>> b,g,r = cv.split(img)
>>> img = cv.merge((b,g,r))
或者
>>> b = img[:,:,0]
假如你想要设置所有的红色像素到0,你无需先去拆分彩色信号通道。用Numpy索引更快:
>>> img[:,:,2] = 0
警告
cv.split() 是一个消耗很大的操作(这里的消耗大指的是时间上),所以,除非你真的需要这么干,否则就用Numpy索引吧。
为图像做内边框
如果你需要围绕图像创建一个好像相框一样的边框,你可以使用 cv.copyMakeBorder() 方法。但这方法有更多的玩法,比如用来做卷积运算、补零函数(译者注:大家可以自行百度这两个关键词)等等,这方法有如下这些参数:
- src - 输入图像
- top, bottom, left, right - 在对应方向上以像素为单位的框框宽度
- borderType - 定义了要添加的边框的种类,可以是以下这些种类:
- cv.BORDER_CONSTANT - 添加一个常量的颜色边框,数值应该被给在下个参数里。
- cv.BORDER_REFLECT - 边框元素会被镜像反射来组成边框,比如这样:fedcba|abcdefgh|hgfedcb
- cv.BORDER_REFLECT_101 或者 cv.BORDER_DEFAULT - 和上面一样,但有些微小的变化,大概像这样:gfedcb|abcdefgh|gfedcba
- cv.BORDER_REPLICATE - 最后的元素会一直重复到尽头,像这样:aaaaaa|abcdefgh|hhhhhhh
- cv.BORDER_WRAP - 不知道咋解释(译者注:原文就是这么写的)它看起来像这样:cdefgh|abcdefgh|abcdefg
- cv.BORDER_CONSTANT - 添加一个常量的颜色边框,数值应该被给在下个参数里。
- value - 如果边框类型是 cv.BORDER_CONSTANT 的话,这个个参数要给出边框的颜色。
为了更好理解,下面是一段简单的代码来说明所有的边框类型:
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()
看下面的结果。(图像是在matplotlib里显示的,因此红色和蓝色的信号通道被互换了。(译者注:前面的章节有提到matplotlib和OpenCV的不同))