OpenCV - Basics
with its python binding
0. Introduction
这破玩意的在安装的时候叫
opencv-python,但是在引用的时候叫cv2,至少现在还叫这个名字...OpenCV的包有编号,一个
cv1一个cv2,这不是版本号,而是用来指代opencv底层开发语言的版本,一开始的cv1是用C开发的,后来的cv2是用C++开发的-
引用OpenCV
xxxxxxxxxx11import cv2
1. 图片
1.1 图片格式
-
一般的彩图是以RGB(Red-Green-Blue)模式储存的,但是OpenCV中彩图是以BGR(Blue-Green-Red)的通道顺序储存的
颠倒通道顺序是哪个逆天人物想出来的啊kora
一般B, G, R channel values在不同图片类型下的取值范围:
xxxxxxxxxx31CV_8U 8bit unsigned integer 0 - 2552CV_16U 16bit unsigned integer 0 - 655353CV_32F 32bit floating point 0 - 1 Alpha Channel是除了BGR以外的第四通道——“非彩色通道”,能读取透明度
灰度图则只有一个通道
1.2 导入图片 cv2.imread()
xxxxxxxxxx 1 1 img = cv2.imread('path/to/pic', read_mode) -
参数1 - 图片路径
与代码在同一文件夹下可直接写文件名
'hao_kang_de.png'不在同一文件夹下则需要给出完整路径
/home/user_name/hao_kang_de.png
Tip:路径中不要有中文
-
参数2 - 读取模式(Optional)
cv2.IMREAD_UNCHANGED- 4通道(BGR+Alpha)彩图(-1)cv2.IMREAD_GRAYSCALE- 单通道灰度图(0)cv2.IMREAD_COLOR- 3通道(BGR)彩图(1),默认值
返回值 - BGR形式的图像对象,类型为
numpy.ndarray
1.3 显示图片 cv2.imshow()
xxxxxxxxxx 4 1 cv2.namedWindow('hao_kang_de', window_type) # Optional 2 cv2.imshow('hao_kang_de', img) 3 cv2.waitKey(0) # Mandatory!!! 4 cv2.destroyAllWindows() # 详见善后工作 -
参数1 - 窗口的名称
如果定义窗口则需要二者的窗口名称保持一致
不先定义窗口那么打开的就是不可调整的自适应大小的窗口
参数2 - 导入的图片
-
配套的辅助性代码:
-
cv2.waitKey()(Mandatory)参数 - 等待的时间(单位:ms),时间到(或者按下某个键)后会执行接下来的程序,若数值为0则意味着一直等下去
waitKey控制着imshow的持续时间,所以二者必须配合食用:imshow后不跟waitKey相当于没有给imshow提供显示图像的时间
waitKey不是time.sleep(),前者必须在必须在至少存在一个HighGUI窗口的时候才会起作用;后者则是无条件的延时机制,且对HighGUI窗口不起作用;所以time.sleep()代替不了waitKey!
-
一行常用的代码的解释:
xxxxxxxxxx11cv2.waitKey(1000) & 0xFF == ord('q') # 1000ms内用户在键盘上按下了"q"键cv2.waitKey(1000)- returns a 32-bit integer corresponding to a pressed key in 1000ms0xFF- hex,换成bin为11111111,换成dec是255
waitKey返回值的范围是0-255,但是linux上的waitKey可能会返回比255更大的数值(毕竟总共有32位),拿
0xFF来做位与运算的目的是通过它保留waitKey正常返回值(前8位),屏蔽掉不该有但可能出现的的后24位。
-
cv2.namedWindow()(Optional)参数1 - 窗口的名称
参数2 - 窗口的类型
cv2.WINDOW_AUTOSIZE- 默认值,窗口大小自适应图片,不可调整(图片大的时候会很nasty)cv2.WINDOW_NORMAL- 窗口大小可以调整
-
1.4 保存图片 cv2.imwrite()
xxxxxxxxxx 1 1 cv2.imwrite('file_name.png', img) 参数1 - 包含后缀名的文件名(.bmp, .png, .jpg, .webp等等)
参数2 - 要保存的图片
1.5 读取属性信息
xxxxxxxxxx 1 1 img = cv2.imread('114514_1919810.png') -
Shape of an image
img.shapexxxxxxxxxx21>>> print(img.shape)2(114, 514, 3)若
img是彩图,则返回一个三项的tuple:(rows, columns, channels)若
img是灰度图,则只有两项,不返回channel的数量 -
Sum of pixels
img.sizexxxxxxxxxx21>>> print(img.size)2175788请注意,它返回的不是单一图层的pixel数量,而是三个图层所有pixel的数量,这点可以用
img.shape的数据计算得来 -
Image data type
img.dtype/astypexxxxxxxxxx81# 查看图片数据类型2>>> print(img.dtype)3uint8 # 8-bit unsigned integer45# 如果图片数据类型是float64之类的,然后需要转换到uint86>>> img = img.astype('np.uint8')7>>> print(img.dtype)8uint8
1.6 像素级修改
听起来很酷吧?其实并不(
需要始终记住:导入的图片是有图层的,而每个图层都是可以修改的
-
Access pixel
xxxxxxxxxx101# 图片坐标(100,250)的像素2# 获取完整的BGR值3>>> px = img[100, 250]4>>> print(px)5[157 166 200] # [Blue Green Red]67# 获取Blue图层的数值8>>> blue = img[100, 250, 0]9>>> print(blue)10157 -
Modify pixel
xxxxxxxxxx41# 修改图片坐标(111, 370)像素的BGR值2>>> img[111, 370] = [255, 255, 255]3>>> print(img[111, 370])4[255, 255, 255] -
关于单一pixel的单一图层
以上方式一般是用来选择图片的某个区域的,而对于某个pixel的某个图层,由于读取的图像是numpy数组,numpy内置的
array.item()与array.itemset()更合适xxxxxxxxxx91# 图片坐标(77, 256)像素的Red图层2# 获取数值3>>> img.item(77, 256, 2)416756# 修改数值7>>> img.itemset((77, 256, 2), 33)8>>> img.item(77, 256, 2)933 -
图像截取
数据类型是numpy数组,所以用最基本的slicing就可以截取了
xxxxxxxxxx11img_cut = img[16:33, 44:77]
1.7 通道拆分合并
彩图的BGR三个通道可以拆开,也可以合并
cv2.split()- 通道拆分cv2.merge()- 通道合并
xxxxxxxxxx 2 1 b, g, r = cv2.split(img) 2 neo_img = cv2.merge((b, g, r)) 实际上split()比较耗时,用numpy索引效率更高
xxxxxxxxxx 3 1 b = img[:, :, 0] 2 cv2.imshow('blue', b) 3 cv2.waitKey(0)
1.1.8 色彩空间转换
-
色彩空间
BGR和灰度图在图片格式里讲过了
HSV色彩空间
OpenCV中用得最多的色彩空间,比BGR更容易区分颜色(S和V的改变只会导致变成同一颜色的变体)
Hue - Saturation - Value(色相 - 饱和度 - 明度)

-
取值范围:
Hue - [0, 179]
Saturation - [0, 255]
Value - [0, 255]
Hue的理论数值应当是[0, 360],然而8bit图像最大只有255,所以OpenCV中Hue的范围被除了2
-
空间转换
xxxxxxxxxx11cv2.cvtcolor(img, cvt_mode) # 该函数能将图片从一种色彩空间(比如BGR)转换到另一种色彩空间(比如灰度图)参数1 - 待转换的导入的图片
-
参数2 - 转换模式,常用的两个:
BGR到灰度图 -
cv2.COLOR_BGR2GRAYBGR到HSV -
cv2.COLOR_BGR2HSV
xxxxxxxxxx41# Example: BGR -> gray scale23img = imread('hao_kang_de.jpg')4img_gray = cv2.cvtcolor(img, cv2.COLOR_BGR2GRAY)色彩转换其实是数学运算,比如灰度化常用的公式是:gray = R * 0.299 + G * 0.587 + B * 0.114
1.1.9 图片混合
-
直接相加
叠加两张格式,高度,宽度,通道数都相同的图片时,可以用
cv2.add(),也可以a + b(numpy数组相加),但原理不一样xxxxxxxxxx151img = cv2.add(src1, src2)2# 参数 src1/2 叠加的两张源图像34# Example5# 两张相同格式,相同通道数,仅有一个像素的图片6a = np.uint8([250])7b = np.uint8([10])8neo_1 = cv2.add(a, b)9neo_2 = a + b1011# 原理不一样,结果就有概率不一样12>>> print(neo_1)13255 # 250+10=260 => 255 ”有天花板”14>>> print(neo_2)154 # (250+10)%256 = 4 ”数值滚动”但是如果是Binary Image(只有0和255两种值,不是灰度图!),那么两种方法的结果是一样的
-
加权混图
cv2.addWeighted()允许将两张图片按照以下公式线性加权混合xxxxxxxxxx41res = cv2.addWeighted(src1, c1, src2, c2, c3)2# 参数3# src1/2 叠加的两张源图像4# c1/2/3 分别对应上文公式中的alpha,beta,gamma
2. 视频流
OpenCV提供了用于处理视频的类cv2.VideoCapture
2.1 捕获视频
cv2.VideoCapture() 既能处理相机视频流,也能处理视频文件
xxxxxxxxxx 5 1 # 初始化笔记本内置相机视频流 2 cap = cv2.VideoCapture(0) 3
4 # 初始化某视频文件 5 cap = cv2.VideoCapture('path/to/vid.mkv') 参数 - 可以是相机的设备索引号,也可以是到某个现成视频的path
返回值 - 捕获的cv2.VideoCapture对象
相机的设备索引号默认值为-1,表示随机选取一个摄像头
0代表第一个摄像头(一般是笔记本内置的那个),1代表第二个...以此类推
2.2 “确保”打开相机
cv2.VideoCapture.open() 用于打开相机
cv2.VideoCapture.isOpened() 用于检测相机是否正确打开,返回值为bool
xxxxxxxxxx 3 1 # 如果相机没打开,那就打开它 2 if not cap.isOpened(): 3 cap.open()
2.3 读帧(播放)
-
cv2.VideoCapture.read()-
简单读帧
这个函数能在视频流中捕获帧信息,会返回两个量:
是否正确读取帧(bool)
读取的帧的图像,如果没读到则该值为空
xxxxxxxxxx51ret, frame = cap.read()2if ret:3cv2.imshow('captured', frame)4cv2.waitKey(0)5cv2.destroyWindow('captured') -
播放视频
xxxxxxxxxx131cap = cv2.VideoCapture('path/to/something.mkv')23if not cap.isOpened():4cap.open()56while True:7ret, frame = cap.read()8if ret:9cv2.imshow('hao_kang_de', frame)10cv2.waitKey(1)1112cap.release()13cv2.destroyAllWindows()我们可以通过控制
waitKey()的时长来控制视频播放速度(相当于控制每一帧的时间)每读完一帧,
cap.read()都会自动指向下一帧,所以视频会正常播放下去,read()的具体机制可以拆分为“解码已读帧”和“指向下一帧”两部分,详见下文的grab()和retrieve()
-
-
cv2.VideoCapture.grab()这个函数用于指向下一帧,返回一个bool来表示是否成功
xxxxxxxxxx11ret = cap.grab() -
cv2.VideoCapture.retrieve()这个函数用于解码
grab()捕获的视频帧,返回内容与read()一样xxxxxxxxxx11ret, frame = cap.retrieve() -
read()与grab(),retrieve()的关系read()可以理解为grab()与retrieve()的结合体:grab()指向下一帧,retrieve()解码并返回这一帧的图像
2.4 属性读取/修改
cv2.VideoCapture.get() 传入VideoCaptureProperties(propID)即可查看,可参考这个网址
cv2.VideoCapture.set() 传入propID和需要的数值就即可修改属性
propID可以是属性名称,也可以是代表它们的数字
xxxxxxxxxx 9 1 cv2.VideoCapture.get(propID) 2 cv2.VideoCapture.get(propID, value) 3
4 # 用两种方式查看帧宽度 5 cap.get(3) 6 cap.get(cv2.CAP_PROP_FRAME_WIDTH) 7
8 # 修改帧宽度 9 cap.get(cv2.CAP_PROP_FRAME_WIDTH, 114514) 常用的propID与其对应的数字如下:
xxxxxxxxxx 13 1 cv2.CAP_PROP_POS_MSEC 0 当前播放进度在视频文件中的位置(单位:ms) 2 cv2.CAP_PROP_FRAME_WIDTH 3 帧宽度 3 cv2.CAP_PROP_FRAME_HEIGHT 4 帧高度 4 cv2.CAP_PROP_FPS 5 FPS 5 cv2.CAP_PROP_FRAME_COUNT 7 总帧数 6
7 # 以下仅适用于相机 8 cv2.CAP_PROP_BRIGHTNESS 10 亮度 9 cv2.CAP_PROP_CONTRAST 11 对比度 10 cv2.CAP_PROP_SATURATION 12 饱和度 11 cv2.CAP_PROP_HUE 13 色相 12 cv2.CAP_PROP_GAIN 14 白平衡提升 13 cv2.CAP_PROP_EXPOSURE 15 曝光度
2.5 保存视频
不是捕获!是保存!
3. 善后工作(释放资源)
3.1 cv2.destroyAllWindows()
这个屑函数的功能如下:
close all the windows
de-allocate any associated memory usage
一般来说不涉及内存资源调用的简单程序不需要这一行,操作系统会take care of everything
然而它还是很重要啊啊啊啊啊!!!
3.2 cv2.destroyWindow()
可以定向关闭某个窗口,释放相关资源
xxxxxxxxxx 1 1 cv2.destroyWindow('window_name')
3.3 cv2.VideoCapture.release()
如果说有一个cv2.VideoCapture()类的对象cap,那就能用典中典的cap.release()
这个屑函数的功能是在程序结束时候释放视频流,比如已经打开的视频文件/已经初始化的摄像头
4. 绘图函数
WARNING:绘图函数均会直接影响原图
4.1 空手搓图
如前文所言,OpenCV的图片数据是以numpy数组的形式储存的,所以...
xxxxxxxxxx 2 1 # 一张512*512的bgr格式的黑色uint8图片 2 img = np.zeros((512, 512, 3), np.uint8)
4.2 直线&矩形&多边形
-
直线
cv2.line()xxxxxxxxxx51cv2.line(src_img, pt1, pt2, color, thickness)2# src_img 画布图像3# pt1/2 线的两个端点的坐标4# color 线的色彩,BGR格式5# thickness (Optional)线宽,默认为1 -
矩形
cv2.rectangle()xxxxxxxxxx61cv2.rectangle(src_img, upper_l, lower_r, thickness)2# src_img 画布图像3# upper_l 矩形左上点坐标4# lower_r 矩形右下点坐标5# color 线的色彩,BGR格式6# thickness (Optional)线宽,默认为1,若为-1则是"填充" -
多边形
cv2.polylines()xxxxxxxxxx121# 画多边形需要指定一系列顶点坐标,格式必须是int322pts = np.array([[10, 5], [50, 10], [70, 20], [20, 30]], np.int32)3# 然后需要reshape成一个n*1*2的三维矩阵4# reshape的第一个参数是新多出来的z-axis,该值是-1,表明这个维度的长度是通过后面的两个维度计算出来的(顶点数量不确定)5pts = pts.reshape((-1, 1, 2))67cv2.polylines(src_img, [pts], isClosed, color, thickness)8# src_img 画布图像9# pts 处理过的顶点坐标10# isClosed 图像是否闭合11# color 线的色彩,BGR格式12# thickness (Optional)线宽,默认为1,若为-1则是"填充"
4.3 圆形&椭圆形
OpenCV中的原点在左上角,坐标轴方向是正常的
-
圆形
cv2.circle()xxxxxxxxxx61cv2.circle(src_img, center, radius, color, thickness)2# src_img 画布图像3# center 圆心坐标4# radius 半径5# color 线的色彩,BGR格式6# thickness (Optional)线宽,默认为1,若为-1则是"填充" -
椭圆形
cv2.ellipsexxxxxxxxxx91cv2.ellipse(src_img, center, x_y_axis, rot_angle, start_angle, end_angle, color, thickness)2# src_img 画布图像3# center 椭圆圆心4# x_y_axis x/y轴长度5# rot_angle 椭圆整体旋转角度6# start_angle 椭圆启始角度(从+x-axis算)7# end_angle 椭圆结束角度(正方向为clockwise)8# color 线的色彩,BGR格式9# thickness (Optional)线宽,默认为1,若为-1则是"填充"
4.4 文字
cv2.putText() - 添加文字的函数
xxxxxxxxxx 10 1 font = cv2.FONT_HERSHEY_SIMPLEX # 常用字体 2 cv2.putText(src_img, 'text', posit, font, size, color, thickness, lineType=cv2.LINE_AA) 3 # src_img 画布图像 4 # text 文本内容 5 # posit 起始坐标(左下角) 6 # font 字体 7 # size 文本大小 8 # color 线的色彩,BGR格式 9 # thickness (Optional)文本粗细,默认为1 10 # lineType 线的类型,一般选择cv2.LINE_AA(抗锯齿线型)