4 图片
4.1 读取/显示/保存
# 读取图片,是什么?数组。加载图片也尽量用英文,中文会无法加载 rose = cv2.imread('rose1.jpg') # (1365,2048,3)数组形状,1365像素 print(rose.shape) # numpy数组 print(type(rose)) # 三个中括号,三维数组,(高度、宽度、像素红绿蓝) print(rose) # 显示图片 # 弹窗展示 只能显示英文标题,中文会乱码.第一维上下,第二维左右,最后一维是颜色,-1进行翻转 cv2.imshow('rose', rose[:, :, [0, 2, 1]]) # 举例:-1可以让数组翻转 a = np.array([1, 3, 5, 7]) print(a, a[::-1], sep='\n') # 等待:0键盘输入消失 1500等待毫秒 cv2.waitKey(1500) cv2.destroyAllWindows() # 回收内存 # 保存对象 # [0,1,2]分别代表三个原色 cv2.imwrite('rwTest1.png', rose[:, :, ::-1]) cv2.imwrite('rwTest2.png', rose[:, :, [2, 0, 1]]) cv2.imwrite('rwTest2.png', rose[:, :, [0, 2, 1]])
4.2 使用 Matplotlib
Matplotib 是 python 的一个绘图库,里头有各种各样的绘图方法。之后会陆续了解到。
现在,你可以学习怎样用 Matplotib 显示图像。
你可以放大图像,保存它等等。
img = cv2.imread('rose.jpg', 0) plt.imshow(img, cmap='gray', interpolation='bicubic') plt.xticks([]) plt.xticks([]) plt.show()
注意:
彩色图像使用 OpenCV 加载时是 BGR 模式。
但是 Matplotib 是 RGB模式。
所以彩色图像如果已经被 OpenCV 读取,那它将不会被 Matplotib 正确显示。
5 视频
目标 • 学会读取视频文件,显示视频,保存视频文件 • 学会从摄像头获取并显示视频 • 你将会学习到这些函数:cv2.VideoCapture(),cv2.VideoWrite()
5.1 用摄像头捕获视频
我们经常需要使用摄像头捕获实时图像。OpenCV 为这中应用提供了一个非常简单的接口。让我们使用摄像头来捕获一段视频,并把它转换成灰度视频显示出来。从这个简单的任务开始吧。 为了获取视频,你应该创建一个VideoCapture 对象(Capture=捕获)。他的参数可以是设备的索引号,或者是一个视频文件。设备索引号就是在指定要使用的摄像头。一般的笔记本电脑都有内置摄像头。所以参数就是 0。你可以通过设置成 1 或者其他的来选择别的摄像头。之后,你就可以一帧一帧的捕获视频了。但是最后,别忘了停止捕获视频。
import numpy as np from cv2 import cv2 if __name__ == '__main__': # 首先创建一个videoCapture对象 Capture=捕获 cap = cv2.VideoCapture(0) print(cap.isOpened()) # 判断cap是否成功被初始化 while (True): # Capture frame-by-frame 逐帧捕获 ret, frame = cap.read() # 返回一个bool值 可用它判断是否读到了结尾 # Our operations on the frame come here 我们在框架上的行动就在这里 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 变成灰度的 # Display the resulting frame 显示结果帧 cv2.imshow('frame', gray) if cv2.waitKey(1) & 0xFF == ord('q'): break # When everything done, release the capture # 一切完成后,释放捕获 cap.release() cv2.destroyAllWindows()
-
cap.read() 返回一个布尔值(True/False)。如果帧读取的是正确的,就是 True。所以最后你可以通过检查他的返回值来查看视频文件是否已经到了结尾。
-
有时 cap 可能不能成功的初始化摄像头设备。这种情况下上面的代码会报错。你可以使用 cap.isOpened(),来检查是否成功初始化了。如果返回值是True,那就没有问题。否则就要使用函数 cap.open()。
-
你可以使用函数 cap.get(propId) 来获得视频的一些参数信息。这里propId 可以是 0 到 18 之间的任何整数。每一个数代表视频的一个属性,见下表
-
其中的一些值可以使用 cap.set(propId,value) 来修改,value 就是你想要设置成的新值。
-
例如,我可以使用 cap.get(3) 和 cap.get(4) 来查看每一帧的宽和高。
-
默认情况下得到的值是 640X480。但是我可以使用 ret=cap.set(3,320)和 ret=cap.set(4,240) 来把宽和高改成 320X240。
-
注意:当你的程序报错时,你首先应该检查的是你的摄像头是否能够在其他程序中正常工作(比如 linux 下的 Cheese)。
5.2 从文件中播放视频
与从摄像头中捕获一样,你只需要把设备索引号改成视频文件的名字。在播放每一帧时,使用 cv2.waiKey() 设置适当的持续时间。如果设置的太低视频就会播放的非常快,如果设置的太高就会播放的很慢(你可以使用这种方法控制视频的播放速度)。通常情况下25 毫秒就可以了。
import numpy as np from cv2 import cv2 if __name__ == '__main__': # 首先创建一个videoCapture对象 Capture=捕获 cap = cv2.VideoCapture('baby.mp4') while cap.isOpened(): # 逐帧捕获 ret, frame = cap.read() # ret是bool值 if ret: # frame就是每一帧的原图 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # RGB grayHSV = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # hsv f1 = frame[:, :, [0, 2, 1]] # 可以对原图进行切片 cv2.imshow('frame', gray) # waitkey加25毫秒一般大概等于正常速度,越小越快 if cv2.waitKey(60) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
注意:你应该确保你已经装了合适版本的 ffmpeg 或者 gstreamer。
如果你装错了那就比较头疼了。
什么意思?
FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。
采用LGPL或 GPL 许可证。 它提供了录制、转换以及流化音视频的完整解决方案。
Gstreamer 是一个支持Windows,Linux,Android, iOS的跨平台的多媒体框架,应用程序可以通过管道(Pipeline)的方式,将多媒体处理的各个步骤串联起来,达到预期的效果。
5.3 保存视频
在我们捕获视频,并对每一帧都进行加工之后我们想要保存这个视频。对于图片来说很简单只需要使用 cv2.imwrite()。但对于视频来说就要多做点工作。
这次我们要创建一个VideoWriter 的对象。我们应该确定一个输出文件的名字。接下来指定 **FourCC 编码(下面会介绍)。播放频率和帧的大小也都需要确定。最后一个是isColor 标签**。如果是 True,每一帧就是彩色图,否则就是灰度图。 FourCC 就是一个 4 字节码,用来确定视频的编码格式。可用的编码列表可以从fourcc.org查到。这是平台依赖的。下面这些编码器对我来说是有用个。
• In Fedora: DIVX, XVID, MJPG, X264, WMV1, WMV2. (XVID更可取。MJPG可产生大尺寸视频。X264提供了非常小的视频) • In Windows: DIVX (还有更多需要测试和添加) • In OSX : (我无法访问OSX。有人能填这个吗?)
FourCC 码以下面的格式传给程序,以 MJPG 为例:
cv2.cv.FOURCC('M','J','P','G') 或者 cv2.cv.FOURCC(*'MJPG')。
6 OpenCV 中的绘图函数
目标
• 学习使用 OpenCV 绘制不同几何图形 • 你将会学习到这些函数:cv2.line(),cv2.circle(),cv2.rectangle() ,cv2.ellipse() ,cv2.putText() 等。
代码
上面所有的这些绘图函数需要设置下面这些参数: • img:你想要绘制图形的那幅图像。 • color:形状的颜色。以 RGB 为例,需要传入一个元组,例如: (255,0,0 )代表蓝色。对于灰度图只需要传入灰度值。 • thickness:线条的粗细。如果给一个闭合图形设置为 -1,那么这个图形就会被填充。默认值是 1. • linetype:线条的类型,8 连接,抗锯齿等。默认情况是 8 连接。cv2.LINE_AA为抗锯齿,这样看起来会非常平滑。
6.1 画线line
要画一条线,你只需要告诉函数这条线的起点和终点。我们下面会画一条从左上方到右下角的蓝色线段。
正在上传…重新上传取消
6.2 画矩形rectangle
要画一个矩形,你需要告诉函数的左上角顶点和右下角顶点的坐标。这次我们会在图像的右上角话一个绿色的矩形。
正在上传…重新上传取消
6.3 画圆circle
要画圆的话,只需要指定圆形的中心点坐标和半径大小。我们在上面的矩形中画一个圆。
正在上传…重新上传取消
6.4 画椭圆ellipse
画椭圆比较复杂,我们要多输入几个参数。一个参数是中心点的位置坐标。 下一个参数是长轴和短轴的长度。椭圆沿逆时针方向旋转的角度。椭圆弧演顺时针方向起始的角度和结束角度,如果是 0 很 360,就是整个椭圆。查看cv2.ellipse() 可以得到更多信息。下面的例子是在图片的中心绘制半个椭圆。
正在上传…重新上传取消
6.5 画多边形polylines
画多边形,需要指点每个顶点的坐标。用这些点的坐标构建一个大小等于行数 X1X2 的数组,行数就是点的数目。这个数组的数据类型必须为 int32。 这里画一个黄色的具有四个顶点的多边形。
正在上传…重新上传取消
-
这里 reshape 的第一个参数为 -1, 表明这一维的长度是根据后面的维度的计算出来的。
-
注意:如果第三个参数是 False,我们得到的多边形是不闭合的(首尾不相连)。
-
注意:cv2.polylines() 可以被用来画很多条线。只需要把想要画的线放在一个列表中,将这个列表传给函数就可以了。每条线都会被独立绘制。
这会比用cv2.line() 一条一条的绘制要快一些。
import numpy as np from cv2 import cv2 # 先做一个黑底的图片 # img = np.zeros((512, 512, 3), np.uint8) img = cv2.imread('rose1.jpg') # 也读一张图来画 # 应该也可以读一个视频,然后逐帧的在上面话 # 画一条蓝色线,线宽5像素 # 画布、起点、终点、颜色、线宽 cv2.line(img, (0, 0), (511, 511), (255, 0, 0), 5) cv2.line(img, (511, 0), (0, 511), (255, 0, 0), 5) # 画一个矩形 cv2.rectangle(img, (384, 0), (510, 125), (0, 255, 0), 3) # 画圆 # 画布、圆心坐标、半径、颜色、-1标识填充 cv2.circle(img, (448, 63), 63, (0, 0, 255), -1) # 画椭圆 # 画布、中心店、长轴和短轴、顺时针旋转的方向和角度、逆时针旋转的方向和角度、填充 cv2.ellipse(img, (256, 256), (100, 50), 0, 0, 180, 255, -1) # 多边形 pts = np.array([[10, 5], [20, 30], [70, 20], [50, 10]], np.int32) print(pts) # 是一个二维数组 # reshape:-1=表示这一维度的长度是根据后面的维度计算出来的 # True表示多边形闭合,首位连接 pts = pts.reshape(-1, 1, 2) print([pts]) # 变成了一个三维数组 # 这样将想画的线列成数组,传给polyline函数就可以画多条线,不用一条一条画线 cv2.polylines(img, [pts], True, (0, 255, 255), 2) # 在图片上写字 font = cv2.FONT_HERSHEY_SIMPLEX # 字体 # cv2.putText(img, 'im lihang', (10, 500), font, 4, (255, 255, 255), 2, cv2.LINE_AA) cv2.imshow('img', img) cv2.waitKey(3000) cv2.destroyAllWindows()
6.6 在图片上添加文字putText
要在图片上绘制文字,你需要设置下列参数:
-
你要绘制的文字
-
你要绘制的位置
-
字体类型(通过查看 cv2.putText() 的文档找到支持的字体)
-
字体的大小
-
文字的一般属性如颜色,粗细,线条的类型等。为了更好看一点推荐使用
-
linetype=cv2.LINE_AA。
-
-
在图像上绘制白色的 OpenCV。
警 告:
所 有 的 绘 图 函 数 的 返 回 值 都 是 None, 所 以 不 能 使 用 img =cv2.line(img,(0,0),(511,511),(255,0,0),5) 。
结果 下面就是最终结果了,通过你前面几节学到的知识把他显示出来吧。
7 把鼠标当画笔
目标
• 学习使用 OpenCV 处理鼠标事件 • 你将要学习的函数是:cv2.setMouseCallback()
7.1 简单演示
这里我们来创建一个简单的程序,他会在图片上你双击过的位置绘制一个圆圈。首先我们来创建一个鼠标事件回调函数,但鼠标事件发生是他就会被执行。鼠标事件可以是鼠标上的任何动作,比如左键按下,左键松开,左键双击等。我们可以通过鼠标事件获得与鼠标对应的图片上的坐标。根据这些信息我们可以做任何我们想做的事。你可以通过执行下列代码查看所有被支持的鼠标事件。
import cv2 events=[i for i in dir(cv2) if 'EVENT'in i] print(events)
['EVENT_FLAG_ALTKEY', 'EVENT_FLAG_CTRLKEY', 'EVENT_FLAG_LBUTTON', 'EVENT_FLAG_MBUTTON', 'EVENT_FLAG_RBUTTON', 'EVENT_FLAG_SHIFTKEY', 'EVENT_LBUTTONDBLCLK', 'EVENT_LBUTTONDOWN', 'EVENT_LBUTTONUP', 'EVENT_MBUTTONDBLCLK', 'EVENT_MBUTTONDOWN', 'EVENT_MBUTTONUP', 'EVENT_MOUSEHWHEEL', 'EVENT_MOUSEMOVE', 'EVENT_MOUSEWHEEL', 'EVENT_RBUTTONDBLCLK', 'EVENT_RBUTTONDOWN', 'EVENT_RBUTTONUP']
所有的鼠标事件回调函数都有一个统一的格式,他们所不同的地方仅仅是被调用后的功能。我们的鼠标事件回调函数只用做一件事:在双击过的地方绘制一个圆圈。下面是代码,不懂的地方可以看看注释。
问题:鼠标回调事件底层是怎么实现的?
from cv2 import cv2 import numpy as np # 查看一下从cv2都有哪些事件 # events = [i for i in dir(cv2) if 'EVENT' in i] # print(events) # 鼠标回调函数 def draw_circle(event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDBLCLK: cv2.circle(img, (x, y), 100, (255, 0, 0), -1) img = np.zeros((512, 512, 3), np.uint8) cv2.namedWindow('image') #回调函数用的是什么知识? cv2.setMouseCallback('image', draw_circle) while 1: cv2.imshow('image', img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
7.2 高级一点的示例
现在我们来创建一个更好的程序。这次我们的程序要完成的任务是根据我们选择的模式在拖动鼠标时绘制矩形或者是圆圈(就像画图程序中一样)。所以我们的回调函数包含两部分,一部分画矩形,一部分画圆圈。这是一个典型的例子他可以帮助我们更好理解与构建人机交互式程序,比如物体跟踪,图像分割等。
from cv2 import cv2 import numpy as np drawing = False # 如果鼠标按下、则为true mode = True # 如果为True,则绘制矩形。按“m”切换到曲线 ix, iy = -1, -1 # 鼠标回调函数 def draw_circle(event, x, y, flags, param): global ix, iy, drawing, mode # global是全局变量 if event == cv2.EVENT_LBUTTONDOWN: # 鼠标按下 drawing = True ix, iy = x, y elif event == cv2.EVENT_MOUSEMOVE: # 鼠标移动 if drawing: # 选择话什么图形 if mode: '''带个就是实心的,如果不带就是框框,但是有个问题 那样会每次移动就画个框,并不是鼠标抬起时再画框,如果想得到 那种效果,可以改进一下''' cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), -1) else: cv2.circle(img, (x, y), 5, (0, 0, 255), -1) elif event == cv2.EVENT_LBUTTONUP: # 鼠标抬起 drawing = False if mode: cv2.rectangle(img, (ix, iy), x, y, (0, 255, 0), -1) else: cv2.circle(img, (x, y), 5, (0, 0, 255), -1) # 接下来,我们必须将这个鼠标回调函数绑定到OpenCV窗口。 # 在主循环中,我们应该为按“m”键在矩形和圆形之间切换。 img = np.zeros((512, 512, 3), np.uint8) cv2.namedWindow('image') cv2.setMouseCallback('image', draw_circle) while 1: cv2.imshow('image', img) k = cv2.waitKey(1) & 0xFF if k == ord('m'): # 切换模式 mode = not mode elif k == 27: break cv2.destroyAllWindows()
画框框的延伸练习
from cv2 import cv2 import numpy as np drawing = False # 如果鼠标按下、则为true mode = True # 如果为True,则绘制矩形。按“m”切换到曲线 ix, iy = -1, -1 # 鼠标回调函数 def draw_circle(event, x, y, flags, param): global ix, iy, drawing, mode, img # global是全局变量 if event == cv2.EVENT_LBUTTONDOWN: # 鼠标按下 drawing = True ix, iy = x, y elif event == cv2.EVENT_MOUSEMOVE: # 鼠标移动 if drawing: # 选择话什么图形 if mode: ''' 画出最终的矩形框,交给鼠标抬起事件处理 这里需要做的是在鼠标按下&鼠标移动时 动态画出框框,怎么做? ''' '''这样就可以实现了''' img = np.zeros((512, 512, 3), np.uint8) cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0)) ''' 但是新的问题来了,每次抬起鼠标后再次按下时 画布会重新刷新成初始,这样我只能在画布上留下一个框框, 而我希望再次画框时,上一次的还能保留 怎么办? ''' else: cv2.circle(img, (x, y), 5, (0, 0, 255), -1) elif event == cv2.EVENT_LBUTTONUP: # 鼠标抬起 drawing = False if mode: cv2.rectangle(img, (ix, iy), (x, y), (0, 255, 0), 1) else: cv2.circle(img, (x, y), 5, (0, 0, 255), -1) # 接下来,我们必须将这个鼠标回调函数绑定到OpenCV窗口。 # 在主循环中,我们应该为按“m”键在矩形和圆形之间切换。 img = np.zeros((512, 512, 3), np.uint8) cv2.namedWindow('image') cv2.setMouseCallback('image', draw_circle) while 1: cv2.imshow('image', img) k = cv2.waitKey(1) & 0xFF if k == ord('m'): # 切换模式 mode = not mode elif k == 27: break cv2.destroyAllWindows()
8 用滑动条做调色板
目标
• 学会把滑动条绑定到 OpenCV 的窗口 • 你将会学习这些函数:cv2.getTrackbarPos(),cv2.creatTrackbar()等。
8.1 代码示例
现在我们来创建一个简单的程序:通过调节滑动条来设定画板颜色。我们要创建一个窗口来显示显色,还有三个滑动条来设置 B,G,R 的颜色。当我们滑动滚动条是窗口的颜色也会发生相应改变。默认情况下窗口的起始颜色为黑。 cv2.getTrackbarPos() 函数:
第一个参数是滑动条的名字
第二个参数是滑动条被放置窗口的名字
第三个参数是滑动条的默认位置。第四个参数是滑动条的最大值
第五个函数是回调函数,每次滑动条的滑动都会调用回调函数。回调函数通常都会含有一个默认参数,就是滑动条的位置。在本例中这个函数不用做任何事情,我们只需要 pass 就可以了。
滑动条的另外一个重要应用就是用作转换按钮。默认情况下 OpenCV 本身不带有按钮函数。所以我们使用滑动条来代替。在我们的程序中,我们要创建一个转换按钮,只有当装换按钮指向 ON 时,滑动条的滑动才有用,否则窗户口都是黑的。
from cv2 import cv2 import numpy as np # 干嘛用的? def nothing(x): pass # 做一个黑底色的片,为了画布 img = np.zeros((300, 512, 3), np.uint8) cv2.namedWindow('image') # 为调色增加trackbars控件三个 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) & 0xFF if k == 27: 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()
结合上一节的内容
from cv2 import cv2 import numpy as np def nothing(x): pass # 当鼠标按下时变为 True drawing = False # 如果 mode 为 true 绘制矩形。按下 'm' 变成绘制曲线。 mode = True ix, iy = -1, -1 # 创建回调函数 def draw_circle(event, x, y, flags, param): r = cv2.getTrackbarPos('R', 'image') g = cv2.getTrackbarPos('G', 'image') b = cv2.getTrackbarPos('B', 'image') color = (b, g, r) global ix, iy, drawing, mode # 当按下左键是返回起始位置坐标 if event == cv2.EVENT_LBUTTONDOWN: drawing = True ix, iy = x, y # 当鼠标左键按下并移动是绘制图形。 event 可以查看移动, flag 查看是否按下 elif event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON: if drawing: if mode: cv2.rectangle(img, (ix, iy), (x, y), color, -1) else: # 绘制圆圈,小圆点连在一起就成了线, 3 代表了笔画的粗细 cv2.circle(img, (x, y), 3, color, -1) # 下面注释掉的代码是起始点为圆心,起点到终点为半径的 # r=int(np.sqrt((x-ix)**2+(y-iy)**2)) # cv2.circle(img,(x,y),r,(0,0,255),-1) # 当鼠标松开停止绘画。 elif event == cv2.EVENT_LBUTTONUP: drawing = False # if mode==True: # cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1) # else: # cv2.circle(img,(x,y),5,(0,0,255),-1) img = np.zeros((512, 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) cv2.setMouseCallback('image', draw_circle) while 1: cv2.imshow('image', img) k = cv2.waitKey(1) & 0xFF if k == ord('m'): mode = not mode elif k == 27: break