OpenCV笔记
基础
import cv2 as cv import numpy as np import matplotlib.pyplot as plt
图像
-
读取图像
img = cv.imread(filename [,flags]) """ filename: 工作路径 flags: 标志,阅读图像的方式 cv.IMREAD_COLOR:彩色图像(BGR),忽略透明度,默认标志,1 cv.IMREAD_GRAYSCALE: 灰度模式,0 cv.IMREAD_UNCHANGED: 包括alpha通道,-1 """
-
显示图像
cv.imshow(winname, mat) """ winname: 窗口名称 mat: 图像 """ 常与cv.waitKey()和cv.destroyAllWindows()搭配使用
-
保存图像
cv.imwrite(filename,img [,params]) """ filename:文件名 img: 图像需要保存 """
-
可结合
Matplotlib
视频
-
捕获视频
cap = cv.VideoCapture(filename [,apiPreference]) cap = cv.VideoCapture(index [,apiPreference]) """ filename: 视频文件名称、图像序列、视频流URL index: 视频捕获设备ID,默认打开默认摄像头 """ cap.isOpened() # 判断是否初始化是真的;否则,使用cap.open()打开 ret, frame = cap.read() # ret为布尔值,如果读取帧,则为真
-
保存视频
out = cv.VideoWriter(filename,fourcc,fps,frameSize [,isColor]) """ filename: 文件名 fourcc:4字符编解码器代码用于压缩帧,fourcc = cv.VideoWriter_fourcc(*'XVID') fps:视频流的帧率 frameSize:视频帧大小 isColor:灰度编码为0,否则颜色 """ out.write(frame)
绘制函数
-
线
img = cv.line(img, pt1, pt2, color[, thickness[, lineType[
, shift ] ] ] ) """ img: 图片 pt1:第一个点 pt2:第二个点 color:线条颜色 thickness:线条粗细 lineType:线条类型 shift:点坐标中的小数位数 """ img = np .zeros ( ( 512 , 512 , 3 ) , np .uint8 ) img = cv .line (img , ( 0 , 0 ) , ( 511 , 511 ) , [ 255 , 0 , 0 ] , 5 ) -
矩形
img = cv.rectangle( img,pt1,pt2,color [,thickness [,lineType [,shift]]]) """ img: 图片 pt1:第一个点 pt2:对角顶点 color:矩形颜色 thickness:矩形线条粗细,负则填充 lineType:线条类型 shift:点坐标中的小数位数 """ img = cv.rectangle(img, (384, 0), (510, 128), [0, 255, 0], 3)
-
圆
img = cv.circle(img,center,radius,color [,thickness [,lineType [,shift]]]) """ img: 图片 center: 圆心 radius:半径 color:圆颜色 thickness:圆线条粗细,负则填充 lineType:线条类型 shift:中心坐标和半径中的小数位数 """ img = cv.circle(img, (447, 63), 63, (0, 0, 255), -1)
-
椭圆
img = cv.ellipse(img,center,axes,angle,startAngle,endAngle,color [,thickness [,lineType [,shift]]]) """ img: 图片 center: 椭圆中心 axes:长短半轴,(长半轴,短半轴) angle:旋转角,负角度(顺时针) startAngle:椭圆弧起始角度(以焦点) endAngle:椭圆弧终止角度(以焦点) color:椭圆颜色 thickness:椭圆线条粗细,负则填充 lineType:线条类型 shift:中心坐标和轴值的小数位数 """ img = cv.ellipse(img, (256, 256), (100, 50), 0, 45, 225, 255, -1)
-
多边形
img = cv.polylines(img, pts, isClosed, color[, thickness[, lineType[, shift]]]) """ img: 图片 pts: 多边形的数组 isClosed:折线是否闭合,假则最后一点不与源点相连 color:折线颜色 thickness:折线线条粗细 lineType:线条类型 shift:顶点坐标中的小数位数 """ pts = np.array([[10, 5], [20, 30], [70, 20], [50,10]], np.int32) pts = pts.reshape((-1, 1, 2)) img = cv.polylines(img, [pts], True, (0, 255, 255))
鼠标绘画
-
鼠标事件
events = [i for i in dir(cv) 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']
-
鼠标回调函数
cv.setMouseCallback(winname, onMouse, userdata=0) """ winname:窗口名 onMouse:鼠标事件的回调函数 userdata:传递给回调函数的可选参数 """ def draw_circle(event, x, y, flags, param): """鼠标回调函数""" if event == cv.EVENT_LBUTTONDBLCLK: cv.circle(img, (x, y), 100, (255, 0, 0), -1) img = np.zeros((512, 512, 3), np.uint8) cv.namedWindow('image') cv.setMouseCallback('image', draw_circle) while(1): cv.imshow('image', img) if cv.waitKey(20) == 27: break cv.destroyAllWindows()
轨迹栏作调色板
-
创建轨迹栏
cv.createTrackbar(trackbarname, winname, value, count, onChange=0, userdata=0) """ trackbarname:轨迹栏名称 winname:用于创建轨迹栏的父级窗口名称 value:初始值 count:滑块最大位置,最小始终为0 onChange:每次滑块更改位置时要调用的函数名 userdata:传递给回调的用户数据。它可用于处理轨迹栏事件而无需使用全局变量 """
-
获取轨迹栏位置
retval = cv.getTrackbarPos(trackbarname, winname) """ trackbarname:轨迹栏名称 winname:用于创建轨迹栏的父级窗口名称 """
def nothing(x): pass # 创建黑画窗口 img = np.zeros((300, 512, 3), np.uint8) cv.namedWindow('image') # 创建改变色彩的轨迹栏 cv.createTrackbar('R', 'image', 50, 255, nothing) cv.createTrackbar('G', 'image', 0, 255, nothing) cv.createTrackbar('B', 'image', 0, 255, nothing) # 创建
开关 switch = '0: OFF\n1: ON' cv.createTrackbar(switch, 'image', 0, 1, nothing) while(1): cv.imshow('image', img) k = cv.waitKey(1) if k == 27: break # 获得各轨迹栏的位置 r = cv.getTrackbarPos('R', 'image') g = cv.getTrackbarPos('G', 'image') b = cv.getTrackbarPos('B', 'image') s = cv.getTrackbarPos(switch, 'image') if s == 0: img[:] = 0 else: img[:] = [b, g, r] cv.destroyAllWindows()
核心操作
图像的基本操作
-
读取和修改像素值
px = img[100, 100] # [157, 166, 200] # 获得蓝色像素 blue = img[100, 100, 0] # 157 # 修改像素值 img[100, 100] = [255, 255, 255] # 更好的获取和修改方法 img.item(10, 10, 2) # 59 获取红色像素 img.itemset((10, 10, 2), 100) # 修改红色像素值
-
读取图像属性
img.shape # (342, 548, 3) 行、列、通道 img.size # 562248 所有像素值 img.dtype # uint8 数据类型
-
图像感兴趣区域(ROI)
roi = img[280: 340, 330: 390]
-
分离合并图像通道
# 分离 b, g, r = cv.split(img) # 很费时的操作 b = img[:,:,0] # 合并 img = cv.merge((b, g, r))
-
生成图像边框(填充 Padding)
dst = cv.copyMakeBorder(src, top, bottom, left, right, borderType[, dst[, value]]) """ src: 源图像 top,bottom,left,right:上下左右宽度像素值 borderType: cv.BORDER_CONSTANT:恒定值 cv.BORDER_REFLECT: fedcba|abcdefgh|hgfedcb cv.BORDER_REFLECT_101 or cv.BORDER_DEFAULT: gfedcb|abcdefgh|gfedcba cv.BORDER_REPLICATE:aaaaaa|abcdefgh|hhhhhhh cv.BORDER_WRAP:cdefgh|abcdefgh|abcdefg value:颜色值,如果类型为cv.BORDER_CONSTANT """
图像的算术运算
-
图像相加
cv.add
为饱和运算(建议使用),numpy
为取模运算
x = np.uint8([250]) y = np.uint8([100]) print(cv.add(x, y)) # 255 建议使用 print(x + y) # 94
-
图像融合(Image Blending)
# 加权相加以达到图片融合 dst = cv.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]]) """ src1、src2:相加数组 alpha:1的权重 beta:2的权重 gamma:偏置 """ dst = cv.addWeighted(img1, 0.7, img2, 0.3, 0)
-
逐位运算(Bitwise Operations)
- 通常在提取部分图片时使用
- 包括
cv.bitwise_and
:dst(I)=src1(I)∧src2(I)if mask(I)≠0
dst(I)=src1(I)∧src2if mask(I)≠0
,通道数与src1
相同dst(I)=src1∧src2(I)if mask(I)≠0
,通道数与src2
相同
cv.bitwise_or
:dst(I)=src1(I)∨src2(I)if mask(I)≠0
dst(I)=src1(I)∨src2if mask(I)≠0
,通道数与src1
相同dst(I)=src1∨src2(I)if mask(I)≠0
,通道数与src2
相同
cv.bitwise_not
:dst(I)=¬src(I)
cv.bitwise_xor
:dst(I)=src1(I)⊕src2(I)if mask(I)≠0
dst(I)=src1(I)⊕src2if mask(I)≠0
,通道数与src1
相同dst(I)=src1⊕src2(I)if mask(I)≠0
,通道数与src2
相同
dst = cv.bitwise_and(src1, src2[, dst[, mask]]) """ src1,src2:数组或标量 mask:掩膜 """ img1 = cv.imread('messi5.jpg') img2 = cv.imread('opencv-logo-white.png') # 创建ROI rows, cols, channels = img2.shape roi = img1[0: rows, 0: cols] # 创建掩膜和反向掩膜 img2gray = cv.cvtColor(img2, cv.COLOR_BGR2GRAY) ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY) mask_inv = cv.bitwise_not(mask) # 在ROI过滤出标志位置(黑色) img1_bg = cv.bitwise_and(roi,roi,mask = mask_inv) # 取出标志 img2_fg = cv.bitwise_and(img2,img2,mask = mask) # 合并 dst = cv.add(img1_bg,img2_fg) img1[0:rows, 0:cols ] = dst cv.imshow('res',img1) cv.waitKey(0) cv.destroyAllWindows()
性能测试和提升技术
-
性能测试
e1 = cv.getTickCount() # 需要执行的代码 e2 = cv.getTickCount() time = (e2 - e1) / cv.getTickFrequency()
-
优化默认
cv.useOptimized() # True cv.setUseOptimized(False) # 关闭优化
-
性能优化技术
- 尽量避免使用循环
- 尽量矢量化,
Numpy
和OpenCV
都对其进行了优化 - 利用缓存一致性
- 尽量少复制数组
图像处理
改变色彩空间
-
改变色彩空间
- 有150多种色彩空间变换方法,比较常用的是
BGR<->GRAY
和BGR<->HSV
HSV
,色相Hue∈[0, 179]
,饱和度Saturation∈[0, 255]
,明度Value∈[0, 255]
dst = cv.cvtColor(src, code[, dst[, dstCn]]) """ src:8位或16位无符号或单精度浮点图像 code:色彩空间变换码 dstCn:最终图片通道数,默认自动 """ code = [i for i in dir(cv) if i.startswith('COLOR_')]
- 有150多种色彩空间变换方法,比较常用的是
-
物体追踪
- 获取视频的每一帧图像
- 转化成
HSV
色彩空间 - 阈值过滤
HSV
图像 - 可以追踪物品,进行其他操作
cap = cv.VideoCapture(0) while True: # 获取一帧图片 _, frame = cap.read() # 将BGR-->HSV(色相,饱和度,明度) hsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV) # 在HSV中,定义蓝色的范围 lower_blue = np.array([110, 50, 50]) upper_blue = np.array([130, 255, 255]) # 过滤HSV图像获得蓝色掩膜 mask = cv.inRange(hsv, lower_blue, upper_blue) # bitwise_and 掩膜和原始图像 res = cv.bitwise_and(frame, frame, mask=mask) cv.imshow('frame', frame) cv.imshow('mask', mask) cv.imshow('res', res) if cv.waitKey(5) == 27: break cap.release() cv.destroyAllWindows()
- 如何找到
HSV
的值用于追踪
green = np.uint8([[[0, 255, 0]]]) hsv_green = cv.cvtColor(green, cv.COLOR_BGR2HSV) print(hsv_green) # [[[ 60 255 255]]]
图像的几何变换(Geometric Transformations)
-
缩放(Scaling)
cv.INTER_AREA
:用于缩小的插值法cv.INTER_CUBIC
:用于放大的插值法,慢cv.INTER_LINEAR
:用于放大的插值法,默认
dst = cv.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) """ src:输入图片 dsize:输出图片尺寸,如果为0,则dsize = Size(round(fx*src.cols), round(fy*src.rows)) fx:水平缩放系数,如果为0,(double)dsize.width/src.cols fy:垂直缩放系数,如果为0,(double)dsize.height/src.rows interpolation:插值方法 """ img = cv.imread('messi5.jpg') res = cv.resize(img, None, fx=2, fy=2, interpolation = cv.INTER_CUBIC) # 或 height, width = img.shape[: 2] res = cv.resize(img, (2*width, 2*height), interpolation = cv.INTER_CUBIC)
-
平移(Translation)
- 平移
(tx,ty)
,则平移矩阵M=[[1, 0, tx], [0, 1, ty]]
dst(x,y) = src(M11x + M12y + M13, M21x + M22y + M23)
dst = cv.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) """ src:输入图片 M:2 x 3变换矩阵 dsize:输出尺寸(宽,高) flags:结合了插值方法和逆变换,默认线性插值 """ img = cv.imread('messi5.jpg', 0) rows,cols = img.shape M = np.float32([[1, 0, 100], [0, 1, 50]]) dst = cv.warpAffine(img, M, (cols, rows)) cv.imshow('img', dst) cv.waitKey(0) cv.destroyAllWindows()
- 平移
-
旋转(Rotation)
- 旋转中心
center
和旋转角度θ
(正为逆时针),则旋转矩阵为M=[[α, β, (1−α)⋅center.x−β⋅center.y], [−β, α, β⋅center.x+(1−α)⋅center.y]]
,且α=scale⋅cosθ,β=scale⋅sinθ
retval = cv.getRotationMatrix2D(center, angle, scale) """ 得到仿射矩阵 center:旋转中心 angle:旋转角度,正值为逆时针 scale:缩放系数 """ img = cv.imread('messi5.jpg', 0) rows,cols = img.shape M = cv.getRotationMatrix2D(((cols-1)/2.0, (rows-1)/2.0), 90, 1) dst = cv.warpAffine(img, M, (cols, rows))
- 旋转中心
-
仿射变换(Affine Transformation)
- 找到原图中的三点位置和对应仿射图中的三点位置,形成仿射矩阵
[[x′i], [y′i]] = map_matrix · [[xi], [yi], 1]
,dst(i)=(x′i,y′i), src(i)=(xi, yi),i=0, 1, 2
retval = cv.getAffineTransform(src, dst) """ 得到仿射矩阵 src:原图中三角形顶点坐标 dst:目标图中三角形顶点坐标 """ img = cv.imread('drawing.png') rows,cols,ch = img.shape pts1 = np.float32([[50, 50],[200, 50],[50, 200]]