pyopencv基础操作指南

2023-11-20

个人学习整理,欢迎指正!

实验版本

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参数

  1. cv2.IMREAD_COLOR:默认参数,读入一副彩色图片,忽略alpha通道.宏定义1
  2. cv2.IMREAD_GRAYSCALE:读入灰度图片。宏定义0
  3. cv2.IMREAD_UNCHANGED:顾名思义,读入完整图片,包括alpha通道。宏定义-1

创建窗口
cv2.namedWindow(winname: Any, flags: int = …)

cv2.namedWindow('image',cv2.WINDOW_GUI_NORMAL)
  1. 第一个参数,表示窗口名称,传入字符串即可
  2. 第二个参数,窗口显示方式,取值如下

flags参数

  1. cv2.WINDOW_AUTOSIZE:窗口大小不可以改变
  2. cv2.WINDOW_FREERATIO:窗口大小自适应比例
  3. cv2.WINDOW_KEEPRATIO: 窗口大小保持比例
  4. cv2.WINDOW_GUI_EXPANDED:显示色彩变成暗色

图像显示

#图像显示
cv2.imshow('image',img)
  1. 第一个参数,设置需要显示的窗口名称
  2. 第二个参数,填写需要显示的图像

如果之前没用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()

  1. 第一个参数,保存路径
  2. 编码器。不同格式的视频,编码器不同
  3. 20 帧率
  4. 视频的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()

其他功能

  1. open()—打开视频文件或者摄像头
  2. isOpened()–判断读取视频文件是否正确,正确返回true
  3. release()—关闭视频流文件
  4. grab()—抓取下一帧的视频文件或设备
  5. retrieve()—解码并返回视频帧
  6. get()—返回指定视频类的相关参数信息
  7. set()—设置类信息的一个属性
  8. cv2.CAP_PROP_POS_FRAMES #当前帧位置
  9. cv2.CAP_PROP_FRAME_COUNT #视频总帧数
  10. cv2.CAP_PROP_FPS #视频帧率
  11. fourcc = vd.get(cv2.CAP_PROP_FOURCC) #获取视频编解码器

4.颜色空间

在opencv中用元组表示颜色。如BGR(0,255,255,alpha)表示黄色,alpha是透明度一般用不到就不给。

5.基本绘图函数

创建背景

#创建黑底
bg = np.zeros((500,5003),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

  1. img,背景图
  2. pt1,直线起点坐标
  3. pt2,直线终点坐标
  4. color,当前绘画的颜色。如在BGR模式下,传递(255,0,0)表示蓝色画笔。灰度图下,只需要传递亮度值即可。
  5. thickness,画笔的粗细,线宽。若是-1表示画封闭图像,如填充的圆。默认值是1.
  6. lineType,线条的类型
  7. 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]]])

  1. img,背景图
  2. center:圆心位置
  3. radius:圆的半径
  4. color:圆的颜色
  5. thickness:圆形轮廓的粗细(如果为正)。负厚度表示要绘制实心圆。
  6. lineType: 圆边界的类型。
  7. 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)

  1. 背景
  2. 要添加的文字。中文不能正常显示,需要自己写函数实现。
  3. 文字的位置,左下角坐标。
  4. 字体的类型
  5. 字体大小。数值越大,字体越大
  6. 字体颜色
  7. 字体粗细,越大越粗

6.OpenCV界面事件操作

鼠标

cv2.setMouseCallback(windowName, onMouse [, param]) -> None

  1. windowName控件放在哪个窗口
  2. 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

  1. trackbarname-----滚动条名称
  2. winname-----滚动条所依附的窗口名称(namedWindow创建)
  3. value—滑块初始位置
  4. count—滑动块最大位置,默认最小为0
  5. 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

  1. src1是第一张图片
  2. src2是第二张图片
  3. 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

  1. src1,第一个图像
  2. src2第二个图像
  3. 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)

仿射

  1. 构建变化矩阵m,这个矩阵数据类型需要np.float32.
  2. 使用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

  1. src – 输入图像
  2. M – 变换矩阵
  3. dsize – 输出图像的大小
  4. flags – 插值方法的组合(int 类型!,与上文interpolation同)
  5. borderMode – 边界像素模式(int 类型!)
  6. 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,存在透视效果
图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.图像滤波

  1. 滤波实际上是信号处理的一个概念,图像可以看成一个二维信号,其中像素点灰度值得高低代表信号的强弱
  2. 高频:图像中变化剧烈的部分
  3. 低频:图像中变化缓慢,平坦的部分
  4. 根据图像高低频特性,设置高通和低通滤波器。高通滤波可以检测图像中尖锐、变化明显的地方(边缘),低通滤波可以让图像变得平滑,消除噪声干扰。
  5. 图像滤波是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.图像阈值化

  1. 图像阈值化是图像处理的重要基础部分, 应用很广泛, 可以根据灰度差异来分割图像不同部分
  2. 阈值化处理的图像一般为单通道图像(灰度图)
  3. 阈值化参数的设置可以使用滑动条来debug
  4. 阈值化处理易光照影响, 处理时应注意

两种主要的图像阈值化函数方法:

  1. 全局固定阈值:cv2.threshold()
  2. 局部自适应阈值:cv2.adaptiveThreshold()

全局固定阈值:cv2.threshold()

threshold(src, thresh, maxval, type[, dst]) -> retval, dst

  1. src: 单通道图像(灰度图或二值图,实际上3通道的BGR也不会报错的)
  2. dst: 输出图像要求和src一样的尺寸和类型
  3. thresh: 给定的阈值
  4. maxval:第五个参数设置为CV_THRESH_BINARY或CV_THRESH_BINARY_INV 阈值类型的最大值
  5. type:第五个参数是操作标志位。一般设置为CV_THRESH_BINARY或CV_THRESH_BINARY_INV。
    返回值
  6. retval,设置的thresh或者是通过动态阈值自动获取的。常见错误,没用接收这个返回值
  7. 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

  1. shape是形状。cv2.MORPH_RECT代表矩阵,cv2.MORPH_CROSS代表十字形,cv2.MORPH_ELLIPSE是椭圆形。
  2. 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

  1. src: 输入原图像(建议为二值图),BGR返回值就是一张灰度图。
  2. dst: 输出图像要求和src一样的尺寸和类型。一般不填,选择主动接收.
  3. kernel: 膨胀操作的核, 可由getStructuringElement构建。当为NULL时, 表示使用参考点位于中心的3x3的核。
  4. interations: 膨胀的次数
  5. anchor: 锚的位置, 默认值Point(-1,-1), 表示位于中心
  6. borderType: 边界模式, 一般采用默认值
  7. 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来实现。

  1. src: 输入原图像
  2. op: 表示形态学运算的类型。
  3. kernel: 形态学运算内核, 若为NULL, 表示使用参考点位于中心的3x3内核, 一般使用getStruecuringElement函数获得
  4. anchor: 锚的位置, 默认值Point(-1,-1), 表示位于中心
  5. interations: 迭代使用函数的次数, 默认为1
  6. borderType: 边界模式, 一般采用默认值
  7. 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 高阈值

效果
在这里插入图片描述
可以看到有了显著的改善。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

pyopencv基础操作指南 的相关文章

  • 保存为 HDF5 的图像未着色

    我目前正在开发一个将文本文件和 jpg 图像转换为 HDF5 格式的程序 用HDFView 3 0打开 似乎图像仅以灰度保存 hdf h5py File Sample h5 img Image open Image jpg data np
  • Django 的内联管理:一个“预填充”字段

    我正在开发我的第一个 Django 项目 我希望用户能够在管理中创建自定义表单 并向其中添加字段当他或她需要它们时 为此 我在我的项目中添加了一个可重用的应用程序 可在 github 上找到 https github com stephen
  • 如何使用 opencv.omnidir 模块对鱼眼图像进行去扭曲

    我正在尝试使用全向模块 http docs opencv org trunk db dd2 namespacecv 1 1omnidir html用于对鱼眼图像进行扭曲处理Python 我正在尝试适应这一点C 教程 http docs op
  • 处理 Python 行为测试框架中的异常

    我一直在考虑从鼻子转向行为测试 摩卡 柴等已经宠坏了我 到目前为止一切都很好 但除了以下之外 我似乎无法找出任何测试异常的方法 then It throws a KeyError exception def step impl contex
  • 使用 Python 从文本中删除非英语单词

    我正在 python 上进行数据清理练习 我正在清理的文本包含我想删除的意大利语单词 我一直在网上搜索是否可以使用像 nltk 这样的工具包在 Python 上执行此操作 例如给出一些文本 Io andiamo to the beach w
  • Pandas 日期时间格式

    是否可以用零后缀表示 pd to datetime 似乎零被删除了 print pd to datetime 2000 07 26 14 21 00 00000 format Y m d H M S f 结果是 2000 07 26 14
  • 如何使用 Pandas、Numpy 加速 Python 中的嵌套 for 循环逻辑?

    我想检查一下表的字段是否TestProject包含了Client端传入的参数 嵌套for循环很丑陋 有什么高效简单的方法来实现吗 非常感谢您的任何建议 def test parameter a list parameter b list g
  • Pandas Merge (pd.merge) 如何设置索引和连接

    我有两个 pandas 数据框 dfLeft 和 dfRight 以日期作为索引 dfLeft cusip factorL date 2012 01 03 XXXX 4 5 2012 01 03 YYYY 6 2 2012 01 04 XX
  • 如何在不丢失注释和格式的情况下更新 YAML 文件 / Python 中的 YAML 自动重构

    我想在 Python 中更新 YAML 文件值 而不丢失 Python 中的格式和注释 例如我想改造 YAML 文件 value 456 nice value to value 6 nice value 界面类似于 y yaml load
  • pyspark 将 twitter json 流式传输到 DF

    我正在从事集成工作spark streaming with twitter using pythonAPI 我看到的大多数示例或代码片段和博客是他们从Twitter JSON文件进行最终处理 但根据我的用例 我需要所有字段twitter J
  • 加快网络抓取速度

    我正在使用一个非常简单的网络抓取工具抓取 23770 个网页scrapy 我对 scrapy 甚至 python 都很陌生 但设法编写了一个可以完成这项工作的蜘蛛 然而 它确实很慢 爬行 23770 个页面大约需要 28 小时 我看过scr
  • javascript 是否有等效的 __repr__ ?

    我最接近Python的东西repr这是 function User name password this name name this password password User prototype toString function r
  • Jupyter Notebook 找不到 Python 模块

    不知道发生了什么 但每当我使用 ipython 氢 原子 或 jupyter 笔记本时都找不到任何已安装的模块 我知道我安装了 pandas 但笔记本说找不到 我应该补充一点 当我正常运行脚本时 python script py 它确实导入
  • Python3 在 DirectX 游戏中移动鼠标

    我正在尝试构建一个在 DirectX 游戏中执行一些操作的脚本 除了移动鼠标之外 我一切都正常 是否有任何可用的模块可以移动鼠标 适用于 Windows python 3 Thanks I used pynput https pypi or
  • 不同编程语言中的浮点数学

    我知道浮点数学充其量可能是丑陋的 但我想知道是否有人可以解释以下怪癖 在大多数编程语言中 我测试了 0 4 到 0 2 的加法会产生轻微的错误 而 0 4 0 1 0 1 则不会产生错误 两者计算不平等的原因是什么 在各自的编程语言中可以采
  • 如何使用原始 SQL 查询实现搜索功能

    我正在创建一个由 CS50 的网络系列指导的应用程序 这要求我仅使用原始 SQL 查询而不是 ORM 我正在尝试创建一个搜索功能 用户可以在其中查找存储在数据库中的书籍列表 我希望他们能够查询 书籍 表中的 ISBN 标题 作者列 目前 它
  • Python:XML 内所有标签名称中的字符串替换(将连字符替换为下划线)

    我有一个格式不太好的 XML 标签名称内有连字符 我想用下划线替换它 以便能够与 lxml objectify 一起使用 我想替换所有标签名称 包括嵌套的子标签 示例 XML
  • 实现 XGboost 自定义目标函数

    我正在尝试使用 XGboost 实现自定义目标函数 在 R 中 但我也使用 python 所以有关 python 的任何反馈也很好 我创建了一个返回梯度和粗麻布的函数 它工作正常 但是当我尝试运行 xgb train 时它不起作用 然后 我
  • 使用for循环时如何获取前一个元素? [复制]

    这个问题在这里已经有答案了 可能的重复 Python 循环内的上一个和下一个值 https stackoverflow com questions 1011938 python previous and next values inside
  • 更改 Tk 标签小部件中单个单词的颜色

    我想更改 Tkinter 标签小部件中单个单词的字体颜色 我知道可以使用文本小部件来实现与我想要完成的类似的事情 例如使单词 YELLOW 显示为黄色 self text tag config tag yel fg clr yellow s

随机推荐

  • 量化投资学习-36:选股的基本方式

    1 选择的总原则 1 强者恒强 热点龙头 2 超跌反弹 星空雷达 2 策略总原则 1 主策略 1 2 辅策略 N 3 候选指标 趋势 支撑线 压力线 短期趋势通道 长期趋势通道 布林线 震荡 MACD底特征 KDJ震荡超卖 9转序列低9 能
  • 4大主流CPU处理器技术架构

    推荐阅读 浅谈linux 内核网络 sk buff 之克隆与复制 深入linux内核架构 进程 线程 了解Docker 依赖的linux内核技术 导读 RISC 精简指令集计算机 是一种执行较少类型计算机指令的微处理器 起源于80年代的MI
  • 【C++】STL中list容器内部元素的移动和交换

    文章目录 前言 一 list是什么 二 元素移动 1 插入 删除 2 切除 拼接 三 元素交换 1 元素值交换 2 元素 节点 交换 总结 前言 提示 list insert list erase list splice std iter
  • 【ESP32】反复重启

    ESP32开发 反复重启 串口输出如下所示 rst 0xc SW CPU RESET boot 0x13 SPI FAST FLASH BOOT configsip 188777542 SPIWP 0xee clk drv 0x00 q d
  • 使用 AJAX,局部刷新 GridView 进行数据绑定的简单实现

    很多用户都有这样需求 比如 点击按钮 刷新 GridView 中的数据 而不是这个页面刷新 使用简单的 XMLHttpRequest 就可以直接实现 具体代码如下 ASPX 代码 lt
  • C语言实现随机发纸牌

    C语言实现随机发纸牌 为避免重复发牌 设二维数组sign 4 13 记载是否发过纸牌 其中行下表表示花色 列下标表示点数 设字符串指针数组card n 存储随机发的n张纸牌 例如card 0 梅花2 按照以下方法以此发出每一张牌 首先产生一
  • Python异常捕获

    在 Python 中 try 和 except 语句用于捕获和处理异常 except 子句可以用来捕获不同类型的异常 Exception 这是 Python 中所有异常的基类 可以捕获几乎所有异常类型 ValueError 当函数收到不适当
  • 使用css 动画实现,水波纹的效果

    每日鸡汤 每个你想要学习的瞬间都是未来的你向自己求救 需求 实现水波纹动画效果 要求中心一个圆点 然后有3个圈 一圈一圈的向里面缩小 说实话我第一个想到了给3个圈设置不同的宽高 然后设置动画0 100 一次缩小宽高 但是 我转念一想 我是不
  • Intellisense and NAnt .build files in VS.NET

    Intellisense and NAnt build files in VS NET This has been blogged about before here and there but I wanted to share it a
  • 最大k乘积问题--动态规划

    问题 问题描述 设x是一个n位十进制整数 如果将x划分为k段 则可得到k个整数 这k个整数的乘积称为x的一个k乘积 试设计一个算法 对于给定的x和k 求出x的最大k乘积 编程任务 对于给定的x和k 编程计算x的最大k 乘积 示例 Sampl
  • 深入理解数据库事务(超详细)

    一 事务的介绍 事务是一组操作的集合 事务会把所有操作作为一个整体一起向系统提交或撤销操作请求 即这些操作要么同时成功 要么同时失败 二 事务的基本操作 2 1 事务操作方式一 例子 转账场景 张三向李四转账 1 查询张三账户余额 sele
  • mac下搭建cocos2d-x3.2开发环境

    1 软件 Xcode Ant apache ant 1 9 4 bin tar gz Jdk jdk 8u45 macosx x64 dmg 有的mac系统上没有自带 Ndk android ndk r10d darwin x86 64 b
  • 如何成为一名优秀的开发人员?

    如何成为一名优秀的开发人员 1 你不需要知道一切 没有人知道所有的细节 2 存在错误对于学习至关重要 3 谷歌搜索是一项合法的技能 4 尝试思考而不是编码 5 慎重对待教程 1 你不需要知道一切 没有人知道所有的细节 作为一名开发人员 你可
  • C++STL总结笔记(二)——仿函数(函数对象)

    文章目录 一 概念 总结 一 概念 仿函数又称函数对象 即重载了函数调用运算符 的类的对象 优势 1 仿函数对象的内部可以有自己的状态 可以实现一些其他的功能 2 函数对象可以作为参数进行传递 当仿函数类内重载的返回值是bool类型被称为谓
  • Python爬虫网易云音乐--JS逆向-补充笔记

    课程地址 https www bilibili com video BV1Mi4y147Yb up主省略掉的 用报错信息来一一回溯的方式把js文件补全的过程 主要把握的几个点 把以下部分代码找到粘贴到js中即可补全并顺利运行 CryptoJ
  • 《数据库系统概论》重点整理

    Word文档链接 https wenku baidu com view 063d7656f9c75fbfc77da26925c52cc58bd69088 第一章 数据模型的三个要素 数据结构 数据操作 完整性约束 数据库领域常用的逻辑模型
  • minikube start启动集群失败Unable to find image gcr.io/k8s-minikube/kicbase:v0.0.10

    配套视频教程 minikube带你玩转k8s集群 1 minikube启动集群 minikube start vm driver docker image mirror country cn image repository registr
  • Intel CPU5种不同的CPU频率标定方式

    作者 cici xiang 链接 https www zhihu com question 271509706 answer 364246338 来源 知乎 著作权归作者所有 商业转载请联系作者获得授权 非商业转载请注明出处 Intel C
  • G - Ginger的NB数(二分)

    G Ginger的NB数 SDUT OnlineJudge include
  • pyopencv基础操作指南

    个人学习整理 欢迎指正 实验版本 python版本 3 6 13 opencv版本 2 4 9 1 opencv简介 官网 http docs opencv org 3 0 beta doc py tutorials py tutorial