CSDN:OpenCV学习_笨小古的博客-CSDN博客
在计算机视觉中,使用者时常需要与计算机本身进行交互。比如打开摄像头进行拍照时,机器本身可能很难知道使用者想要它在什么时刻进行拍照动作,但是我们可以通过手动操作来实现。而我们与系统的交互一般传递的是 ASCII (American Standard Code for Information Interchange, 美国信息交换标准码),为此我们需要将键盘输入的字符转为 ASCII 来让计算机明白我们想要它去做什么操作。
那么首先要学习的便是实现使用者与计算机之间交互的基础——将键盘输入的字符转换为 ASCII 数值或 Unicode(统一码)数值。
ord 函数的语法如下。
ord(char)
其中char为一个字符类型的参数,它的返回值是char所对应的十进制整数 ASCII 数值,如 ord(‘a’) 返回97, ord(‘A’)返回65.
**注意:**如果所给的Unicode字符超出了Python的定义范围,则会引发一个名为"TypeError"的异常。
如果将图片看成一种输入信号,那么其中难免会有噪声干扰计算机本身进行兴趣数据(即使用者想要的数据)的提取。为了尽可能减少干扰,我们常常会使用 max 函数和 min 函数来对含有噪声的图像进行去噪操作。
(在计算机视觉处理中,我们一般认为图像指的是“数字图像”,是一种原始连续信号经过抽样、量化后的结果状态:而图片则更多蕴含的是一种“初始”的概念,定义的范围比图像更加广泛。可以说,图像是图片的一种表达形式)
max 函数和 min 函数分别返回给定参数的最大值与最小值。
max 函数的语法如下,[符号后表示的是参数可选项,可以不使用。
max(iterable, *[, key, default])
max 函数的参数解释如下:
- iterable: 一个可迭代对象。
- key: 可以理解为一个排列的方法。
示例代码如下。
b = ['a', 'B']
print(max(b))
print(max(b,key=str.upper))
第二行代码的运行结果为a,因为此时 max 函数会按照全体 ASCII 的顺序去比较数值大小,a–97,B–66,所以最大值为a。但是如果我们加入key的参数,如上面第三行:
key=str.upper
max 函数的排列顺序就按照字符的大写的 ASCII 数值去比较,此时a的大写字符A的 ASCII 数值为65,而B的 ASCII 数值为66,所以第三行代码的运行结果为B。
min 函数的语法和max 函数一致,只是返回值为给定参数的最小值。
**注意:**key 后面可以跟对象参数的方法。
与 max 函数和 min 函数相同,sorted 函数也可以用来提高程序抗图片噪声干扰的能力。 sorted 函数的作用是对所有可迭代的对象进行排序。
该函数的语法如下。
sorted(iterable, cmp=None, key=None, reverse=False)
sorted 函数的参数解释如下。
- iterable: 一个可迭代对象。
- cmp: 一个比较函数,其具有两个参数,参数的值都是从可迭代对象中取出的;此函数必须满足一个条件,即大于则返回1,小于则返回-1,等于则返回0.
- key: 和 max 函数、min 函数中的 key 一样,主要是用来进行计较的元素,只有一个参数,具体的函数取自可迭代对象,指定可迭代对象中的一个元素来进行排序。
- reverse: 控制排序规则,如果 reverse=True,则为从大到小;如果 reverse=False,则为从小到大(默认为False)。
返回值为经过处理的列表。
示例代码如下。
a=[1,2,3,4,5]
b=sorted(a,reverse=True)
print(b)
其运行结果如下:
[5,4,3,2,1]
&0xff是用户在调用 cv2.waitKey 的时候经常会加在后面的代码,但是它的意义到底是什么呢?如果你的计算机操作系统是64位的,在使用 cv2.waitKey 这个函数时就需要在其后加上&0xff,形式如下。
cv2.waitKey(0)&0xff
因为系统中的各个按键(例如键盘上的q键)都在 ASCII 表中有一个对应的值,但是系统中各个按键对应的 ASCII 码值并不一定只有8位(即不同系统中对应的 ASCII 码值不一定相同),但最后8位一定相同,所以此处加上&0xff是为了排除不同系统对判断按键的干扰。
主要内容:
一张 1024 x 960 像素的图片,如果我们在程序里使用它或者对它进行一些操作,那么它就是一个 960 行、1024 列的二维矩阵。矩阵的每一个元素存储的都是一个列表,而列表里面存在的则是各个通道的值,那什么是通道呢?
在学习通道之前,先了解一下图片的几种存储形式。
我们平常在生活里拍摄的图片一般是 RGB (R:红色, G:绿色, B:蓝色)格式的图片,而在OpenCV里,图片使用的格式是 BGR,两者本质上没有区别,只是使用的习惯不同。调节3种颜色的值可以构成不同颜色的像素点,但是我们在处理图片的时候,一般都不直接采用 BGR 图进行操作,而是需要进行图片颜色格式的转换。
我们称B、G、R为图片上每个像素点构成的通道,所以BGR图是一个三通道(蓝、绿、红3种通道)图片。在OpenCV里,每个通道的取值范围均为0~255,我们可以通过Python中的元组的形式进行颜色的合成,如(255, 255, 255)为白色,(0, 0, 0)为黑色。
相比于 BGR 图,灰度图的每个像素点不再由 B、G、R这3个通道构成,它只由一个通道来控制,即灰度值,所以灰度图是由灰度值来控制的单通道图。灰度图的取值范围为0~255;如果取0表示黑色;如果取255表示白色。
HSV图显示出来也是彩色的,且HSV图也是由3个通道构成的,只不过HSV图里的3个通道和BGR图中的不同,它的3个通道的介绍分别如下。
HSV图也可以理解为BGR图的另一种表达方式,这种表达方式更有助于我们对指定颜色的物体进行追踪和提取。
二值图可以理解为一种很特殊的灰度图,它不具有通道,并且图中的每个像素点的取值只有0或255两个值,换句话说,即非黑即白。而灰度图与二值图不同的地方就在于,灰度图可以取0~255的任何一个值。
二值图的意义在于它可以帮助用户去除图片噪点,使得图片内只存在我们想要的那个物体的二值化表示部分。
进行图片的读取需要调用函数cv2.imread(),它的语法如下。
cv2.imread(filename, flag=1)
示例代码可以如下:
import cv2
import numpy as np
img = cv2.imread('1.jpg')
经此读取,img这个变量就已经是矩阵形式的"1.jpg"图片,并且它的每个像素点都是以BGR三通道的方式存储的。
如果我们想要以灰度图的方式读取图片,只需要将flag参数设置为0即可,代码如下:
import cv2
import numpy as np
img = cv2.imread('1.jpg', 0)
此时,img变量中保存的就是单通道的"1.jpg"图片的矩阵形式,矩阵的每个元素列表中只有一个灰度值。
进行图片的保存需要调用函数cv2.imwrite(),它的语法如下。
cv2.imwrite(filename, img)
- filename:要保存的图片的名字,并且需要包含在图片的存储位置中(可以是相对位置,也可以是绝对位置)。
- img:要保存的图片的矩阵形式。
示例代码如下:
import cv2
import numpy as np
img = cv2.imread('1.jpg', 0)
cv2.imwrite('1.png', img)
上述代码就是将py文件夹中的图片"1.jpg"以矩阵方式读取到程序中,然后将它以"1.png"为名保存到.py文件所在的文件夹中(如果是相对位置的话;如果是绝对位置也可以存储到其他位置)。
img = cv2.imread(in_finename)
cv2.imwrite(out_finename, img)
在有些地方可能会看到的图片显示方式与本处有所出入,尽管调用的是cv2.imread()函数,但是它后面的参数并不是1,而是cv2.IMREAD_COLOR,代码如下:
cv2.imread(filename, cv2.IMREAD_COLOR)
它是与下列代码等价的。
cv2.imread(filename)
cv2.imread(filename, 1)
以灰度图的方式打开图片然后再保存:
img = cv2.imread(in_filename, 0)
cv2.imwrite(out_filename, img)
**注意:**这里保存的图片只是看起来像灰度图,如果再加载此图片,它还是以 BGR 的方式加载。
当然,灰度图也有另一种读取方法,调用的依旧是cv2.imread()函数,但是后面的参数并不是0.而是cv2.IMREAD_GRAYSCALE,代码如下:
cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
等价于以下代码:
img = cv2.imread(filename, 0)
如果要在程序运行的时候观察图片的当前状态,就需要使用cv2.imshow()函数来进行图片展示,它的语法如下:
cv2.imshow(name, img)
示例代码如下:
import cv2
import numpy as np
img = cv2.imread('1.jpg', cv2.IMREAD_COLOR)
cv2.imshow('image', img)
cv2.imwrite('1_out.png', img)
# 如果使用了cv2.imshow()函数,下面一定要跟着一个摧毁窗口的函数
cv2.destroyAllWindows()
这时还会出现一个问题,就是图片弹出来一瞬间便就关闭了,这是因为 cv2.imshow() 函数并没有延时的作用,这个函数起着显示图片的作用,可是计算机的运行速度比计算机窗口的弹出速度要快,所以窗口只显示了极短的时间,代码已经执行到下一行了,图片就自动关闭了。那么,要使图片停留足够的时间以达到可以观察清楚的效果,我们需要在使用函数的这行代码和下一行代码之间加一个延时函数就可以了。
现在我们在 cv2.imshow() 所在行和下一行的代码之间加上一个延时函数来确保人眼能够观察清楚窗口。我们需要 cv2.waitKey 函数来做到这一点,语法如下:
cv2.waitKey(time)
其中,time表示等待的时间,单位为毫秒。
这个函数可以这样理解,在 time 时间内,计算机会等待我们键盘上的命令,如果在 time 时间内程序没有等待到按键指令,它就会自动进入到下一帧。因为这里是图片,没有下一帧,所以就会关闭窗口。如果将 time 设置为0,并不是表示等待0毫秒然后进入下一帧,而是停止在当前帧,有按键指令它才会进入下一帧。所以在显示单张图片的时候大多数情况都写的是cv2.waitKey(0)。
直接使用cv2.waitKey(0)进行延时时,按键盘上的任何键,此程序过程都将关闭。如果想要按某个特定的键程序才会关闭,可以通过添加一个 if 语句来进行判定。如果按的键不为指定的键,就会一直处于 while 循环中,直到我们按下指定的键后才会通过 break 语句结束循环,之后才能调用 cv2.destroyAllWindows()函数来摧毁窗口。
例如指定按键 q 键来退出窗口,这时 while 循环可以这样写:
while 1:
if cv2.waitKey(0) == ord('q'):
break
else:
pass
cv2.destroyAllWindows()
关于如何控制图片窗口显示的大小
通过实例演示可以了解到窗口的大小取决于图片尺寸的大小,一张图片的尺寸越大,它所对应的窗口就越大,这是因为我们没有控制窗口尺寸的大小。控制窗口大小的函数为 cv2.namedWindow,其初始设定的标签为 cv2.WINDOW_AUTOSIZE,将其改为cv2.WINDOW_NORMAL 后就可以控制窗口的大小了。代码如下:
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow('image', img)
注意:
1.创建的窗口名字要与图片展示的窗口名字一致。
2. cv2.namedWindow 函数需要放在 cv2.imshow 函数的前面。
图片的行数是一张图片竖直方向上像素的个数,如一张 1024 x 960 像素的图片,它的行数就是960,列数就是1024.
import cv2
img = cv2.imread('1.jpg')
print(img.shape[0]) # 行数
print(img.shape[1]) # 列数
print(img.shope[2]) # 通道数
图片的行数、列数以及通道数都保存在 img.shape 的成员里面,因此我们可以输出整个成员。
print(img.shape)
BGR图的通道数为3,如果将BGR图换成灰度图,img.shape输出的元组结果只有两个元素,即没有通道数,只有图片的行数和列数。
**注意:**二值图可以理解为一种特殊的灰度图,所以在处理二值图的时候,我们也不能调用一张二值图的通道数。
cv2.line(img, start_point, end_point, color, thickness=0)
- img:需要画的图像。
- start_point:直线的开头,必须是一个元组类型。
- end_point:直线的结尾,必须是一个元组类型。
- color:直线的颜色,必须是一个元组类型。
- thickness:直线的宽度。
cv2.rectangle(img, point1, point2, color, thickness=0)
- img:需要处理的图像。
- point1:矩形左上角的点的坐标,必须是一个元组类型。
- point2:矩形右下角的点的坐标,必须是一个元组类型。
- color:线的颜色,必须是一个元组类型。
- thickness:线的宽度。
cv2.circle(img, center, R, color, thickness=0)
- img:要画的图像。
- center:圆心坐标,必须是一个元组类型。
- R:圆的半径。
- color:线的颜色,必须是一个元组类型。
- thickness:线的宽度,如果是-1,会变成向内填充。
cv2.ellipse(img, center, (a,b), direction, angle_start, angle_end, color, thickness=0)
- img:要画的图像。
- center:椭圆中心的位置。
- (a,b):椭圆的长轴和短轴。
- direction:按照顺时针方向旋转椭圆的角度。
- angle_start:画椭圆开始的角度。
- angle_end:画椭圆结束的角度。
- color:线的颜色。
- thickness:线的宽度,如果为-1,表示向内填充。
cv2.polylines(img, pts, isClosed, color, thickness=0)
- img:要画的图像。
- pts:点的集合,以列表的形式填入。
- isClosed:多边形是否闭合,如果为False则为一个不闭合的图形,如果为True则为一个闭合的图形。
- color:线的颜色。
- thickness:线的宽度。
**注意:**点的格式必须是 np.int32。
cv2.putText(img, text, org, fontFace, fontScale, color, thickness=0, lineType=0)
- img:要添加文字的图像。
- text:添加的文字。
- org:添加文字的位置。
- fontFace:字体。
- fontScale:字号大小。
- color:字体颜色。
- thickness:线条的宽度。
- lineType:线条的种类。
4.1 节 OpenCV学习——绘图函数案例_笨小古的博客-CSDN博客
关于处理鼠标事件,整个过程大致:程序一边执行下面的代码,一边等待鼠标的动作;当鼠标产生了动作(如单击屏幕),代码就去回调那个你打算在鼠标产生相应动作时运行的函数。
这个过程里调用回调函数的方法:使用cv2.setMouseCallback函数。它的作用是将画面和想要回调的函数进行绑定,其语法如下:
cv2.setMouseCallback(img, onMouse)
其参数分别解释如下:
- img:要绑定的画面的名字。
- onMouse:响应函数,即当鼠标事件触发时调用的函数。
可以使用如下代码查看所有被支持的鼠标事件。
# 查看所有被支持的鼠标事件
import cv2
events = [i for i in dir(cv2) if 'EVENT' in i]
print(events)
此处列举一些常用的鼠标事件:
序号 | 参数 | 对应的鼠标事件 |
---|---|---|
1 | cv2.EVENT_MOUSEMOVE | 滑动 |
2 | cv2.EVENT_LBUTTONDOWN | 左键单击 |
3 | cv2.EVENT_RBUTTONDOWN | 右键单击 |
4 | cv2.EVENT_MBUTTONDOWN | 中键单击 |
5 | cv2.EVENT_LBUTTONUP | 左键释放 |
6 | cv2.EVENT_RBUTTONUP | 右键释放 |
7 | cv2.EVENT_MBUTTONUP | 中键释放 |
8 | cv2.EVENT_LBUTTONBLCLK | 左键双击 |
9 | cv2.EVENT_RBUTTONBLCLK | 右键双击 |
10 | cv2.EVENT_MBUTTONBLCLK | 中键双击 |
11 | cv2.EVENT_FLAG_LBUTTON | 左键拖曳 |
12 | cv2.EVENT_FLAG_RBUTTON | 右键拖曳 |
13 | cv2.EVENT_FLAG_MBUTTON | 中键拖曳 |
14 | cv2.EVENT_FLAG_CTRLKEY | 按Ctrl键不放 |
15 | cv2.EVENT_FLAG_SHIFTKEY | 按Shift键不放 |
16 | cv2.EVENT_FLAG_ALTKEY | 按Alt键不放 |
回调函数的作用是在事件触发时,程序能够去调用它,编写回调函数时有一定的格式规范。不能随意设置参数,如下:
def callback(event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDBLCLK:
cv2.circle(img, (x,y), 100, (255, 0, 0), -1)
第一个参数event表示鼠标事件,x,y 可以理解为图像中鼠标指针所在的像素点的坐标值。
创建图像与窗口并将窗口与回调函数进行绑定
img = np.zeros((500,500,3), np.uint8) # 尺寸是500x500,图像是3通道
cv2.namedWindow('image')
cv2.setMouseCallback('image', callback)
通过滑动条来控制R、G、B这3个颜色的值,从而调配出各种颜色。滑动条式调色板的目标有以下两个:
这个过程涉及两个函数:cv2.createTrackbar 函数和 cv2.getTrackbarPos 函数。 cv2.createTrackbar 函数的作用是创建一个滑动条, cv2.getTrackbarPos 函数的作用是调用回调函数去接收指定滑动条的值。
cv2.createTrackbar 函数的语法如下:
cv2.createTrackbar(Track_name, img, min, max, TrackbarCallback)
- Track_name:滑动条的名字。
- img:滑动条所在画布。
- min:滑动条的最小值。
- max:滑动条的最大值。
- TrackbarCallback:滑动条的回调函数。
cv2.getTrackbarPos 函数的语法如下:
cv2.getTrackbarPos(Track_name, img)
- Track_name:滑动条的名字。
- img:滑动条所在画布。
函数返回值是滑动条当前所在的位置。
示例:滑动条控制的不只有R、G、B的值,还有一个使能端用于控制是否需要改变值。OpenCV中没有按钮,所以需要设置一个滑动条,它的值是0或1,当使能端值为1时,画布才会发生变化。
import cv2
import numpy as np
def nothing(x):
# 回调函数
pass
img = np.zeros((300, 512, 3), np.uint8)
cv2.namedWindow('image')
cv2.createTrackbar('R', 'image', 0, 255, nothing)
cv2.createTrackbar('G', 'image', 0, 255, nothing)
cv2.createTrackbar('B', 'image', 0, 255, nothing)
switch = '0:OFF\n1:ON'
cv2.createTrackbar(switch, 'image', 0, 1, nothing)
while (1):
cv2.imshow('image', img)
k = cv2.waitKey(1)
if k == ord('q'):
break
r = cv2.getTrackbarPos('R', 'image')
g = cv2.getTrackbarPos('G', 'image')
b = cv2.getTrackbarPos('B', 'image')
s = cv2.getTrackbarPos(switch, 'image')
if s == 0:
img[:] = 0
else:
img[:] = [b, g, r]
cv2.destroyAllWindows()
将最后一个滑动条的值滑到1值,就可以通过滑动上面的3个控制条来控制画布颜色在实时变化。
主要内容:
1.图像上某像素点的像素点的捕获。
2.对图像进行简单的算术操作。
3.图像逻辑运算。
注意:图像的运算在获取图像掩膜后进行图像捕捉时几乎是必须要用到的。
更新中…