个人学习整理,欢迎指正!
实验版本
python版本:3.6.13
opencv版本:2.4.9
1.opencv简介
官网
- http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_tutorials.html
- https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_tutorials.html
- http://docs.opencv.org/2.4/genindex.html
opencv安装
#环境设置
D:\python;
D:\python\Lib\site-packages;
D:\python\Scripts;
#模块下载
pip install opencv-python
pip install numpy #需要安装numpy模块
#测试是否下载成功
import cv2
print(cv2.__Version__)
#2.4.9
2.图像读取,显示,保存
基本流程:图像读取,窗口创建,图像显示,图像保存,资源释放
图像读取
path = 'image\gezi.jpg' #图像路径
#图像读取
img = cv2.imread(path,flags=1) # flags=0读取图像为单通道的灰度图,1为BRG格式的三通道图像
print(type(img)) # <class 'numpy.ndarray'>
print(img.shape) # (1005, 1200, 3) h*w*c
path:注意路径格式用\或/,同时路径中不要有中文
如果路径是错误的,opencv是不会提示的,但是读取的图像是None
采取异常处理
if img is None: #img==None是错误的
print('read img error')
cv2.imread(path,flags=1)中flags参数
- cv2.IMREAD_COLOR:默认参数,读入一副彩色图片,忽略alpha通道.宏定义1
- cv2.IMREAD_GRAYSCALE:读入灰度图片。宏定义0
- cv2.IMREAD_UNCHANGED:顾名思义,读入完整图片,包括alpha通道。宏定义-1
创建窗口
cv2.namedWindow(winname: Any, flags: int = …)
cv2.namedWindow('image',cv2.WINDOW_GUI_NORMAL)
- 第一个参数,表示窗口名称,传入字符串即可
- 第二个参数,窗口显示方式,取值如下
flags参数
- cv2.WINDOW_AUTOSIZE:窗口大小不可以改变
- cv2.WINDOW_FREERATIO:窗口大小自适应比例
- cv2.WINDOW_KEEPRATIO: 窗口大小保持比例
- cv2.WINDOW_GUI_EXPANDED:显示色彩变成暗色
图像显示
#图像显示
cv2.imshow('image',img)
- 第一个参数,设置需要显示的窗口名称
- 第二个参数,填写需要显示的图像
如果之前没用cv2.namedWindow创建image窗口,那么会自动调用cv2.namedWindow创建窗口。
图像保存
#图像保存
cv2.imwrite('save.png',img)
- 第一个参数,设置保存的文件名,需填写后缀,如"1.bmp"
- 第二个参数,要保存的Mat类型图像数据
- 第三个参数,表示特定格式保存的参数编码,一般采用默认值不填写
相同路径会替换原有的。
资源释放
#资源释放
cv2.destroyAllWindows()
cv2.destroyWindow() #销毁指定窗口,参数填窗口名称
窗口等待
用于窗口停留展示,不然窗口会一闪而过结束程序。
key = cv2.waitKey(delay=100)
if key == ord('s'):
print('save image')
结束条件:delay时间结束,或键盘触发(key接受键盘按键ASCII码)。delay = 0为无限等待。
完整代码
import cv2
import numpy as np
print(cv2.__version__)
path = 'image\gezi.jpg' #图像路径
#图像读取
img = cv2.imread(path,flags=1) # flags=0读取图像为单通道的灰度图,1为BRG格式的三通道图像
print(type(img)) # <class 'numpy.ndarray'>
print(img.shape) # (1005, 1200, 3) h*w*c
#窗口创建
cv2.namedWindow('image',flags=0) #flags可以调节窗口的大小
#图像显示
cv2.imshow('image',img)
#图像保存
cv2.imwrite('save.png',img)
key = cv2.waitKey(delay=0)
#资源释放
cv2.destroyAllWindows()
其他补充
cv2读取的图像是numpy格式的,所以numpy的操作是适用的。
- img.shape[0] 获取图像行数(高度)
- img.shape[1] 获取图像列数(宽度)
- img.shape[2] 获取图像通道数
- img.size 获取总的像素个数(宽度x高度x通道数)
- img.dtype 获取图像的数据类型。uint8 (0-255)
有一点需要注意的是在用numpy的方法进行操作后,要注意dtype是否还是uint8,如果不是需要调用astype(np.uint8)进行转换。
3.摄像头视频读取、写入
VideoCapture类提供了从摄像机或视频文件捕获视频的C++接口。
基本流程:创建视频接口,捕获视频帧,视频播放,视频保存,资源释放
创建视频接口
方式a:
filename = 'gamevideo.mp4'
cap = cv2.VideoCapture()
cap.open(filename)
方式b:
filename = 'gamevideo.mp4'
cap = cv2.VideoCapture(filename)
以上这两种打开的视频可以是本地,也可以是网络视频filename = "http://www.laganiere.name/bike.avi"
方式c:从摄像头获取视频
dev = 0
cap = cv2.VideoCapture(dev)
捕获视频帧
读取视频要加异常判断
方式a:
if cap.isOpened():
print('打开失败')
方式b:在读取时进行判断
ret,frame = cap.read()
if ret == False:
print('ret is false')
ret,frame = cap.read(),frame就是每一帧的画面。
视频播放
while True:
ret,frame = cap.read()
if ret == False:
print('ret is false')
break
cv2.imshow('frame',frame)
cv2.waitKey(1000//24)
注意在视频显示的时候需要延时,1s24帧。
视频保存
保存每一帧构成视频。
fourcc = cv2.VideoWriter_fourcc(*'mp4v') #自定义视频编解码器
out = cv2.VideoWriter('ouput.mp4',fourcc,20.0,(w,h)) #创建保存视频类
out.write(frame)#写入帧
cv2.VideoWriter()
- 第一个参数,保存路径
- 编码器。不同格式的视频,编码器不同
- 20 帧率
- 视频的size需要和写入帧的size一致,这里要注意时w,h不是h,w,shape返回的时值h在前。
资源释放
cap.release() #关闭视频文件
完整代码
import cv2
import numpy as np
print(cv2.__version__)
#
filename = 'gamevideo.mp4'
cap = cv2.VideoCapture(filename)
# dev = 0
# cap = cv2.VideoCapture(dev)
if not cap.isOpened():
print('打开失败')
ret,frame = cap.read()
if ret == False:
print('ret is false')
fourcc = cv2.VideoWriter_fourcc(*'mp4v') #自定义视频编解码器
h,w = frame.shape[:-1]
out = cv2.VideoWriter('ouput.mp4',fourcc,20.0,(w,h))
while True:
ret,frame = cap.read()
if ret == False:
print('ret is false')
break
cv2.imshow('frame',frame)
out.write(frame[...,::-1])
cv2.waitKey(1000//24)
#资源释放
cap.release() #关闭视频文件
cv2.destroyAllWindows()
其他功能
- open()—打开视频文件或者摄像头
- isOpened()–判断读取视频文件是否正确,正确返回true
- release()—关闭视频流文件
- grab()—抓取下一帧的视频文件或设备
- retrieve()—解码并返回视频帧
- get()—返回指定视频类的相关参数信息
- set()—设置类信息的一个属性
- cv2.CAP_PROP_POS_FRAMES #当前帧位置
- cv2.CAP_PROP_FRAME_COUNT #视频总帧数
- cv2.CAP_PROP_FPS #视频帧率
- fourcc = vd.get(cv2.CAP_PROP_FOURCC) #获取视频编解码器
4.颜色空间
在opencv中用元组表示颜色。如BGR(0,255,255,alpha)表示黄色,alpha是透明度一般用不到就不给。
5.基本绘图函数
创建背景
#创建黑底
bg = np.zeros((500,500,3),dtype=np.uint8)
cv2.imshow('bg',bg)
cv2.waitKey()
这里需要三通道,不然后面绘图函数的颜色是出不来的,只有黑和白。
绘制直线
bg = np.zeros((500,500,3),dtype=np.uint8)
l = cv2.line(bg,(100,100),(200,200),color=(0,255,0),thickness=2)
cv2.imshow('bg',bg)
cv2.waitKey()
cv2.line(img, pt1, pt2, color[, thickness[, lineType[, shift]]]) → img
- img,背景图
- pt1,直线起点坐标
- pt2,直线终点坐标
- color,当前绘画的颜色。如在BGR模式下,传递(255,0,0)表示蓝色画笔。灰度图下,只需要传递亮度值即可。
- thickness,画笔的粗细,线宽。若是-1表示画封闭图像,如填充的圆。默认值是1.
- lineType,线条的类型
- shift:中心坐标和半径值中的小数位数。
绘制圆
bg = np.zeros((500,500,3),dtype=np.uint8)
cir = cv2.circle(bg,(250,250),100,(0,255,0),thickness=-1)
cv2.imshow('bg',bg)
cv2.waitKey()
cv2.circle(img, center, radius, color[, thickness[, lineType[, shift]]])
- img,背景图
- center:圆心位置
- radius:圆的半径
- color:圆的颜色
- thickness:圆形轮廓的粗细(如果为正)。负厚度表示要绘制实心圆。
- lineType: 圆边界的类型。
- shift:中心坐标和半径值中的小数位数。
thickness=-1
thickness>0
绘制矩形
bg = np.zeros((500,500,3),dtype=np.uint8)
rec = cv2.rectangle(bg,(100,100),(200,200),(0,255,0))
cv2.imshow('bg',bg)
cv2.waitKey()
(100,100),(200,200)分别是左上角和右下角的坐标。
添加文字
bg = np.zeros((500,500,3),dtype=np.uint8)
cv2.putText(bg,'Opencv',(100,100),cv2.FONT_HERSHEY_SIMPLEX,1,(0,255,0),1)
cv2.imshow('bg',bg)
cv2.waitKey()
cv2.putText(bg,‘Opencv’,(100,100),cv2.FONT_HERSHEY_SIMPLEX,1,(0,255,0),1)
- 背景
- 要添加的文字。中文不能正常显示,需要自己写函数实现。
- 文字的位置,左下角坐标。
- 字体的类型
- 字体大小。数值越大,字体越大
- 字体颜色
- 字体粗细,越大越粗
6.OpenCV界面事件操作
鼠标
cv2.setMouseCallback(windowName, onMouse [, param]) -> None
- windowName控件放在哪个窗口
- onMouse 鼠标操作的回调函数。有鼠标操作会自动调用这个回调函数
def onmouse(event,x,y,flags,*param):
print(x,y,param)
#创建背景
bg = np.full((500,500),fill_value=255,dtype=np.uint8)
cv2.imshow('bg',bg)
#创建鼠标事件
cv2.setMouseCallback('bg',onmouse,0)
回调函数onmouse(event,x,y,flags,*param)的输入参数最好是这样的。
- event鼠标动作对应的宏定义。
- x,y鼠标的位置
- flags键盘+鼠标的一些操作
- param不定长参数,说明用户可以传回多个数据
Event:
#define CV_EVENT_MOUSEMOVE 0 //滑动
#define CV_EVENT_LBUTTONDOWN 1 //左键点击
#define CV_EVENT_RBUTTONDOWN 2 //右键点击
#define CV_EVENT_MBUTTONDOWN 3 //中键点击
#define CV_EVENT_LBUTTONUP 4 //左键放开
#define CV_EVENT_RBUTTONUP 5 //右键放开
#define CV_EVENT_MBUTTONUP 6 //中键放开
#define CV_EVENT_LBUTTONDBLCLK 7 //左键双击
#define CV_EVENT_RBUTTONDBLCLK 8 //右键双击
#define CV_EVENT_MBUTTONDBLCLK 9 //中键双击
flags:
#define CV_EVENT_FLAG_LBUTTON 1 //左鍵拖曳
#define CV_EVENT_FLAG_RBUTTON 2 //右鍵拖曳
#define CV_EVENT_FLAG_MBUTTON 4 //中鍵拖曳
#define CV_EVENT_FLAG_CTRLKEY 8 //(8~15)按Ctrl不放事件
#define CV_EVENT_FLAG_SHIFTKEY 16 //(16~31)按Shift不放事件
#define CV_EVENT_FLAG_ALTKEY 32 //(32~39)按Alt不放事件
参考: Opencv函数setMouseCallback鼠标事件响应
滑动条操作
createTrackbar(trackbarName, windowName, value, count, onChange) -> None
- trackbarname-----滚动条名称
- winname-----滚动条所依附的窗口名称(namedWindow创建)
- value—滑块初始位置
- count—滑动块最大位置,默认最小为0
- onChange----指向回调函数的指针,原型必须为func(x)。其中x为滑块所处的位置
def onChange(x):
print(x)
cv2.createTrackbar('woshihuakuai','bg',10,255,onChange)
7.对比度亮度调整与通道分离合并
对比度亮度调整:
数学公式
- 参数f(x)表示原图像像素
- 参数g(x)表示输出图像像素
- 参数a(a>0),被称为增益(gain), 通常用来控制图像的对比度
- 参数b通常被称为偏置(bias), 通常用来控制图像的亮度
path = r'datas\testimage.png'
contrast = 100*0.1
b = 50
img = cv2.imread(path,0)
for i in range(0,img.shape[0]):
for j in range(0,img.shape[1]):
bright = img[i,j]*contrast+b
if bright>255:
bright = 255
img[i,j] = bright
溢出保护:确保像素值为整数,且当灰度值大于255时,强转为255
通道分离和合并
分离
b,g,r = cv2.split(img)
合并
dst = cv2.merge([b,g,r])
8.图像基本运算
掩码操作
ROI感兴趣区域
roi = img[200:500,200:500]
注意ROI参数顺序y1, y2, x1, x2
掩码
- mask—(掩码)—是一个8位单通道图像(灰度图/二值图)
- 掩码某个位置如果为0,则在此位置上的操作不起作用
- 掩码某个位置如果不为0,则在此位置上的操作会起作用
- 可以用来提取不规则ROI
图像算数运算
要求运算对象的size和type一样
图像加法
add(src1, src2[, dst[, mask[, dtype]]]) -> dst
- src1是第一张图片
- src2是第二张图片
- mask掩膜。
dst = cv2.add(img1,img2)
img1和img2shape必须相同,类型必须相同。
addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]]) -> dst
dst = cv2.addWeighted(img1,0.7,img2,0.3,0)
带权重的相加
图像减法
cv2.subtract()
cv2.absdiff()
乘除
图像逻辑运算
图像相与
bitwise_and(src1, src2[, dst[, mask]]) -> dst
- src1,第一个图像
- src2第二个图像
- dst输出的图像。可以选择不填,而采用主动接受的方式
bg = np.zeros((500,500,3),dtype=np.uint8)
rect = cv2.rectangle(bg.copy(),(100,100),(400,400),(255,255,255),thickness=-1)
cv2.imshow('rect',rect)
cir = cv2.circle(bg.copy(),(250,250),180,(255,255,255),thickness=-1)
cv2.imshow('cir',cir)
dst = cv2.bitwise_and(rect,cir)
cv2.imshow('dst',dst)
注意copy
src1
src2
dst
取像素点为(255,255,255)的交集
逻辑相或
cv2.bitwise_or
逻辑异或
cv2.bitwise_xor
9.图像几何变换
图像缩放
resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) -> dst
- src: 输入图像
- dst: 输出图像
- dsize: Size类型,指定输出图像大小,如果它等于0,由下式计算:dsize = Size(round(fx * src.cols), round(fy * src.rows))
- fx: 沿水平方向的缩放系数,默认值0,等于0时,由下式计算:(double)dsize.width/src.cols.<src.cols矩阵的列数>
- fy: 沿垂直方向的缩放系数,默认值0,等于0时,由下式计算:(double)dsize.height/src.rows.<src.rows矩阵的行数>
- interpolation: 用于指定插值方式,默认为cv2.INTER_LINEAR (线性插值)
interpolation
- INTER_NEAREST 最近邻插值
- INTER_LINEAR 双线性插值(默认设置,放大图像使用–快)
- INTER_AREA 使用像素区域关系进行重采样。 它可能是图像抽取的首选方法,因为它会产生无云纹理的结果。 但是当图像缩放时,它类似于INTER_NEAREST方法。(缩小图像推荐使用)
- INTER_CUBIC 4x4像素邻域的双三次插值(放大图像使用–慢)
- INTER_LANCZOS4 8x8像素邻域的Lanczos插值
resize_img = cv2.resize(img,dsize=(img.shape[0]//2,img.shape[1]//2),interpolation=cv2.INTER_AREA)
仿射
- 构建变化矩阵m,这个矩阵数据类型需要np.float32.
- 使用cv2.warpAffine()函数
移动
平移就是将对象换一个位置。如果你要沿(x,y)方向移动,移动的距离是(tx,ty),可以构建移动矩阵m.
使用numpy构建m
m = np.array([[1,0,50],[0,1,50]],dtype=np.float32)
warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) -> dst
- src – 输入图像
- M – 变换矩阵
- dsize – 输出图像的大小
- flags – 插值方法的组合(int 类型!,与上文interpolation同)
- borderMode – 边界像素模式(int 类型!)
- borderValue – (重点!)边界填充值; 默认情况下,它为0。
dst = cv2.warpAffine(img,m,dsize=img.shape[:-1])
旋转
- OpenCV没有提供直接旋转图像的函数,图像旋转可以用仿射变换来实现
- 使用cv2.getRotationMatrix2D()构建m矩阵
通过数学推导m:
opencv可以通过内置函数
getRotationMatrix2D(center, angle, scale) -> retval构建m - center旋转中心Ox,Oy
- angle旋转角度
- scale旋转后的缩放比例
M = cv2.getRotationMatrix2D((w/2, h/2), 90, 1) #中心点,旋转90度,不缩放
print(M.shape) #(2, 3)
如果不在进行后续变化了m取前二行即可(0,0,1是为了结果能进行后续仿射而特意添加的),所以打印出来m的shape是2*3。
旋转仿射
dst = cv2.warpAffine(img1, M, (w, h))
透视
图A,存在透视效果
我们的目的是把图像转换成图B
仿射的关键是构建M,那么这里的变换怎么构建M呢。我们可以去寻找4个点。
通过一些方法标注,变换前的4个点和变换后的4个点
src_p = np.float32([(13,66),(574,66),(55,380),(554,380)]) # x,y
dst_p = np.float32([(13,66),(574,66),(10,380),(576,380)]) # x',y'
利用getPerspectiveTransform(src, dst[, solveMethod]) -> retval反解出M,再使用仿射变化warpPerspective。
m = cv2.getPerspectiveTransform(src_p,dst_p)
dst = cv2.warpPerspective(scard,m,(w,h))
10.图像滤波
- 滤波实际上是信号处理的一个概念,图像可以看成一个二维信号,其中像素点灰度值得高低代表信号的强弱
- 高频:图像中变化剧烈的部分
- 低频:图像中变化缓慢,平坦的部分
- 根据图像高低频特性,设置高通和低通滤波器。高通滤波可以检测图像中尖锐、变化明显的地方(边缘),低通滤波可以让图像变得平滑,消除噪声干扰。
- 图像滤波是OpenCV图像处理的重要部分,在图像预处理方面应用广泛,图像滤波的好坏决定着后续处理的结果好坏
图像滤波函数方法:
6. 线性滤波:方框滤波、均值滤波、高斯滤波
7. 非线性滤波:中值滤波、双边滤波
邻域算子:利用给定像素周围的像素值决定此像素的最终输出值的一种算子
线性滤波
一种常用的邻域算子,像素输出取决于输入像素的加权和,如下图示:
锚点是中间位置。
线性滤波器输出像素g(i, j)是输入像素f(i+k, j+I)的加权和,其中h(k, l)我们称之为核,是滤波器的加权系数。基本上这就是CNN中的卷积操作。
方框滤波cv2.boxFilter()
kernel
当normalize为true时,方框滤波也就成了均值滤波。也就是说均值滤波是方框滤波归一化后的特殊情况。
boxFilter(src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]]) -> dst
- src要处理的图像
- ddepth: 输出图像的深度, -1代表使用原图像深度,即src.depth()链接: 图像表示的相关概念:图像深度、像素深度、位深的区别和关系
- ksize: Size类型表示内核大小,一般用Size(w,h)表示内核大小, Size(3,3)表示3x3的核大小
- anchor: 表示锚点(即被平滑的那个点), 默认值Point(-1, -1),表示锚点在核中心
- normalize: 默认值true, 标识符, 表示内核是否被归一化
- borderType: 图像像素边界模式,一般用默认值即可
dst = cv2.boxFilter(img,-1,(5,5),normalize=True)
效果对比
亮度变得均匀->模糊
均值滤波cv2.blur
均值滤波即方框滤波归一化特例,就是用邻域内像素均值来代替该点像素值,均值滤波在去噪的同时也破坏了图像细节部分
blur(src, ksize[, dst[, anchor[, borderType]]]) -> dst
- ksize: Size类型表示内核大小,一般用Size(w,h)表示内核大小, Size(3,3)表示3x3的核大小
- anchor: 表示锚点(即被平滑的那个点), 默认值Point(-1, -1),表示锚点在核中心
- borderType: 图像像素边界模式,一般用默认值即可
dst = cv2.blur(img,(3,3))
效果对比
高斯滤波cv2.GaussianBlur
高斯滤波器被称为最有用的滤波器,每个像素点都是由本身和邻域内的其他像素值经过加权平均后得到的, 加权系数越靠近中心越大, 越远离中心越小, 能够很好的滤除噪声。
GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) -> dst
- ksize: 高斯内核大小,一般用Size(w,h)表示内核大小, w, h可以不同, 但是必须为正奇数或者0, 由sigma计算得来
- sigmaX: 表示高斯函数在X方向上的标准偏差。概率论的,好像是可以置信度95%什么的,不懂,用到的时候去查资料吧
- sigmaY: 表示高斯函数在Y方向上的标准偏差, 若sigmaY=0, 就将它设置为sigmaX;
- borderType: 图像像素边界模式,一般用默认值即可
dst = cv2.GaussianBlur(img,(3,3),0)
效果对比
中值滤波cv2.medianBlur()
中值滤波是一种非线性滤波, 是用像素点邻域灰度值的中值代替该点的灰度值, 可以去除脉冲噪声和椒盐噪声。
什么是中值:
median({1,2,3,3,7,5,1,8})=3 排序后的中间那个值。如果中间有两个数可以取均值,或者取左侧or右侧随便一个值。
medianBlur(src, ksize[, dst]) -> dst
- ksize是int就可以了。
dst = cv2.medianBlur(img,3)
效果对比
双边滤波cv2.bilateralFilter()
双边滤波是一种非线性滤波, 是结合图像空间邻近度和像素值相似度的一种折中处理, 尽量在去噪同时保存边缘。
bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]]) -> dst
- d: 表示过滤过程中每个像素的邻域直径
- sigmaColor: 颜色空间滤波器sigma值, 值越大表面该像素邻域内有越广泛的颜色
会混到一起,产生较大的半相等颜色区域 - sigmaSpace: 坐标空间中滤波器的sigma值, 坐标空间的标准方差
- borderType: 图像像素边界模式,一般用默认值即可
dst = cv2.bilateralFilter(img,20,500,500)
效果对比
11.图像阈值化
- 图像阈值化是图像处理的重要基础部分, 应用很广泛, 可以根据灰度差异来分割图像不同部分
- 阈值化处理的图像一般为单通道图像(灰度图)
- 阈值化参数的设置可以使用滑动条来debug
- 阈值化处理易光照影响, 处理时应注意
两种主要的图像阈值化函数方法:
- 全局固定阈值:cv2.threshold()
- 局部自适应阈值:cv2.adaptiveThreshold()
全局固定阈值:cv2.threshold()
threshold(src, thresh, maxval, type[, dst]) -> retval, dst
- src: 单通道图像(灰度图或二值图,实际上3通道的BGR也不会报错的)
- dst: 输出图像要求和src一样的尺寸和类型
- thresh: 给定的阈值
- maxval:第五个参数设置为CV_THRESH_BINARY或CV_THRESH_BINARY_INV 阈值类型的最大值
- type:第五个参数是操作标志位。一般设置为CV_THRESH_BINARY或CV_THRESH_BINARY_INV。
返回值 - retval,设置的thresh或者是通过动态阈值自动获取的。常见错误,没用接收这个返回值
- dst,二值图
maxval
宏定义 | 像素值>thresh | 其他情况 |
---|---|---|
cv2.THRESH_BINARY =0 | maxval | 0 |
cv2.THRESH_BINARY_INV =1 上面取反 | 0 | maxval |
cv2.THRESH_TRUNC =2 | thresh | 当前灰度值 |
cv2.THRESH_TOZERO =3 | 当前灰度值 | 0 |
cv2.THRESH_TOZERO_INV =4 | 0 | 当前灰度值 |
辅助宏定义自动获取thresh
宏定义 | 功能 |
---|---|
cv2.THRESH_OTSU | 使用最小二乘法处理像素点,适合双峰图 |
cv2.THRESH_TRIANGLE | 使用三角算法处理像素点,适合单峰图 |
cv2.THRESH_MASK | / |
单峰图或者双峰图指的是灰度直方图。
这种适合
参考: CV2简单阈值函数:cv2.threshold()
cv2.THRESH_OTSU、cv2.THRESH_TRIANGLE是标志位,他们可以和其他参数,比如cv2.THRESH_BINARY一起使用。当使用cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE时,阈值是动态的,会根据计算结果返回相应的数值。
_,bingary = cv2.threshold(gray,120,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
效果对比
自适应阈值cv2.adaptiveThreshold()
对矩阵采用自适应阈值操作, 自适应阈值是根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值
adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) -> dst
参数基本同cv2.threshold(),注意返回值只有一个。
- adaptiveMethod: 指定自适应阈值算法, 可取值为cv2.ADAPTIVE_THRESH_MEAN_C 或cv2.ADAPTIVE_THRESH_GAUSSIAN_C
- blockSize: 用来计算阈值的邻域大小3, 5, 7,…必须要是奇数
- C: 减去平均或加权平均后的常数值
每个局部的阈值T(x,y)的计算
if adaptiveMethod== cv2.ADAPTIVE_THRESH_MEAN_C:
先求出块中的均值,再减掉C
if adaptiveMethod== cv2.ADAPTIVE_THRESH_GAUSSIAN_C:
先求出块中的加权和(gaussian), 再减掉C
adaptiveThreshold 将灰度图像变换到二值图像,采用下面公式:
thresholdType= cv2.THRESH_BINARY :
thresholdType= cv2.THRESH_BINARY _INV:
代码
binary = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
效果对比
cv2.adaptiveThreshold()具有一定轮廓提取能力。
12.图像处理——形态学
从数学角度上来讲, 膨胀或腐蚀就是将图像(或区域)A与核B进行卷积。最大值池化和最小值池化
卷积核
构建核是进行膨胀和腐蚀的前提。核可以是任意大小和形状, 它有一个独立定义的参考点(锚点), 多数情况下, 核是一个小的中间带参考点的实心正方形或者圆盘, 可以看做是一个模板或掩码。
可使用cv2.getStructuringElement获得指定形状和尺寸的核。
getStructuringElement(shape, ksize[, anchor]) -> retval
- shape是形状。cv2.MORPH_RECT代表矩阵,cv2.MORPH_CROSS代表十字形,cv2.MORPH_ELLIPSE是椭圆形。
- ksize卷积核大小。
k1 = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
k1:
[[0 1 0]
[1 1 1]
[0 1 0]]
当然,可以使用numpy直接构建,但是要注意数据类型,自行实验。
膨胀
膨胀是求局部最大值的操做, 核B与图形卷积, 即核B覆盖的区域的像素点的最大值, 并把这个最大值复制给参考点指定的像素, 这样就会使图像中的高亮区域逐渐增长。
cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) -> dst
- src: 输入原图像(建议为二值图),BGR返回值就是一张灰度图。
- dst: 输出图像要求和src一样的尺寸和类型。一般不填,选择主动接收.
- kernel: 膨胀操作的核, 可由getStructuringElement构建。当为NULL时, 表示使用参考点位于中心的3x3的核。
- interations: 膨胀的次数
- anchor: 锚的位置, 默认值Point(-1,-1), 表示位于中心
- borderType: 边界模式, 一般采用默认值
- borderValue: 边界值, 一般采用默认值
#膨胀
k1 = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3)) #构建卷积核
dst_dilate = cv2.dilate(img_2v,k1,iterations=1)#img_2v为一张二值图
效果对比
腐蚀
腐蚀和膨胀相反, 是取局部最小值, 高亮区域逐渐减小, 如下图所示:
erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) -> dst
参数与膨胀一样,不做介绍。
k2 = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
dst_erosion = cv2.erode(img_2v,k2,iterations=1)
效果对比
其他形态学操作
开运算、闭运算、顶帽、黒帽、形态学梯度。这些操作都是基于膨胀和腐蚀实现的。
通过调用cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) -> dst来实现。
- src: 输入原图像
- op: 表示形态学运算的类型。
- kernel: 形态学运算内核, 若为NULL, 表示使用参考点位于中心的3x3内核, 一般使用getStruecuringElement函数获得
- anchor: 锚的位置, 默认值Point(-1,-1), 表示位于中心
- interations: 迭代使用函数的次数, 默认为1
- borderType: 边界模式, 一般采用默认值
- borderValue: 边界值, 一般采用默认值
参数也基本与膨胀和腐蚀一样,根据操作选择不同的op
开运算
先腐蚀后膨胀的过程, 开运算可以用来消除小物体(高亮度的), 在纤细点处分离物体, 并在平滑较大物体边界的同时不明显的改变其面积。op选择cv2.MORPH_OPEN
k = cv2.getStructuringElement(cv2.MORPH_CROSS,(4,4))
dst_open=cv2.morphologyEx(img_2v,cv2.MORPH_OPEN,k)
kernel的size是需要更具噪点的大小进行调整的。个人认为直接使用cv2.morphologyEx接口腐蚀和膨胀的核的大小是一样的,选择不同的核使用cv2.erode和cv2.dilate可能效果会更好。
效果对比
闭运算
闭运算是先膨胀后腐蚀的过程, 闭运算可以用来消除小型黑洞(黑色区域)。
op选择cv2.MORPH_CLOSE
k = cv2.getStructuringElement(cv2.MORPH_CROSS,(4,4))
dst_close=cv2.morphologyEx(img_2v,cv2.MORPH_CLOSE,k)
效果对比
形态学梯度
形态学梯度是膨胀图与腐蚀图之差, 以将团块(blob)边缘凸显出来, 可以用其来保留边缘轮廓。
op选择cv2.MORPH_GRADIENT
k = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
dst_gd = cv2.morphologyEx(img_2v,cv2.MORPH_GRADIENT,k)
效果对比:
顶帽
顶帽运算也被称为”礼帽”, 是开运算结果和原图像做差的结果, 可以用来分离比邻近点亮一些的斑块。
op是cv2.MORPH_TOPHAT
k = cv2.getStructuringElement(cv2.MORPH_CROSS,(4,4))
dst_th = cv2.morphologyEx(img_2v,cv2.MORPH_TOPHAT,k)
效果对比
黑帽
黑帽运算是原图像和开运算做差的结果, 可以用来分离比邻近点暗一些的斑块。
k = cv2.getStructuringElement(cv2.MORPH_CROSS,(4,4))
dst_b = cv2.morphologyEx(img_2v,cv2.MORPH_BLACKHAT,k)
效果对比
边缘检测
边缘信息变化比较大,梯度大,那么梯度大的地方就是边缘。
边缘检测可以提取图像重要轮廓信息, 减少图像内容, 可以用于分割图像、做特征提取等
边缘检测的一般步骤:
- 滤波----(滤出噪声対检测边缘的影响)
- 增强----(可以将像素邻域强度变化凸显出来—梯度算子)
- 检测----(阈值方法确定边缘)
常用的边缘检测算子:
- Sobel算子
- Canny算子
- Scharr算子
- Laplacian算子
Sobel算子
Sobel算子是一个主要用于边缘检测的离散微分算子, 它结合了高斯平滑和微分求导, 用来计算图像灰度函数的近似梯度。
Sobel边缘检测函数—cv2.Sobel()
Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) -> dst
- src: 输入原图像
- ddepth: 输出图像的深度
- dx: X方向上的差分阶数
- dy: Y方向上的差分阶数
- ksize: 默认值3, 表示Sobel核大小, 1,3,5,7.int,奇数
- scale: 计算导数值时的缩放因子, 默认值1, 表示不缩放
- delta(δ): 表示在结果存入目标图之前可选的delta值, 默认值0
- borderType: 边界模式, 一般采用默认
- dst: 输出图像要求和src一样的尺寸和类型
ddepth有如下:
位深越大,小数点后的位数越多,计算越准确,速度越慢。
img_sb = cv2.Sobel(binary,cv2.CV_16S,1,0,ksize=3).astype(np.uint8)
or
img_sb = cv2.Sobel(binary,cv2.CV_16S,1,0,ksize=3)
img_sb = cv2.convertScaleAbs(img_sb)
dx = 1,dy = 0
注意到此时更加关注的是纵向的线
注意这里是需要进行类型转换的。
G=0.5*Gx+0.5Gy
img_sb_x = cv2.Sobel(binary,cv2.CV_16S,1,0,ksize=3)
img_sb_y = cv2.Sobel(binary,cv2.CV_16S,0,1,ksize=3)
img_sb_x = cv2.convertScaleAbs(img_sb_x)
img_sb_y = cv2.convertScaleAbs(img_sb_y)
img_sb = cv2.addWeighted(img_sb_x,0.5,img_sb_y,0.5,0)
效果图
Canny
Canny相较于Sobel最大的区别在于采用双阈值。
- 如果某一像素的幅值大于高阈值,该像素被认为是边缘像素点,别保留。
- 如果某一像素的幅值小于低阈值,该像素被认为不是边缘像素点,不别保留。
- 如果某一像素的幅值位于高低阈值之间,该像素仅仅在连接到一个边缘点的时候被保留。
Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) -> edges
- src: 输入原图像(一般为单通道8位图像)
- dst: 输出边缘图像要求和src一样的尺寸和类型(单通道)
- threshold1: 低阈值(用于边缘连接)
- threshold2: 高阈值(控制边缘初始段)。(推荐高低阈值比值在2:1到3:1之间)
- apertureSize: 表示Sobel算子孔径大小, 默认值3
- L2gradient: 计算图像梯度幅值的标识
canny = cv2.Canny(img,180,90)
效果图
Laplacian算子
Laplacian算子是n维欧几里德中的一个二阶微分算子。
数学定义:
Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]]) -> dst
- src: 输入原图像(单通道8位图像)
- dst: 输出边缘图像要求和src一样的尺寸和通道数
- ddepth: 目标图像的深度
- Ksize: 用于计算二阶导数的滤波器孔径大小, 须为正奇数, 默认值1
- scale: 可选比例因子, 默认值1
- delta: 可选参数δ, 默认值0
- borderType: 边界模式, 一般采用默认值
代码
lap = cv2.Laplacian(img,-1,ksize=3)
效果图
Laplacian边缘效果似乎更加的细腻。
小结
拉普拉斯和Canny边缘检测后不需要类型转换。
基尔霍夫变换及应用
霍夫变换(Hough Transform)是图像处理中的一种特征提取技术, 该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换的结果。
霍夫变换在OpenCV中主要分两种:
- 霍夫线变换—检测直线(线段)
- 霍夫圆变换—检测圆
主要使用的函数:
- cv2.HoughLines()—标准霍夫变换、多尺度霍夫变换
- cv2.HoughLinesP()—累计概率霍夫变换
- cv2.HoughCricles()—霍夫圆变换
霍夫线变换
霍夫线变换是一种寻找直线的方法, 一般在使用霍夫变换前首先将图像进行边缘检测处理, 一般霍夫变换的输入为边缘二值图。
OpenCV支持三种不同的霍夫线变换, 包括:
- 标准霍夫变换(SHT)------cv2.HoughLines()函数
- 多尺度霍夫变换(MSHT)------是SHT在多尺度下的一个变种------cv2.HoughLines()函数
- 累计概率霍夫变换(PPHT)------是SHT的改进, 在一定范围内进行霍夫变换(减少计算时间和运算量)------cv2.HoughLinesP()函数
cv2.HoughLines()
- src: 输入原图像(一般为8位单通道二值图像)
- lines: 经过霍夫变换后检测线条的输出矢量, 每一条线由两个元素的矢量(ρ, Θ)表示, 其中ρ是离坐标原点的距离, Θ是弧度线条旋转角度(0表示垂直线, π/2度表示水平线)
- rho: 以像素为单位的距离精度, 另一种表述方式是直线搜索时的进步尺寸的单位半径
- theta: 以弧度为单位的角度精度, 另一种表述方式是直线搜索时的进步尺寸的角度单位
- threshold: 累加平面的阈值参数, 即识别某部分为一条直线时它在累加平面中必须达到的值, 大于阈值threshold的线段才可以被检测通过并返回到结果中
- srn: 默认值0, 对于多尺度的霍夫变换, 这是第三个参数进步尺寸rho的除数距离
- stn: 默认值0, 对于多尺度霍夫变换, 表示单位角度theta
直方图计算及绘制
直方图是对数据进行统计的一种方法, 可以直观表现图像某属性的数值(频率)分布情况, 包括灰度直方图、RGB直方图等
相关概念及函数:
- dims: 需要统计得特征的数目, 如只统计灰度值—dims=1, 统计RGB值—dims=3
- bins: 每个特征空间子区域段的数目,也可称为组距(简单理解为直方图分成几个柱子组成)
- range: 每个特征空间的取值范围, 如灰度值取值范围[0, 255]
- 计算直方图函数: cv2.calcHist()
cv2.calcHist()
- images: 源图像, 输入数组(或数组集), 需要有相同的深度和尺寸
- channels: 需要统计通道的索引, 表示要使用哪个通道或多个通道(属性)
- mask: 可选的操作掩码, 如果不为空, 则必须为8位, 并且与图像有一样大小尺寸
- hist: 输出的目标直方图
- dims: 需要计算的直方图维度, 必须是正数
- histSize: 存放每个维度的直方图尺寸的数组, 即bins
- ranges:像素值的范围,一般为[0,255]表示0~255
- uniform: 直方图是否均匀的标识符, 默认值true
- accumulate: 累计标识符, 默认值false, 若为true, 直方图在配置阶段不会被清零
除了mask,其他四个参数都要带[ ]号.
代码
import cv2
import numpy as np
import matplotlib.pyplot as plt
filename = r'datas\chepai.jpg'
img = cv2.imread(filename,0)
hist = cv2.calcHist([img],[0],None,[256],[0,255])
#hist是一个shape为(256,1)的数组,表示0-255每个像素值对应的像素个数,下标即为相应的像素值
#plot一般需要输入x,y,若只输入一个参数,那么默认x为range(n),n为y的长度
plt.plot(hist,color = 'b')
plt.hist(img.ravel(),256,[0,256])
plt.show()
cv2.waitKey()
效果对比
模板匹配及应用
原理
模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。模板匹配不是基于直方图的, 而是通过在输入图像上滑动图像块(模板)同时比对相似度, 来对模板和输入图像进行匹配的一种方法。
输入图像:
模板:
实际中,模板的size必须要和输入图像中的匹配图像大小一致。
应用
- 目标查找定位
- 运动物体跟踪
代码:
cv2.matchTemplate()
- image: 待搜索图像(大图)
- templ: 搜索模板, 需和原图一样的数据类型且尺寸不能大于源图像
- result: 比较结果的映射图像, 其必须为单通道, 32位浮点型图像, 如果原图(待搜索图像)尺寸为W x H, 而templ尺寸为 w x h, 则result尺寸一定是(W-w+1)x(H-h+1)。
- method: 指定的匹配方法
method | 定义 |
---|---|
cv2.TM_SQDIFF | 平方差匹配法(最好匹配0,越小越好) |
cv2.TM_SQDIFF_NORMED | 归一化平方差匹配法(最好匹配0) |
cv2.TM_CCORR - | 相关匹配法(最坏匹配0) |
cv2.TM_CCORR_NORMED | 归一化相关匹配法(最坏匹配0) |
cv2.TM_CCOEFF | 系数匹配法(最好匹配1,越大越好) |
cv2.TM_CCOEFF_NORMED | 化相关系数匹配法(最好匹配1) |
上述模式越往下越复杂,花费时间越多,准确率应该是越高,根据需要选择。
import sys
import cv2
import numpy as np
filename = r'data\01.bmp'
templfile = r'testdata\templ0.bmp'
src = cv2.imread(filename)
templ = cv2.imread(templfile)
res = cv2.matchTemplate(src,templ,cv2.TM_SQDIFF)
print(res)
# cv2.imshow('src',src)
# cv2.imshow('templ',templ)
# cv2.imshow('res',res)
cv2.waitKey()
output
模板每次移动一个像素,每次计算相似度,相似度将会保存在result矩阵中。相似度的结果并不是保存在中心点,而是保存在左上角。
假设这个模式越靠近0说明越相似,那么我们需要从res这个矩阵中,找到其中最小的元素的位置,从而可以对应到原图src中的位置。
代码:
minMaxLoc(src[, mask]) -> minVal, maxVal, minLoc, maxLoc
- src: 输入原图像, 单通道图像
- minVal: 返回最小值的指针, 若无需返回, 则设置0
- maxVal: 返回最大值的指针, 若无需返回, 则设置0
- minLoc: 返回最小位置的指针,若无需返回, 则设置0
- maxLoc:返回最大位置的指针,若无需返回, 则设置0
- mask: 可选的掩码操作
import sys
import cv2
import numpy as np
filename = r'data\01.bmp'
templfile = r'testdata\templ0.bmp'
src = cv2.imread(filename)
templ = cv2.imread(templfile)
res = cv2.matchTemplate(src,templ,cv2.TM_SQDIFF)
min_value,max_value,min_loc,max_loc = cv2.minMaxLoc(res)
print(min_value,max_value,min_loc,max_loc)
cv2.waitKey()
output:
print(min_value,max_value,min_loc,max_loc)
72.0 1287406848.0 (365, 483) (1319, 1891)
那么(365, 483)差不多就是原图中匹配到的位置。为什么说是差不多呢,因为res和src还位置不是一样对应的,差多少取决于你模板的大小。
轮廓查找和绘制
原理
轮廓可以简单认为成将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度,提取轮廓就是提取这些具有相同颜色或者灰度的曲线,或者说是连通域,轮廓在形状分析和物体的检测和识别中非常有用。
程序如何保存这些曲线呢?保存轮廓点的集合,大致就可以保存下轮廓曲线。
注意:
- 为了更加准确,要使用二值化图像。在寻找轮廓之前,要进行阈值化处理或者 Canny 边界检测
- 在OpenCV 中,查找轮廓就像在黑色背景中找白色物体。你应该记住,
要找的物体应该是白色而背景应该是黑色。
代码
cv2.findContours()-----查找轮廓
- image: 输入图像, 8位单通道图像(一般为二值图)
- contours: 检测到的轮廓, 每个轮廓存储为一个点向量(一个列表,len(contours)是轮廓的个数)。可以不传,用返回值接受代替。
- hierarchy: 可选的输出向量, 包含图像的拓扑信息。其作为轮廓数量的表示, 包含了许多元素, 每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0]~hierarchy[i][3], 分别表示后一轮廓、前一轮廓、父轮廓、内嵌轮廓的索引编号, 如果没有对应项, 设置为负数
- mode: 轮廓检索模式
- method: 轮廓的近似方法, 取值见图2
- offset: 每个轮廓的可选偏移量, 默认值Point()
mode | 意义 |
---|---|
cv2.RETR_EXTERNAL=0 | 表示只检测最外层轮廓(一般选用) |
cv2.RETR_LIST=1 | 提取所有轮廓并放置在list中, 轮廓不建立等级关系 |
cv2.RETR_CCOMP=2 | 提取所有轮廓并组织为双层结构 |
cv2.RETR_TREE =3 | 提取所有轮廓并重新建立网状轮廓结构 |
method | 意义 |
---|---|
cv2.CHAIN_APPROX_SIMPLE | 存储所有轮廓点。 |
cv2.CHAIN_APPROX_NONE | 压缩存储。对于线段,只储存线段的两个端点。对四边形只用4个点就可以确定。 |
cv2.drawContours()---------绘制轮廓
- image: 目标图像, Mat类型对象即可
- contours: 所有的输入轮廓, 每个轮廓存储为一个点向量
- contourIdx: 轮廓绘制指示变量(索引), 若为负值, 则表示绘制所有轮廓
- color: 绘制轮廓的颜色
- thickness: 轮廓线条的粗细, 默认值1, 如果为负值, 则绘制轮廓内部, 可选宏CV_FILLED
- lineType: 线条类型, 默认值8
- hierarcy: 可选的层次结构信息, 默认值noArray()
- maxLevel: 表示用于绘制轮廓的最大等级, 默认值INT_MAX
- offset: 可选的轮廓偏移参数, 默认值Point()
filename = r'data\01.bmp'
src = cv2.imread(filename)
gray = cv2.cvtColor(src,cv2.COLOR_BGR2GRAY)
if src is None:
print('check the filenam')
sys.exit()
#二值化
_,binary = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
#轮廓查找
cnts ,hir = cv2.findContours(binary,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
cv2.namedWindow('binary',cv2.WINDOW_NORMAL)
cv2.imshow('binary',binary)
#轮廓绘制
cv2.drawContours(src,cnts,-1,(0,0,255),2) # -1绘制所有轮廓,0绘制cnts中第一个轮廓...
cv2.namedWindow('src',cv2.WINDOW_NORMAL)
cv2.imshow('src',src)
cv2.waitKey()
效果展示
红色就是绘制的轮廓。
#打印轮廓数量
print(hir.shape)
(1, 541, 4)
说明上图一共提取了514个轮廓,噪点太多。
#打印每个轮廓包含的点的数量
for cnt in cnts:
print(cnt.shape)
(1, 1, 2)
(15, 1, 2)
(1, 1, 2)
(4, 1, 2)
(2, 1, 2)
(1, 1, 2)
(1, 1, 2)
(5, 1, 2)
(12, 1, 2)
(6, 1, 2)
(36, 1, 2)
...
通过点的数量,也可以筛选出你需要的轮廓。
注意:
- 二值化图像往往需要滤波,避免上图的毛刺。
- 调用绘制轮廓函数时,会直接改变原有的src,如果src后面需要,可以传入src.copy()
- 轮廓提取在二值图,绘制轮廓在原图三通道更好,颜色突出。二值图只有黑白二色。
使用特定的形状的轮廓包围
在实际应用中, 经常会有将检测到的轮廓用多边形表示出来的需求, 提取包围轮廓的多边形也方便我们做进一步分析, 轮廓包围主要有一下几种:
- 轮廓外接矩形
- 轮廓最小外接矩形(旋转)
- 轮廓最小包围圆形
- 轮廓拟合椭圆
- 轮廓逼近多边形曲线
轮廓外接矩形—cv2.boundingRect()
boundingRect(array) -> retval
- points: 输入的二维点集。就是我们上面轮廓查中轮廓列表中的一个元素
- 返回值: Rect类矩形对象(x,y,w,h)。这样我们可以画出矩形
dst = src.copy()
#轮廓外接矩形
for cnt in cnts:
if cnt.shape[0]>500:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(dst,(x,y),(x+w,y+h),(0,0,255),3)
通过点的数量,筛选目标。
设置合适的点的数量阈值,直到准确寻找到轮廓。
轮廓最小外接矩形—cv2.minAreaRect()
minAreaRect(points) -> retval
- points: 输入的二维点集
- 返回值: RotatedRect类矩形对象, 外接旋转矩形主要成员有center、size、 angle。(这个角度有可能需要90-angle或者其他转换一下才可以)
手上有center、size、 angle可以画出矩形了吗?可能可以,我们使用另外的方式,使用函数获取矩形的每一个点。
boxPoints(box[, points]) -> points
- box:center、size、 angle
box = cv2.boxPoints(res)
box = np.int0(box)
for cnt in cnts:
if cnt.shape[0]>500:
res = cv2.minAreaRect(cnt)
box = cv2.boxPoints(res)
box = np.int0(box)
cv2.drawContours(dst,[box],-1,(0,0,255),3)
由于获取的box是点集,那么我们可以使用cv2.drawContours进行绘制最小外接矩形。
效果图
最小外接圆
轮廓最小外接圆—cv2.minEnclosingCircle()
minEnclosingCircle(points) -> center, radius
- points: 输入的二维点集
- center: Point2f&类型的center, 圆的输出圆心
- radius: float&类型, 表示圆的输出半径
椭圆拟合
轮廓椭圆拟合—cv2.fitEllipse()
fitEllipse(points) -> retval
- points: 输入的二维点集, 可以填Mat类型或std::vector
- 返回值: RotatedRect类旋转矩形对象
逼近多边形曲线
cv2.approxPolyDP
- curve: 输入的二维点集或轮廓
- approxCurve: 多边形逼近的结果, 其类型和输入二维点集类型一致
- epsilon: 逼近的精度, 为原始曲线和近似曲线间的最大值
- closed: 如果其为真, 则近似的曲线为封闭曲线, 否则近似的曲线不封闭
轮廓属性
计算轮廓面积—cv2.contourArea()
contourArea(contour[, oriented]) -> retval
- contour: 输入的二维点集或轮廓
- oriented: 默认值false, 表示返回面积为绝对值, 否则带符号
- 返回值: double类型返回轮廓面积
#通过轮廓面积筛选轮廓
for cnt in cnts:
box_area = cv2.contourArea(cnt)
if box_area>2000:
cv2.drawContours(dst,cnt,-1,(0,0,255),3)
计算轮廓长度(周长或者曲线长度)
arcLength(curve, closed) -> retval
- curve: 输入的二维点集或轮廓
- colsed: 用于指示曲线是否封闭的标识符, 默认值true, 表示曲线封闭
- 返回值: double类型返回轮廓长度
颜色空间
HSV颜色空间
HSV颜色空间与人眼所看色彩较接近, 故常用于颜色
检测与识别。其中H(色调)、S(饱和度)、V(亮度)
H—不同的颜色(红色/绿色/蓝色)—范围: 0~360
S—颜色深浅(浅红/深红)—范围: 0.0~1.0
V—颜色亮暗(暗红/亮红)—范围: 0.0~1.0
OpenCV默认的HSV范围分别是:
H: 0~180, S: 0~255, V: 0~255
在opencv中,H、S和V仍然被放大到0-255直接,因此各个颜色的HSV取值如下。
颜色空间转换—cv2.cvtColor()
颜色区间范围删选—cv2.inRange()
inRange(src, lowerBound, upperbBound[, dst]) -> dst
- src: 输入原图或数组
- lowerb: 低边界或者颜色阈值
- upperb: 高边界或者颜色阈值
- dst: 输出目标图像, 需要和原图一样的size并且类型需为CV_8U
代码
import sys
import cv2
import numpy as np
filename = r'datas\chepai.jpg'
src = cv2.imread(filename)
if src is None:
print('check the filenam')
sys.exit()
# 颜色区间范围删选
#BGR2GRAY
hsv = cv2.cvtColor(src,cv2.COLOR_BGR2HSV)
l_v = np.array([100,43,46]) #HSV 低阈值
h_v = np.array([124,255,255]) #HSV 高阈值
mask = cv2.inRange(hsv,l_v,h_v)
cv2.namedWindow('mask',cv2.WINDOW_NORMAL)
cv2.imshow('mask',mask)
cv2.waitKey()
效果对比:
可以看到蓝色像素点被置为255.
通过调节阈值可以获得更好的效果,上图中阈值设置如下
l_v = np.array([100,43,46]) #HSV 低阈值
h_v = np.array([124,255,255]) #HSV 高阈值
我们看到原图中,车牌上方的蓝色是比较暗的,所以我们调高一下低阈值的V
l_v = np.array([100,43,170]) #HSV 低阈值
h_v = np.array([124,255,255]) #HSV 高阈值
效果
可以看到有了显著的改善。