2 Star 12 Fork 2

会动耳de小李 / OpenCV

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

OpenCV

一、基础操作

窗口的展示

窗口

创建显示窗口

  1. namedWindow('name',cv2.window_nomal) 创建窗口

  2. imshow('name',img) 显示窗口

  3. destoryAllWindows() 释放窗口

  4. resizeWindow('name', 800, 600) 设置大小

  5. waitKey(0) 显示时间ms

# 02.win_api.py
import cv2

cv2.namedWindow('new', cv2.WINDOW_AUTOSIZE)
cv2.resizeWindow('new', 640, 480)
cv2.imshow('new', 0)

key = cv2.waitKey(0)
if(key == 'q'):
    exit()

cv2.destroyAllWindows()

图像/视频的加载

读取/保存图片

  1. 加载图片 imread(path,flag)
  2. 保存图片 imwrite(name,img) img是mat类型
#03.reading_writing.py
import cv2
from sqlalchemy import true

cv2.namedWindow('img', cv2.WINDOW_NORMAL)
img = cv2.imread(
    'G:\\BaiduNetdiskWorkspace\\Code From VSCode\OpenCV\\timg_.jpg')

while true:
    cv2.imshow('img', img)

    key = cv2.waitKey(0)

    # if key == 'q':
    if key & 0xff == ord('q'):  # 退出
        break
    elif key & 0xff == ord('s'):
        cv2.imwrite(
            'G:\\BaiduNetdiskWorkspace\\Code From VSCode\OpenCV\\save.png', img)
    else:
        print(key, 'otherkeys')
cv2.destroyAllWindows()

从摄像头/视频文件读取视频帧

  1. 捕捉摄像头 VideoCapure(0, api)
  2. 读取视频 VideoCapure('path')
    • cap.read() 将数据一帧一帧采集处理,返回两个值:
    • 第一个为状态值,读到为True;
    • 第二个为视频帧
  3. 释放资源 cap.release()
  4. 判断摄像头是否打开 isOpened()
#04.VideoCap.py
import cv2
from sqlalchemy import true

# 创建窗口
cv2.namedWindow('video', cv2.WINDOW_GUI_NORMAL)
# 设置大小
cv2.resizeWindow('video', 800, 600)

# 获取摄像头设备
cap = cv2.VideoCapture(0)

# 读取视频
# cap = cv2.VideoCapture(
# 'G:\\BaiduNetdiskWorkspace\\Code From VSCode\\OpenCV\\VLAN划分.mp4')
# 判断摄像头是否为打开状态
while cap.isOpened():
    # 从摄像头读取视频帧
    ret, frame = cap.read()
    if ret == True:
        # 将视频帧在窗口显示
        cv2.imshow('video', frame)
        # 重新设置窗口
        cv2.resizeWindow('video', 640, 360)

        # 等待键盘事件
        key = cv2.waitKey(30)
        if key & 0xff == ord('q'):
            break
    else:
        break

# 释放VideoCapture
cap.release()
cv2.destroyAllWindows()

视频录制(保存)

  1. VideoWriter_fourcc('MJPG')*
  2. VideoWriter('文件',格式,帧率,分辨率)
  3. vw.write()
  4. vw.release()
#05.VideoWrite.py
import cv2

# 创建VideoWriter为多媒体文件
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
vw = cv2.VideoWriter('./out.mp4v', fourcc, 25, (1280, 720))

# 创建窗口
cv2.namedWindow('video', cv2.WINDOW_AUTOSIZE)

# 设置大小
# cv2.resizeWindow('video', 800, 600)

# 获取摄像头设备
cap = cv2.VideoCapture(0)

while True:

    # 从摄像头读取视频帧
    ret, frame = cap.read()

    # 将视频帧在窗口显示
    cv2.imshow('video', frame)

    # 写数据到多媒体文件
    vw.write(frame)

    # 等待键盘事件
    key = cv2.waitKey(30)
    if key & 0xff == ord('q'):
        break

# 释放VideoCapture
cap.release()
# 释放VideoWriter
vw.release()
cv2.destroyAllWindows()

TrackBar

  1. 创建trackbar creatTrackbar(tackbarname,winname,value,count)

    • trackbarname : 命名
    • winname: 所在窗口名
    • value: trackbar当前值
    • count:最小0,最大count
    • callback(回调函数),userdata
  2. 获取trackbar值 getTrackbarPos(trackbarname,winname)

#06.trackbar.py
import cv2
import numpy as np

def callback():
    pass

# 创建窗口
cv2.namedWindow('trackbar')
# 创建trackbar
cv2.createTrackbar('R', 'trackbar', 0, 255, callback)
cv2.createTrackbar('G', 'trackbar', 0, 255, callback)
cv2.createTrackbar('B', 'trackbar', 0, 255, callback)
# 创建一个背景图片
img = np.zeros((480, 640, 3), np.uint8)

while True:

    # 获取当前trackbar的值
    r = cv2.getTrackbarPos('R', 'trackbar')
    g = cv2.getTrackbarPos('G', 'trackbar')
    b = cv2.getTrackbarPos('B', 'trackbar')
    # 改变背景图片颜色
    img[:] = [b, g, r]
    cv2.imshow('trackbar', img)
    key = cv2.waitKey(10)
    if key & 0xff == ord('q'):
        break

cv2.destroyAllWindows()

鼠标事件

#09.mouse.py
import cv2
import numpy as np


# 鼠标回调函数
def mouse_callback(event, x, y, flags, userdata):
    #获取鼠标点击事件及位置坐标
    print(event, x, y, flags, userdata) 

# mouse_callback(1, 100, 100, 16, "6666")


# 创建窗口
cv2.namedWindow('mouse', cv2.WINDOW_NORMAL)
cv2.resizeWindow('mouse', 640, 360)

# 设置鼠标回调
cv2.setMouseCallback('mouse', mouse_callback, '123')

# 显示窗口和背景
img = np.zeros((360, 640, 3), np.uint8)
while True:
    cv2.imshow('mouse', img)
    key = cv2.waitKey(1)
    if key & 0xFF == ord('q'):
        break

cv2.destroyAllWindows()

二、基本图形的绘制

色彩空间

  • RGB :人眼的色彩空间
  • OpenCV默认使用BGR
  • HSV/HSB/HSL

HSV

Hue:色相,即色彩,如红色,蓝色
Saturation:饱和度,颜色的纯度
Value:名度

1646738947761

HSL

Hue:色相,即色彩,如红色,蓝色
Saturation:饱和度,颜色的纯度
Ligthness:亮度

1646743391264

通道的分离与合并

  1. 通道分离 split(mat)
  2. 通道合并 merge((ch1,ch2,ch3,…))
#07.sp_merge.py
import cv2
import numpy as np

img = np.zeros((480, 640, 3), np.uint8)
#通道分离
b, g, r = cv2.split(img)

b[10:100, 10:100] = 255
g[10:100, 10:100] = 255
#通道合并
img2 = cv2.merge((b, g, r))

cv2.imshow('img', img)
cv2.imshow('b', b)
cv2.imshow('g', g)
cv2.imshow('img2',img2)
cv2.waitKey(0)

图形绘制

  1. 绘制直线 cv2.line(img,(x,y),(x,y),(b,g,r),width)
  2. 绘制矩形 cv2.rectangle(img,(x,y),(x,y),(b,g,r),width)
  3. 绘制圆 cv2.circle(img,(x,y), r, (B,G,R), width)
  4. 绘制椭圆 cv2.ellipse(img,(x,y),(a,b),角度,0,360,(0,124,123),3)
    • 0,360: 从0度开始,顺时针画到360度
  5. 绘制多边形 cv2.polylines(img, [pts], True, (112, 150, 123)) pts点集
  6. 绘制文本 cv2.putText(img,字符串,起始点,字体,字号……)
#08.draw.py
import cv2
from cv2 import FONT_HERSHEY_COMPLEX
import numpy as np

# 创建背景图片
img = np.zeros((480, 640, 3), np.uint8)

# 画线,坐标为(x,y))
cv2.line(img, (10, 20), (300, 400), (0, 0, 255), 1)
cv2.line(img, (10, 100), (300, 100), (0, 230, 25), 5)
# 画矩形
cv2.rectangle(img, (320, 200), (500, 100), (0, 0, 255), 5)
# 画圆
cv2.circle(img, (320, 240), 100, (0, 0, 255))
cv2.circle(img, (320, 240), 27, (255, 0, 0), 1)
# 画椭圆
# 度是按顺时针计算的
# 0度是从左侧开始的
cv2.ellipse(img, (320, 240), (100, 50), 0, 0, 360, (0, 124, 123), 3)
cv2.ellipse(img, (320, 240), (200, 100), 90, 0, 360, (0, 124, 123), 3)
# 画多边形
# 三个点
pts = np.array([(300, 10), (150, 100), (450, 100)], np.int32)
cv2.polylines(img, [pts], True, (112, 150, 123))
# 填充多边形
cv2.fillPoly(img, [pts], (255, 255, 0))
#绘制文本
cv2.putText(img, 'hello,world',(40,200),cv2.FONT_HERSHEY_COMPLEX,3,(255,255,0))

cv2.imshow('img', img)
cv2.waitKey(0)

1646809958434

小案例—使用鼠标画简易图形

# 基本功能
# 可以通过鼠标进行基本图形的绘制
# 1. 可以画线,当用户按下l键,即选择了画线。此时,滑动鼠标即可画线
# 2. 可以画矩形,当用户按下r键,即选择了画矩形。此时,滑动鼠标即可画矩形
# 3. 可以画线,当用户按下c键,即选择了画圆。此时,滑动鼠标即可画圆
# .....

# curshape: 0-drawline, 1-drawrectangle, 2-drawcircle
import cv2
import numpy as np

curshape = 0
startpos = (0, 0)

# 显示窗口和背景
img = np.zeros((480, 640, 3), np.uint8)


# 鼠标回调函数
def mouse_callback(event, x, y, flags, userdata):
    global curshape, startpos
    print(event, x, y, flags, userdata)
    if event & cv2.EVENT_LBUTTONDOWN == cv2.EVENT_LBUTTONDOWN:
        startpos = (x, y)
    elif event & cv2.EVENT_LBUTTONUP == cv2.EVENT_LBUTTONUP:
        if curshape == 0:  # line
            cv2.line(img, startpos, (x, y), (0, 0, 255), 3)
        elif curshape == 1:  # rectangle
            cv2.rectangle(img, startpos, (x, y), (255, 0, 0), 3)
        elif curshape == 2:  # circle
            a = (x-startpos[0])
            b = (y-startpos[1])
            r = int((a**2+b**2)**0.5)
            cv2.circle(img, startpos, r, (0, 255, 0))
        else:
            print('error:no shape')


# 创建窗口
cv2.namedWindow('drawshape', cv2.WINDOW_NORMAL)

# 设置鼠标回调
cv2.setMouseCallback('drawshape', mouse_callback)

while True:
    cv2.imshow('drawshape', img)
    key = cv2.waitKey(1) & 0xFF
    if key == ord('q'):
        break
    elif key == ord('l'):  # line
        curshape = 0
    elif key == ord('r'):  # rectangle
        curshape = 1
    elif key == ord('c'):  # circle
        curshape = 2

cv2.destroyAllWindows()

1646809927906

三、图像运算

图像相加

add(a,b)

#11.img_add.py
import cv2
import numpy as np

# 加载图像
tx = cv2.imread('.//src//tx1.jpg')

# 图的加法运算就是矩阵的加法运算
# 两张图尺寸必须相等
# 问题  图片路径不能包含中文?? 
# 见00.Chinese_Path.py
print(tx.shape)
#生成一张背景图
img = np.ones((640, 640, 3), np.uint8) * 50
#加法运算
result = cv2.add(tx, img)

cv2.imshow('car', tx)
cv2.imshow('img', img)
cv2.imshow('result', result)
cv2.waitKey(0)

1646834364179

图像相减

subtract(a,b) a减b

#12.img_sub.py
#下图一  在加法的基础上减去背景图,还原
orig_1 = cv2.subtract(tx, img)
#下图二  在原图上减去背景图,更暗
orig_1 = cv2.subtract(tx, img)
cv2.imshow('orig_1', orig_1)

1646871498338

1646871652138

图像乘除

multiply(A,B) 乘法 升的更快

divide(A,B) **除法 ** 降的更快

图像融合

图像溶合

addWeighted(A,alpha,B,bate,gamma)

  • alpha 和 beta是权重

  • gamma 静态权重

# 图像溶合
# addWeighted(A,alpha,B,bate,gamma)
# alpha 和 beta是权重
# gamma 静态权重
# 13.img_fus.py
import cv2
import numpy as np

smile = cv2.imread('.//src//tx2.jpg')
boy = cv2.imread('.//src//tx1.jpg')

# 只有两张图的属性是一样的才可以进行溶合
print(smile.shape)
print(boy.shape)

result = cv2.addWeighted(smile,0.7,boy,0.3,0)
cv2.imshow('add',result)
cv2.waitKey(0)

1646872961979

其他操作

# 14.img_bitwise.py
import cv2
from cv2 import bitwise_and
import numpy as np

# 创建一张图片
img1 = np.zeros((400, 400), np.uint8)
img2 = np.zeros((400, 400), np.uint8)

img1[50:250, 50:250] = 255
img2[200:350, 200:350] = 255

# new_img = cv2.bitwise_not(img1)  #非运算
# new_img = cv2.bitwise_and(img1,img2)  #与运算
# new_img = cv2.bitwise_or(img1, img2)  # 或运算
new_img = cv2.bitwise_xor(img1, img2)  # 异或运算

cv2.imshow('img1', img1)
cv2.imshow('img2', img2)
cv2.imshow('result', new_img)

cv2.waitKey(0)

非操作

cv2.bitwise_not(img1)

1646874105065

与运算

new_img = cv2.bitwise_and(img1,img2)

1646874667984

或与异或

cv2.bitwise_or(img1, img2)

1646874778298

cv2.bitwise_xor(img1, img2)

1646874853367

小作业—图像加水印

  1. 引入一张图片

  2. 要有一个logo,可自己创建

  3. 计算图片在什么地方,在添加的地方变成黑色

  4. 利用add,将logo与图处叠加到一起

# 1. 引入一张图片,
# 2. 要有一个logo,可自己创建
# 3. 计算图片在什么地方,在添加的地方变成黑色
# 4. 利用add,将logo与图处叠加到一起
import cv2
import numpy as np


# 导入图片
tx = cv2.imread('.//src//tx1.jpg')
print(tx.shape)
# 创建logo
logo = np.zeros((200, 200, 3), np.uint8)
mask = np.zeros((200, 200), np.uint8)

# 绘制logo
logo[20:120, 20:120] = [0, 0, 255]
logo[80:180, 80:180] = [0, 255, 0]

mask[20:120, 20:120] = 255
mask[80:180, 80:180] = 255

# 对mask按位求反
m = cv2.bitwise_not(mask)
# 选择添加logo的位置
roi = tx[0:200, 0:200]
# 与m进行与操作  roi是三通道的
tmp = cv2.bitwise_and(roi, roi, mask=m)
# 添加水印  logo
dst = cv2.add(tmp, logo)

tx[0:200, 0:200] = dst

cv2.imshow('logo', logo)
cv2.imshow('mask', mask)
cv2.imshow('m', m)
cv2.imshow('dst', dst)
cv2.imshow('tmp', tmp)
cv2.imshow('tx', tx)
cv2.waitKey(0)

1646883122839

四、图像变换

图像缩放

resize(src, dst, dsize,fx,fy,interpolation)

  • src:源图像
  • dst:目的图像(输出参数) python直接赋值
  • dsize:目标大小(缩放多少)
  • fx,fy:x轴/y轴的缩放因子 (与dsize冲突,保留一个即可,默认dsize)
  • interpolation:缩放算法
    • INTER_NEAREST:临近插值,速度快,效果差
    • INTER_LINEAR:双线性插值,原图中的4个点
    • INTER_CUBIC:三次插值,原图中的16个点
    • INTER_AREA:效果更好,速度最慢
#16.img_scale.py
import cv2
from cv2 import INTER_NEAREST
import numpy as np

tx = cv2.imread('.//src//tx1.jpg')
print(tx.shape)
#图像缩放
new = cv2.resize(tx, (800, 800), interpolation=cv2.INTER_AREA)
# new = cv2.resize(tx,None,fx=0.3,fy=0.3)

cv2.imshow('tx', tx)
cv2.imshow('new', new)
cv2.waitKey(0)

图像翻转

flip(img,flipCode)

  • flipCpde == 0上下, >0左右,<0上下+左右
# 17.img_flip.py
import cv2
import numpy as np

tx = cv2.imread('.//src//tx1.jpg')
print(tx.shape)
# 图像翻转
# new = cv2.flip(tx, 0)  # 上下
# new = cv2.flip(tx,1)  #左右
new = cv2.flip(tx, -1)  # 上下 左右
cv2.imshow('tx', tx)
cv2.imshow('new', new)
cv2.waitKey(0)

1646894843224

1646894864680

1646894905196

图像旋转

rotate(img, reteateCode)

  • rotateCode
    • ROTATE_90_CLOCKWISE 顺时针90°
    • ROTATE_180 180°
    • ROTATE_90_COUNTERCLOCKWISE 顺指针270° = 逆时针9°
#18.img_rotate.py
import cv2
import numpy as np

tx = cv2.imread('.//src//tx1.jpg')
print(tx.shape)
# 图像旋转
# new = cv2.rotate(tx, cv2.ROTATE_180)  # 180度
# new = cv2.rotate(tx, cv2.ROTATE_90_CLOCKWISE)  # 90度
new = cv2.rotate(tx, cv2.ROTATE_90_COUNTERCLOCKWISE)  # 270度
cv2.imshow('tx', tx)
cv2.imshow('new', new)
cv2.waitKey(0)

1646895358965

1646895381537

1646895436806

图像的仿射变换

仿射变换是图像旋转、缩放、平移的总称。

仿射API

  • warpAffine(sec, M, dsize, flags, mode, value)
    • M变换矩阵
    • dsize:输出尺寸大小
    • flag:与resize中的插值算法一致
    • mode:边界外推法标志
    • value:填充边界的值

平移矩阵

​ 矩阵中的每个像素由 (x,y)组成,因此其变换矩阵式2x2的矩阵,平移向量为2x1的向量,所在平移矩阵为2x3矩阵。

# 19.img_affine.py
import cv2
import numpy as np

tx = cv2.imread('.//src//tx1.jpg')

h, w, ch = tx.shape

# 生成变换矩阵
M = np.float32([[1, 0, 100], [0, 1, 0]])
## [1,0,平移量]
## [1,0][0,1]  2x2矩阵
new = cv2.warpAffine(tx, M, (w, h))

cv2.imshow('tx', tx)
cv2.imshow('new', new)
cv2.waitKey(0)

1646898933394

变换矩阵(一)

  • getRotationMatrix2D(center, angle, scale)
    • center 中心点
    • angle 角度
    • scale 缩放比例
# 在 19.img_affine.py 的基础上将M改为如下  
# 对图像进行中心旋转、缩放
# 旋转角度为逆时针
# 中心点是(x,y)
M = cv2.getRotationMatrix2D((w/2, h/2), 45, 0.3)

1646899674265

变换矩阵(二)

1646899216093

要达到如图所示的变换,可用如下api

  • getAffineTransform(src[], dst[]) 通过三个点来确认变换位置
# 在 19.img_affine.py 的基础上将M改为如下  
#src变换前三个点
#dst变换对应的三个点
src= np.float32([[400,300],[500,300],[400,600]])
dst = np.float32([[200,400],[300,500],[100,610]])
M = cv2.getAffineTransform(src, dst)

1646900232803

透视变换

1646900384922

​ 将一种坐标系变成另外一种角度,达到我们想要的结果。通过透视变换,可以进行图片矫正。

  • warpPerspective(img,M,dsize,…) 参数说明同仿射变换 ★关键在于 M

  • getPersectiveTransform(src,dst) 需要四个点(图形的四个角) 获取变换矩阵

# 20.img_presective.py
import cv2
import numpy as np


img = cv2.imread('.//src//page.png')
print(img.shape)

# 设置对应点
src = np.float32([[20, 195], [380, 190], [0, 715], [450, 690]])
dst = np.float32([[0, 0], [475, 0], [0, 595], [475, 595]])

# 获取变换矩阵
M = cv2.getPerspectiveTransform(src, dst)
# 进行透视变换
new = cv2.warpPerspective(img, M, (475, 595))


cv2.imshow('img', img)
cv2.imshow('new', new)
cv2.waitKey(0)

1646902771272

五、图像滤波

滤波相关概念

滤波的作用:一幅图像通过滤波器得到另一幅图像,其中滤波器又称为卷积核,滤波的过程成为卷积

卷积

1646904250526

img

  • 卷积核的大小
  • 锚点
  • 边界扩充
  • 步长

卷积

  • 卷积核的大小

​ 卷积核一般为奇数,如3x3, 5x5, 7x7等。一方面是增加padding的原因;另一方面是保证锚点在中间,防止位置发生偏移。

  • 卷积核大小的影响

​ 在在深度学习中,卷积核越大,看到的(感受野)越多,提取的特征越好,同时计算量也就越大。

  • 计算公式:

锚点

锚点即卷积核的中心

边界扩充

  • 当卷积核大于1且不进行边界扩充,输出尺寸将相应缩小
  • 当卷积核以标准方式进行边界扩充,则输出数据的空间尺寸将与输入相等

1646903978440

计算公式: N = (W - F + 2P ) / S + 1

  • N 输出图像大小
  • W 源图大小 ; F 卷积核大小 ; P 扩充尺寸
  • S 步长大小

步长

1646904373951

[实战] 图像卷积

低通滤波与高通滤波

1646904547936

  • 低通滤波:低于某个阈值,可以去除噪音或平滑图像
  • 高通滤波:高于某个阈值,可以帮助查找图像的边缘

图像卷积示例

卷积cv2.filter2D(src, ddepth, kernel, anchor, delta, borderType)*

  • ddepth:位深 默认原彩 -1
  • keinel:卷积核
  • anchor:锚点 默认(-1,-1) 根据核的尺寸寻找锚点
  • borderType:边界类型
# 21.img_filter.py
import cv2
import numpy as np

img = cv2.imread('.//src//ZH-CN7993615424_UHD_4k.jpg')
# 生成一个5*5的矩阵  值为 1/25
kernal = np.ones((5, 5), np.float32) / 25

dst = cv2.filter2D(img, -1, kernal)

cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)

1646914434210

低通滤波

主要作用:去噪;

方盒滤波与均值滤波卷积核

1646914564006

  • 与均值滤波类似,主要区别为方盒滤波有参数

  • 参数a的作用

    • normalize = true , a = 1 / W * H
    • normalize = false , a =1
  • 当normalize == true 时,方盒滤波=均值滤波

方盒滤波 cv2.boxFilter(src, ddepth, ksize, anchor, normalize, borderType)

  • ksize:卷积核大小

  • anchor:锚点 默认(-1,-1) 根据核的尺寸寻找锚点

  • normalize:

    • normalize = true , a = 1 / W * H
    • normalize = false , a =1
  • borderType:边界

均值滤波 cv2.blur(src, ksize, anchor, borderType)

# 21.img_blur.py
import cv2
import numpy as np

img = cv2.imread('.//src//ZH-CN7993615424_UHD_4k.jpg')
#均值滤波
dst = cv2.blur(img, (5,5))
cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)

1646914434210

高斯滤波

1646916372870

1646916453105

中心点值可能不是最大,但占比权重最大;周围点值可能很大,但占比权重小。如下图所示。

1646916502124

优点:高斯滤波主要解决高斯噪点

API GanssianBlur(img, kernal, sigmaX, sigmaY, …)

  • sigmaX、sigmaY:中型滤波延展的宽度,也是到中心点的差距

1646916897860

sigma越大,图像越模糊,即平滑程度越大。

1646916969493

当没有sigma时,则以卷积核大小为基准。

# 23.img_GanssBlur.py
import cv2
import numpy as np

img = cv2.imread('.//src//Ganss.png')

# 高斯滤波
dst = cv2.GaussianBlur(img, (9,9), sigmaX=1)

cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)

1646918084615

中值滤波

​ 假设有一个数组[1556789],取其中的中间值作为卷积后的结果值。

​ 优点:对胡椒噪音效果明显

1646918136092

API medianBlur(img, ksize)

# 24.img_medianBlur.py
import cv2
import numpy as np

img = cv2.imread('.//src//Pepper_noise.png')

# 中值滤波
dst = cv2.medianBlur(img, 9)

cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)

1646918463213

双边滤波

​ 优点:可以保留边缘,同时可以对边缘内的区域进行平滑处理。

​ 主要作用是进行美颜

1646918685370

API bilaterFilter(img, d, sigmaColor, sigmaSpace,…)

  • d:核的大小

  • sigmaColor:控制图像灰度变化权重。设置的较大的时候,灰度可以变化的范围也就是越大,那么灰度差异越大也能得到比较高的权重值,细小的边缘因此会被影响掉,不能有很好的保边效果。

  • sigmaSpace:控制空间距离的权重变化。当该值较大时,其有效的空间范围就越大,当较小的时候,有效的空间范围就越小,当窗口一定的情况下,sigmaS越大,那么其边缘点也能得到较大的权重,去噪效果明显。

​ 图像中灰度变化在一定的范围内,两者的灰度差异越大的话,得到的权重应该越小,但是要保证灰度级差异大,也能得到一个有效的权重,就是说,灰度级的差异要落在有效的概率范围内,可以选择3sigma或者2sigma有效范围。因此,sigmaS的设置要根据设定的滤波窗口的大小设置。若Half_size为4,可以选用2sigma原则,设置为2

# 25.img_bilateralfilter.py
import cv2
import numpy as np

img = cv2.imread('.//src//lena.png')

# 中值滤波
dst = cv2.bilateralFilter(img, 7, 20,50)

cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)

1646919121683

高通滤波

  • 作用:检测边缘

  • 常见的高通滤波:

    • Sobel(索贝尔)(高斯):抗噪音性比较强
    • Scharr(沙尔):可以检测出更细的边缘线,但不可以改变卷积核大小。索贝尔size为-1时即沙尔滤波
    • Laplacian(拉普拉斯):不需要单独求x,y的边缘,可以直接检测边缘,但是对于噪音比较敏感,无法进行降噪

Sobel算子

  • 先向x方向求导,然后再y方向求导,最终结果:| G | = | Gx | + | Gy |

API

Sobel(src, ddepth, dx, dy, ksize = 3, scale = 1(缩放), delta = 0, borderType = BORDER_DEFAUTLT)

  • 当 ksize = 1时,即为Scharr(沙尔)滤波
# 26.img_Sobel.py
import cv2
import numpy as np

img = cv2.imread('.//src//chess.png')

# Sobel滤波
# 索贝尔算子y方向边缘
d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize = 5)
# 索贝尔算子x方向边缘
d2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize = 5)

dst = d1 + d2
#dst = cv2.add(d1, d2)
#cv2.imshow('d1',d1)
#cv2.imshow('d2',d2)
cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)

1646987201429

Scharr算子

  • 与Sobel类似,只不过使用的kernel值不同
  • Scharr只能求某一个方向x/y的边缘

API Scharr(src, ddepth, dx, dy, scale = 1, delta = 0, borderType = BORDER_DEFAUTLT)

# 在26.img_Sobel.py中修改
dst = cv2.Scharr(img, cv2.CV_64F, 1, 0)

拉普拉斯算子

  • 可以同时求两个方向的边缘
  • 对噪音敏感,一般需要先进行去噪再调用拉普拉斯

API Laplacian(img, ddepth, ksize=1, scale=1, borderType = BORDER_DEFAULT)

# 在26.img_Sobel.py中修改
dst = cv2.Laplacian(img, cv2.CV_64F, ksize = 5)

1646988817396

边缘检测Canny

  • 使用5x5 高斯滤波消除噪声
  • 计算图像梯度方向(0°/45°/90°/135°)
  • 取局部极大值
  • 阈值计算

1646989229445

如果超过maxVal一定是超过边缘,如果低于maxVal一定不是边缘。

如果在maxVal与minVal之间,看所确定的值是否是连续的。如A是边缘,C在maxVal与minVal之间且与A是连续的(在同一条线上);而B就不被认为是边缘。

API Canny(img, minVal, maxVal,…)

# 27.img_Canny.py
import cv2
import numpy as np

img = cv2.imread('.//src//lena.png')
# Canny边缘检测
dst = cv2.Canny(img, 100, 200)  # 图一
# dst = cv2.Canny(img, 50, 150)  # 图二 
cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)

1646990149813

1646990251122

Kirsch算子和Robinson算子

Kirsch算子

Kirsch算子由以下8个卷积核组成,图像与每一个卷积核进行卷积,然后取绝对值作为对应方向上的边缘强度的量化。对8个卷积结果取绝对值,然后取最大值作为最后输出的边缘强度。

Robinson算子

与Kirsch算子类似,Robinson算子也是由8个卷积核组成,其检测过程和Kirsch是一样的。

​ Kirsch算子使用了8个方向上的卷积核,所以其检测的边缘比标准的Prewitt算子和Sobel算子检测到的边缘会显得更加丰富。

六、 形态学图像处理

  • 基于图像形态进行处理的一些基本方法
  • 这些处理方法基本是对二进制图像进行处理
  • 卷积核则决定着图像处理后的效果。

处理的基本方法

  • 腐蚀膨胀

  • 开运算:先做腐蚀再做膨胀

  • 闭运算:先做膨胀再做腐蚀

  • 顶帽

  • 黑帽

  • ……

图像二值化

​ 将图像的每个像素变成两种值,如0,255

  • 全局二值化:对于全部区域
  • 局部二值化:选定某区域

全局二值化

API threshod(img, thresh, maxVal, type)

  • 两个返回值:ret:True或False,代表有没有读到图片;dst: 目标图像;

  • img:图像,最好是灰度图

  • thresh:阈值

  • maxVal:超过阈值,替换成maxVal

  • type:

    • THRESH_BINARY 和 THRESH_BINARY_INV
    • THRESH_TRUNC
    • THRESH_TOZERO 和 THRESH_TOZERO_INV

1646995968734

# 28.img_binary.py
import cv2

img = cv2.imread('.//src//tx1.jpg')
# 转换为灰度图像
tmp = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化
# 超过阈值使用maxVal代替  如下图一
ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_BINARY)
# 低于阈值使用maxVal代替  如下图二
ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_BINARY_INV)
# 低于阈值保留,高于的使用阈值替代
ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_TRUNC)
# 高于阈值保留,高于的使用0替代
ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_TOZERO)
# 低于阈值保留,高于的使用0替代
ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_TOZERO_INV)
print(dst.shape)
cv2.imshow('img', img)
cv2.imshow('tmp', tmp)
cv2.imshow('dst', dst)
cv2.waitKey(0)

1646995536540

1646995578087

自适应阈值

二值化缺点:由于光照不均匀以及阴影的存在,只有一个阈值会使得在阴影处的白色被二值化成黑色/白色。如下图所示。

1646996685925

对于这种图像,则适用于自适应阈值

API adaptiveThreshold(img, maxVal, adaptiveMethod, type, blockSize, C)

  • adaptiveMethod:计算阈值的方法
    • ADAPTIVE_THRESH_MEAN_C:计算临近区域的平均值
    • ADAPTIVE_THRESH_GAUSSIAN_C:高斯窗口加权平均值(一般使用)
  • Type:
    • THRESH_BINARY
    • THRESH_BINARY_INV
  • blockSize:临近区域的大小
  • C:常量,应从计算出的平均值或加权平均值中减去
# 29.img_adaptive.py
import cv2

img = cv2.imread('.//src//math.png')
# 转换为灰度图像
tmp = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化
# 超过阈值使用maxVal代替
ret1, dst1 = cv2.threshold(tmp, 180, 255, cv2.THRESH_BINARY)
# 自适应阈值
dst2 = cv2.adaptiveThreshold(
    tmp, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 0)

cv2.imshow('img', img)
cv2.imshow('tmp', tmp)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey(0)

结果如下:图一为原始图像;图二为对应灰度图像;图三为二值化图像;图四为自适应阈值后的图像。

1646997293373

处理的基本方法

腐蚀

通过腐蚀,可以使图像体积小很多。

1646997717607

当卷积核全部位于白色区域时,卷积核中心才是白色,否则为黑色。

1646997601142

一般情况下,腐蚀的卷积核为全 1

1646997962353

1646998033476

API erode(img, kernel, iterations = 1)

  • iterations :腐蚀次数
# 30.img_erode.py
import cv2
import numpy as np

img = cv2.imread('.//src//j.png')

# 创建一个3x3的卷积核
kernel = np.ones((3, 3), np.uint8)
# 腐蚀
dst = cv2.erode(img, kernel, iterations=5)

cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)

3x3 腐蚀5次

卷积核的类型

API:获取卷积核 getStructuringElement(type, size)

  • size: (3, 3) (5, 5) ……

  • type:

    • MORPH_RECT:矩形
    • MORPH_ELLIPSE:圆形
    • MORPH_CROSS:交叉
# 31.img_getkernel.py
import cv2
import numpy as np

img = cv2.imread('.//src//j.png')

# 获取一个卷积核
kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))   # 矩形
kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))  # 圆形
kernel3 = cv2.getStructuringElement(cv2.MORPH_CROSS, (7, 7))  # 交叉(十字架)
print(kernel1, kernel2, kernel3)
# 腐蚀
dst1 = cv2.erode(img, kernel1, iterations=3)
dst2 = cv2.erode(img, kernel2, iterations=3)
dst3 = cv2.erode(img, kernel3, iterations=3)

# cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.imshow('dst3', dst3)
cv2.waitKey(0)

1646998875329

膨胀

即腐蚀的逆运算。

1646999112477

只要卷积核中心不为 0,其周边全部变为非 0 。

膨胀的快慢与卷积核大小有关。

1646999246649

API dilate(img, kernel, iterations = 1)

# 32.img_dilate.py
import cv2
import numpy as np

img = cv2.imread('.//src//j.png')

# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))   # 矩形
# 腐蚀
dst1 = cv2.erode(img, kernel, iterations=1)

# 膨胀
dst2 = cv2.dilate(img, kernel, iterations=1)
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey(0)

1646999497114

思考:

  • 如果是白底黑字,进行腐蚀和膨胀会怎样?
  • 卷积核是否可以设置为全 0

开运算

开运算 = 腐蚀 + 膨胀

1646999653110

作用:消除字体外部噪点。

实现一:先腐蚀,再膨胀

# 修改32.img_dilate.py
import cv2

img = cv2.imread('.//src//dotJ.png')

# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))   # 矩形
# 腐蚀
dst1 = cv2.erode(img, kernel, iterations=2)

# 膨胀
dst2 = cv2.dilate(dst1, kernel, iterations=2)
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey(0)

1647000048928

实现二:调用API

API morphologyEx(img, MORPH_OPEN), kernel)

# 33.img_open.py
import cv2

img = cv2.imread('.//src//dotJ.png')

# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9))   # 矩形
# 开运算
dst = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.imshow('img', img)
cv2.imshow('dst1', dst)
cv2.waitKey(0)

1647000278911

闭运算

闭运算 = 膨胀 + 腐蚀

1647000394320

作用: 消除图像内部噪点。

实现:调用API

API morphologyEx(img, MORPH_CLOSE), kernel)

# 34.img_close.py
import cv2

img = cv2.imread('.//src//dotinJ.png')

# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9))   # 矩形
# 闭运算
dst = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imshow('img', img)
cv2.imshow('dst1', dst)
cv2.waitKey(0)

1647000727537

形态学梯度

梯度 = 原图 - 腐蚀

1647001061398

作用: 求图像边沿。

API morphologyEx(img, MORPH_GRADIENT), kernel)

# 35.img_gradient.py
import cv2

img = cv2.imread('.//src//j.png')

# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9))   # 矩形
# 梯度运算
dst = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
cv2.imshow('img', img)
cv2.imshow('dst1', dst)
cv2.waitKey(0)

1647001447556

顶帽运算

顶帽 = 原图 - 开运算

作用: 可以得到图像外的噪声。

1647001627122

API morphologyEx(img, MORPH_TOPHAT), kernel)

# 36.img_tophat.py
import cv2

img = cv2.imread('.//src//tophat.png')

# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (19, 19))   # 矩形
# 顶帽运算
dst = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('img', img)
cv2.imshow('dst1', dst)
cv2.waitKey(0)

1647001938421

黑帽运算

黑帽 = 闭运算 - 原图

作用: 可以得到图像内的噪声。

1647002147208

API morphologyEx(img, MORPH_BLACKHAT), kernel)

# 37.img_blackhat.py
import cv2

img = cv2.imread('.//src//dotinJ.png')

# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))   # 矩形
# 黑帽运算
dst = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('img', img)
cv2.imshow('dst1', dst)
cv2.waitKey(0)

1647002292562

小结

  • 开运算,先腐蚀在膨胀,去除大图形外的小图形

  • 闭运算,线膨胀在腐蚀,去除大图形内的小图形

  • 梯度,求图形边缘

  • 顶帽,原图减去开运算,得到大图形外的小图形

  • 黑帽,闭运算减去原图,得到大图形内的小图形

七、图像轮廓

具有相同颜色强度连续点的曲线。

1647051587947

作用:

  • 可以用于图形分析
  • 物体的识别与检测

注意点:

  • 为了检测的准确性,需要先对图像进行二值化或者Canny操作
  • 画轮廓时会修改原始输入的图像

查找轮廓

API findContours(img,mode, ApproximationMode…)

  • 两个返回值:contours [轮廓列表] 和 hierarchy [层级]
  • mode
    • RETR_EXTERNAL = 0 ,表示只检测外轮廓
    • RETR_LIST = 1, 检测的轮廓不建立等级关系 :从里到外,从右到左
    • RETR_CCOMP = 2, 每层对多两级
    • RETR_TREE = 3, 按树形存储轮廓:从大到小,从右到左
  • ApproximationMode
    • CHAIN_APPROX_NONE,保存所有轮廓上的点
    • CHAIN_APPROX_SIMPLE,只保存角点

1647052026428

1647054170973

1647054234869

1647055836081

1647056031048

# 38.img_contours.py
import cv2

img = cv2.imread('.//src//contours.png')
print(img.shape)

# 转化为灰度图 ---单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(gray.shape)

# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

# 轮廓查找
contours, hirarchy = cv2.findContours(
    binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# contours, hirarchy = cv2.findContours(
#     binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 打印轮廓列表
print(contours)

# cv2.imshow('img', img)
# cv2.imshow('gray', gray)
cv2.imshow('bin', binary)
cv2.waitKey(0)

1647070410904

绘制轮廓

API drawContours(img, contours, contourIdx, color)

  • img:目标图像

  • contours:轮廓点集

  • contourIdx , -1:表示绘制所有轮廓

  • color:颜色(0,0,255)

  • thickness:线宽,-1 是全部填充

# 在38.img_contours.py基础上修改
import cv2

img = cv2.imread('.//src//contours.png')
print(img.shape)

# 转化为灰度图 ---单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(gray.shape)

# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

# 轮廓查找
contours, hirarchy = cv2.findContours(
    binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 轮廓列表
print(contours)
# 绘制轮廓  会在原图上进行修改
cv2.drawContours(img, contours, -1, (0, 0, 255), 1)

cv2.imshow('img', img)
cv2.imshow('bin', binary)
cv2.waitKey(0)

1647070947227

轮廓的面积和周长

计算面积

API contourArea(contour)

  • 结果为图像的面积
  • contour:轮廓

计算周长

API arcLength(curve,closed)

  • curve:轮廓
  • closed:是否是闭合的轮廓

Code

# 在38.img_contours.py基础上修改
import cv2

img = cv2.imread('.//src//contours.png')
print(img.shape)

# 转化为灰度图 ---单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(gray.shape)

# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

# 轮廓查找
contours, hirarchy = cv2.findContours(
    binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 绘制轮廓
cv2.drawContours(img, contours, -1, (0, 0, 255), 1)

# 计算面积
area = cv2.contourArea(contours[0])
print("area=%d" % (area))
# 计算周长
len = cv2.arcLength(contours[0], True)
print('len=%d' % (len))
cv2.imshow('img', img)
cv2.waitKey(0)

1647071749964

多边形逼近与凸包

  • 逼近 :减少数据量,通过特征点将目标描绘出来,如下图左图。
  • 凸包 :描绘整个‘大’轮廓,如下图右图。

1647071891839

逼近

API approxPolyDP(curve, epsilon, closed)

  • curve:轮廓
  • epsilon: 精度
  • closed:是否是闭合的轮廓

凸包

API convexHull(points, clockwise,…)

  • points:轮廓
  • clockwise:顺时针绘制

Code

# 39.img_approx_hull.py
import cv2

# 连接直线形成闭合

def drawShape(src, points):
    i = 0
    while i < len(points):
        if i == len(points) - 1:  # 处理最后一个点,与第一个点连形成闭合
            x, y = points[i][0]
            x1, y1 = points[0][0]
            cv2.line(src, (x, y), (x1, y1), (0, 0, 255), 2)
        else:
            x, y = points[i][0]  # 连接点集
            x1, y1 = points[i+1][0]
            cv2.line(src, (x, y), (x1, y1), (0, 0, 255), 2)
        i += 1

img = cv2.imread('.//src//hand.png')

# 转化为灰度图 ---单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 二值化
ret, binary = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)

# 轮廓查找
contours, hirarchy = cv2.findContours(
    binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# 绘制轮廓
cv2.drawContours(img, contours, 0, (0, 255, 0), 3)
# 多边形逼近
e = 20
approx = cv2.approxPolyDP(contours[0], e, True)
# 多边形凸包
hull = cv2.convexHull(contours[0])

drawShape(img, approx)

drawShape(img, hull)
cv2.imshow('img', img)
cv2.waitKey(0)

1647073201053

外接矩形

  • 最小外接矩形: 能够涵盖图像所有轮廓的最小矩形
  • 最大外接矩形: 能够涵盖图像所有轮廓的最大矩形

1647073487507

最小外接矩形

API minAreaRect(points)

  • points:轮廓
  • 返回值:RotatedRect
    • x,y
    • width,height
    • angle

最大外接矩形

API boundingRect(array)

  • array:轮廓
  • 返回值:Rect
# 40.img_mbRect.py
import cv2
import numpy as np
img = cv2.imread('.//src//hello.png')
print(img.shape)

# 转化为灰度图 ---单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(gray.shape)

# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)

# 轮廓查找
contours, hirarchy = cv2.findContours(
    binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
cv2.drawContours(img, contours, -1, (0, 0, 255), 1)

# 最小外接矩形
r = cv2.minAreaRect(contours[1])
box = cv2.boxPoints(r)
# box为float类型,需要转换为int型
box = np.int0(box)
cv2.drawContours(img, [box], 0, (0, 0, 255), 3)
# 最大外接矩阵
x, y, w, h = cv2.boundingRect(contours[1])
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)

cv2.imshow('img', img)
cv2.waitKey(0)

1647079310117

八、基于形态学的车辆识别与统计

对于车辆识别来讲,主要分为以下几个步骤:

  • 加载视频
  • 通过形态学识别车辆
  • 对车辆进行统计
  • 显示车辆统计信息

加载视频

加载视频,在OpenCV的基本操作中已经做过阐述,详见从摄像头/视频文件读取视频帧

import cv2
import numpy as np
# 加载视频
cap = cv2.VideoCapture('.//src//video.mp4')

while True:
    ret, frame = cap.read()
    if ret == True:
        cv2.imshow('video', frame)

    key = cv2.waitKey(1) & 0xFF
    if key == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

去背景

​ 对视频来讲,大部分场景,背景是相同的不变的,差别在于运动的物体,因此运动的物体可视为前景,而静止的物体则被视为背景,通过去除背景,就可以将运动的物体提取出来。

​ 在视频中某一帧,某一特定区域内,它的像素在整个时间轴的范围内没有发生变化,则可以认为是背景。

API creatBackgroundSubtractorMOG(…)

  • history = 200

在最新的OpenCV v4中 API修改为createBackgroundSubtractorMOG2(),BackgroundSubtractorMOG2用于动态目标检测,用到的是基于自适应混合高斯背景建模的背景减除法,相对于BackgroundSubtractorMOG,其具有更好的抗干扰能力,特别是光照变化。

详见:https://docs.opencv.org/3.2.0/d7/d7b/classcv_1_1BackgroundSubtractorMOG2.html

bgsubmog = cv2.createBackgroundSubtractorMOG2()

# 去背景
mask = bgsubmog.apply(dst)

形态处理

# 灰度化
cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 高斯降噪
blur = cv2.GaussianBlur(frame, (7, 7), 7)
# 中值滤波
dst = cv2.medianBlur(blur, 5)
# 腐蚀
erode = cv2.erode(mask, kernel)
# 膨胀
dilate = cv2.dilate(erode, kernel, 3)
# 闭操作
close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)
close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)
# 获取轮廓
cnts, h = cv2.findContours(
            close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.line(frame, (10, line_high), (1200, line_high), (255, 255, 0), 2)

1647175405891

显示车辆统计信息

for (x, y) in cars:
   if((y < line_high - offset) and (y > line_high)+offset):
   carno += 1
   cars.remove((x, y))
print(carno)
cv2.putText(frame, 'Car nums:'+str(carno), (500, 60),
cv2.FONT_HERSHEY_SCRIPT_COMPLEX, 2, (255, 0, 0), 5)

1647343220479

完整代码

import cv2
from cv2 import createBackgroundSubtractorMOG2
from cv2 import line
import numpy as np

# 加载视频
cap = cv2.VideoCapture('.//src//video.mp4')

bgsubmog = createBackgroundSubtractorMOG2()
# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7))   # 矩形

# 设置最小宽高
min_w = 150
min_h = 150

# 检测线的高度
line_high = 500

# 线的偏移量
offset = 4

# 统计车的数量
carno = 0

# 存放有效车辆数组
cars = []


def center(x, y, w, h):
    x1 = int(w/2)
    y1 = int(h/2)
    cx = x + x1
    cy = y+y1
    return cx, cy


while True:
    ret, frame = cap.read()
    if ret == True:
        # 灰度化
        cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        # 高斯降噪
        blur = cv2.GaussianBlur(frame, (7, 7), 7)
        # 中值滤波
        dst = cv2.medianBlur(blur, 5)
        # 去背景
        mask = bgsubmog.apply(dst)

        # 腐蚀
        erode = cv2.erode(mask, kernel)
        # 膨胀
        dilate = cv2.dilate(erode, kernel, 3)

        # 闭操作
        close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
        close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)
        close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)
        close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)

        # 获取轮廓
        cnts, h = cv2.findContours(
            close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

        cv2.line(frame, (10, line_high), (1200, line_high), (255, 255, 0), 2)
        # 绘制轮廓
        for (i, c) in enumerate(cnts):

            (x, y, w, h) = cv2.boundingRect(c)

            # 对车辆的宽高进行判断
            # 以验证是否是有效的车辆
            isValid = (w >= min_w) and (h >= min_h)
            if not isValid:
                continue
 			# 绘制矩形
            cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 5)
            # 对车进行统计
            cpoint = center(x, y, w, h)
            cars.append(cpoint)
            print(cpoint)
            # 要有一条线
            # 有范围 6
            # 从数组中减去
            for (x, y) in cars:
                if((y < line_high - offset) and (y > line_high)+offset):
                    carno += 1
                    cars.remove((x, y))
                    print(carno)
        cv2.putText(frame, 'Car nums:'+str(carno), (500, 60),
                    cv2.FONT_HERSHEY_SCRIPT_COMPLEX, 2, (255, 0, 0), 5)
        cv2.imshow('video', frame)

    key = cv2.waitKey(20) & 0xFF
    if key == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

1647174325154

问题

​ 由于是基于形态学的轮廓识别,对于视频的拍摄要较高的要求,如道路等背景必须完全静止,拍摄晃动会导致不能正确识别前景、起风等不可控天气因素造成的树木、小草等晃动会被CV“过度”识别,误以为是车辆,或表现为或大或小的噪点增多……为了解决这种问题,使用深度学习、人工智能等实现车辆识别更加准确,以后将慢慢学习更新。

九、特征匹配

什么是特征

图像特征就是指有意义的图像区域,具有独特性、易于识别性,比如角点、斑点以及高密度区。

角点

  • 在特征中最重要的是角点
  • 灰度梯度最大值对应的像素
  • 两条线的交点
  • 极值点(一阶导数最大值,但二阶导数为0)

Harris角点

1647314542933

  1. 当窗口在平面上任何一个方向进行移动时,只要没有像素的变化,Harris检测就认为是平坦的
  2. 当窗口在边沿上上下移动时,即使边沿两侧像素点是不同的,但由于窗口一直在边沿上运动,是没有像素变化的
  3. 当窗口在边沿上左右移动时,这时候像素点就会发生变化,那么这里就是边沿
  4. 当窗口处于角点位置时,无论窗口朝哪个方向移动,像素值都会发生变化,那么这时就检测到一个角点

Harris点总结

  • 光滑地区,无论向哪里移动,衡量系数不变
  • 边缘地址,垂直边缘移动时,衡量系统变化剧烈
  • 在交点处,往哪个方向移动,衡量系统都剧烈变化

API cornerHarris(img, dst, blockSize, ksize, k)

  • blockSize:检测窗口大小
  • ksize:Sobel的卷积核
  • k:权重系数,经验值,一般取0.02~0.04之间
# 41.img_harris.py
import cv2
import numpy as np

# harris
blockSize = 2
ksize = 3
k = 0.04

img = cv2.imread('.//src//chess.png')

# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Harris角点检测
dst = cv2.cornerHarris(gray, blockSize, ksize, k)

# Harris角点的展示
img[dst > 0.01*dst.max()] = [0, 0, 255]

cv2.imshow('harris', img)
cv2.waitKey(0)

1647342523589

Shi-Tomasi角点★

​ Shi-Tomasi是Harris角点检测的改进,Harris角点检测的稳定性和k有关,而k是个经验值,不好设定最佳值,对于不同的图片需要设定不同的最佳k值。

API goodFeaturesToTracck(img, maxCorners, …)

  • maxCoerners:角点的最大数,值为0表示无限制
  • qualityLevel:小于1.0的正数,一般在0.01~0.1之间
  • minDistance:角之间最小欧式距离,忽略小于此距离的点
  • mask:感兴趣的区域
  • blockSize:检测窗口
  • useHarrisDetector:是否使用Harris算法,默认False
  • k:默认0.04,当设置使用Harris时,需要设置k值
# 42.img_Shi-Tomasi.py
import cv2
import numpy as np

# Shi-Tomasi参数
maxCorners = 1000
ql = 0.01
minDistance = 10

img = cv2.imread('.//src//chess.png')

# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Shi-Tomasi角点检测
corners = cv2.goodFeaturesToTrack(gray, maxCorners, ql, minDistance)
corners = np.int0(corners)

# Shi-Tomasi绘制角点
for i in corners:
    x, y = i.ravel()
    cv2.circle(img, (x, y), 3, (255, 0, 0), -1)

cv2.imshow('harris', img)
cv2.waitKey(0)

1647342347270

关键点检测和计算描述算子

SIFT检测

SIFT(Scale-Incariant Feature Transform)

出现的原因

  • Harris角点具有旋转不变的特性
  • 但缩放后,原来的角点有可能就不是角点了,如下图

1647343383859

​ 我们知道,一条线是由许多像素点连续拼接而成的,对于缩放前的角被Harris识别为角点,但放大后,之前识别的角点被许多连续的像素点替代,这时Harris就无法正确的识别为角点,而是用SIFT关键点检测依然可以正确识别到角点。

使用SIFT步骤

  1. 创建SIFT对象
  2. 进行检测,kp = sift.detect(img,……)
  3. 绘制关键点,drawKeypoints(gray, kp, img)
# 43.img_sift.py
import cv2

# 读文件
img = cv2.imread('.//src//chess.png')
# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 创建sift对象
sift = cv2.SIFT_create()
# 进行检测
kp, des = sift.detectAndCompute(gray, None)
# 绘制keypoints
cv2.drawKeypoints(gray, kp, img)

cv2.imshow('img', img)
cv2.waitKey(0)

1647344737352

##### SIFT计算描述算子

关键点和描述子

  • 关键点:位置,大小和方向
  • 关键点描述子:记录了关键点周围对其有贡献的像素点的一组向量值,其不受仿射变换、关照变换等影响

只计算描述子

  • kp, des = sift.compute(img, ip)
  • 作用是进行特征匹配

同时计算关键点和描述子

  • kp, des = sift.detectAndCompute(img,……)
  • mask:指明对img中哪个区域进行计算

SURF检测

SURF(Speeded-Up Robust Features)

SURF的优点

SIFT最大的问题是速度慢,如果对于一系列的图片进行特征点检测,SIFT则显得比较吃力,因此才有SURF,SURF主要解决SIFT的速度问题。

使用SURF的步骤

  • surf = cv2.SURF_create()
  • kp, des = surf.datectAndCompute(img, mask)
# 44.img_ruft.py
import cv2
import numpy as np

# 读文件
img = cv2.imread('.//src//chess.png')

# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 创建surf对象
surf = cv2.xfeatures2d.SURF_create()

# 使用surf进行检测
kp, des = surf.detectAndCompute(gray, None)

# 绘制keypoints
cv2.drawKeypoints(gray, kp, img)

#cv2.imshow('img', img)
cv2.waitKey(0)

1647349771559

通过编程可能会有的小伙伴遇到如下这个问题:

1647349810412

​ 这是由于在最新版本中,OpenCV已经没有SURF的版权,将opencv版本退到3.4.2即可解决,卸载之前的包,然后重新安装。

pip uninstall opencv-python
pip install opencv-python==3.4.2.16
pip install opencv-contrib-python==3.4.2.16

然后使用即可解决。

surf = cv2.xfeatures2d.SURF_create()

ORB检测

优势

  • FAST可以做到描述点的实时监测,BRIEF是对已检测到的特征点进行描述,他加快了特征描述符建立的速度,同时也极大的降低了特征匹配的时间。
  • ORB = Oriented FAST + Rotated BRIEF

使用ORB的步骤

  • orb = cv2.ORB_create()
  • kp, des = orb.detectAndCompute(img, mask)
# 45.img_ord.py
import cv2

#读文件
img = cv2.imread('.//src//chess.png')

#灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

#创建ORB对象
orb = cv2.ORB_create()

#orb进行检测
kp, des = orb.detectAndCompute(gray, None)

#绘制keypoints
cv2.drawKeypoints(gray, kp, img)

cv2.imshow('img', img)

cv2.waitKey(0)

1647350595506

​ 通过ORB算法与其他算法比较发现,ORB算子得到的特征点比较少,这也是ORB速度快的原因:通过减少数据量,降低计算量,达到快速度。

特征匹配

暴力特征匹配

BF(Brute-Force),暴力特征匹配方法

原理

​ 它使用第一组中的每个特征的描述子,与第二组中的所有特征描述子进行匹配。计算它们之间的差距,然后将最接近一个匹配数据返回。

OpenCV特征匹配步骤

  • 创建匹配器,BFMatcher(normType, corssCheck)
  • 进行特征匹配,bf.match(des1, des2)
  • 绘制匹配点,cv2.drawMatches(img1, kp1, img2, kp2, ……)

API BFMatcher()

  • normType:NORM_L1, NORM_L2, HAMMING1……
  • crossCheck:是否进行交叉匹配,默认为false

API match(des1, des2)

  • 参数为SIFT、SURF、OBR等计算的描述子
  • 对两幅图的描述子进行计算

API drawMatches()

  • 搜索img, kp
  • 匹配img, kp
  • match()方法返回匹配结果
#46.img_bfmatch.py
import cv2

# 读文件
img1 = cv2.imread('.//src//opencv_search.png')
img2 = cv2.imread('.//src//opencv_orig.png')

# 灰度化
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# 创建sift对象
sift = cv2.xfeatures2d.SIFT_create()

# 进行检测
kp1, des1 = sift.detectAndCompute(g1, None)
kp2, des2 = sift.detectAndCompute(g2, None)

#  创建bf暴力匹配对象
bf = cv2.BFMatcher(cv2.NORM_L1)
match = bf.match(des1, des2)

img3 = cv2.drawMatches(img1, kp1, img2, kp2, match, None)

cv2.imshow('img3', img3)
cv2.waitKey(0)

1647511052505

  通过观察我们可以看到,大多数的特征点是可以一一对应、相互匹配到的,而对于一些匹配不到的特征点则是按照相似度最高进行匹配。

FLANN匹配

FLANN优缺点

  • 在进行批量特征匹配时,FLANN速度更
  • 由于它使用的是邻近近似值,所以精度较差

FLANN特征匹配步骤

  • 创建FLANN匹配器,FlannBasedMatcher(……)

  • 进行特征匹配,flann.match/knnMatch(……)

  • 绘制匹配点,cv2.drawMatches/drawMatchersKnn(……)

API FlannBasedMatcher(index_params, search_params)

  • index_params 字典: 匹配算法KDTREE、LSH

  • search_params 字典:指定KDTREE算法中遍历树的次数

  • KDTREE 设置方法

    • index_params = dict(algorithm = FLANN_INDEX_KDTREE, tree = 5) ,经验值:5 ,比较快
    • search_params = dict(checks = 50)

API knnMatch(des1, des2, k)

  • 参数为SIFT、SURF、ORB等计算的描述子
  • k,表示取欧式距离最近的前k个关键点

API DMatch()

  • distance,描述子之间的距离,值越低越好
  • queryIdx,第一个图像的描述子索引值
  • trainIdx,第二个图像的描述子索引值
  • imgIdx,第二图的索引值

API drawMatchesKnn()

  • 搜索img, kp
  • 匹配img, kp
  • match()方法返回匹配结果
# 47.img_flann.py
import cv2

# 打开两个文件
img1 = cv2.imread('.//src//opencv_search.png')
img2 = cv2.imread('.//src//opencv_orig.png')

# 灰度化
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# 他建SIFT特征检测器
sift = cv2.xfeatures2d.SIFT_create()

# 计算描述子与特征点
kp1, des1 = sift.detectAndCompute(g1, None)
kp2, des2 = sift.detectAndCompute(g2, None)

# 创建匹配器
index_params = dict(algorithm=1, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)

# 对描述子进行匹配计算
matchs = flann.knnMatch(des1, des2, k=2)

good = []
for i, (m, n) in enumerate(matchs):
    if m.distance < 0.7 * n.distance:
        good.append(m)

ret = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [good], None)
cv2.imshow('result', ret)
cv2.waitKey()

1647515861342

[实战]图像查找

特征匹配 + 单应性矩阵

对于同一个物体,从不同的方向看去,物体的同一点在不同的图像上位置不同,如planar_surface上的 X点在image1上为 x ,在image2上为 x' ,图像中的 H 则是图像与真实物体之间变换的单应性矩阵。

1647516188214

1647516217568

# 48.img_find.py
import cv2
import numpy as np

# 打开两个文件
img1 = cv2.imread('.//src//opencv_search.png')
img2 = cv2.imread('.//src//opencv_orig.png')

# 灰度化
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)

# 他建SIFT特征检测器
sift = cv2.xfeatures2d.SIFT_create()

# 计算描述子与特征点
kp1, des1 = sift.detectAndCompute(g1, None)
kp2, des2 = sift.detectAndCompute(g2, None)

# 创建匹配器
index_params = dict(algorithm=1, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)

# 对描述子进行匹配计算
matchs = flann.knnMatch(des1, des2, k=2)

good = []  # 存放角点
for i, (m, n) in enumerate(matchs):
    if m.distance < 0.7 * n.distance:
        good.append(m)

if len(good) >= 4:
    srcPts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
    dstPts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
    #  查找单应性矩阵
    H, _ = cv2.findHomography(
        srcPts, dstPts, cv2.RANSAC, 5.0)  # cv2.RANSAC  随机抽样一致算法

    h, w = img1.shape[:2]
    pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]
                     ).reshape(-1, 1, 2)
    dst = cv2.perspectiveTransform(pts, H)
	# 对找到的图像进行圈框
    cv2.polylines(img2, [np.int32(dst)], True, (0, 0, 255))
else:
    print('the number of good is less than 4.')
    exit()

ret = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [good], None)
cv2.imshow('result', ret)
cv2.waitKey()

1647517251425

左边的OpenCV logo在右侧图像中找到并全框起来。

[实战]图像拼接

1647667347552

  对于同一景色,从不同方向进行观测/拍照,得到两张图片则会有相关联的景色,如上图山体,那么将

这两张图像按照相同部分进行拼接,就可以得到一份不会重叠而又更加宽阔的景色,如下图。

1647667461737

图像合并的步骤

  • 读取文件并重置尺寸
  • 根据特征点和计算描述子,得到单应性矩阵
  • 图像变换
  • 图像拼接,并输出图像
# 49.img_stitch.py
# 整体思路
# 第一步,读取文件,将图片设置成一样大小640*480
# 第二步,找特征点,描述子,计算单应性矩阵
# 第三步,根据单应性矩阵对图像进行变换,然后平移★
# 第四步,拼接并输出最终结果
import cv2
import numpy as np

# 计算单应性矩阵
def get_homo(img1, img2):

    # 1. 创建特征转换对象
    # 2. 通过特征转换对象获得特征点和描述子
    # 3. 创建特征匹配器
    # 4. 进行特征匹配
    # 5. 过滤特征,找出有效的特征匹配点

    # 创建特征转换对象
    sift = cv2.SIFT_create()
    # 获取特征点和描述子
    k1, d1 = sift.detectAndCompute(img1, None)
    k2, d2 = sift.detectAndCompute(img2, None)

    # 创建特征匹配器
    bf = cv2.BFMatcher()
    matches = bf.knnMatch(d1, d2, k=2)
    # 过滤特征,找出有效的特征匹配点
    # 设置阈值
    verify_tatio = 0.8
    verify_matches = []
    for m1, m2 in matches:
        if m1.distance < verify_tatio * m2.distance:
            verify_matches.append(m1)
    # 设置最小匹配点个数
    min_match = 8
    if len(verify_matches) > min_match:
        img1_pts = []
        img2_pts = []

        for m in verify_matches:
            img1_pts.append(k1[m.queryIdx].pt)
            img2_pts.append(k2[m.trainIdx].pt)
        # [(x1,y1),(x2,y2),……]
        # [[x1,y1],[x2,y2],……]

        img1_pts = np.float32(img1_pts).reshape(-1, 1, 2)
        img2_pts = np.float32(img2_pts).reshape(-1, 1, 2)
        # 获取单应性矩阵
        H, mask = cv2.findHomography(img1_pts, img2_pts, cv2.RANSAC, 5.0)
        return H
    else:
        print('err:Not enough matches!')
        exit()


# 图像拼接
def stitch_img(img1, img2, H):

    # 1. 获得每张图片的四个角点
    # 2. 对图片进行变换(单应性矩阵使图进行旋转,平移)
    # 3. 创建一张大图,将两张图拼接到一起
    # 4. 将结果输出

    # 获得原始图的高/宽
    h1, w1 = img1.shape[:2]
    h2, w2 = img2.shape[:2]
    # 获得四个角点
    img1_dims = np.float32(
        [[0, 0], [0, h1], [w1, h1], [w1, 0]]).reshape(-1, 1, 2)
    img2_dims = np.float32(
        [[0, 0], [0, h2], [w2, h2], [w2, 0]]).reshape(-1, 1, 2)

    img1_transform = cv2.perspectiveTransform(img1_dims, H)

    result_dims = np.concatenate((img2_dims, img1_transform), axis=0)

    [x_min, y_min] = np.int32(result_dims.min(axis=0).ravel()-0.5)
    [x_max, y_max] = np.int32(result_dims.max(axis=0).ravel()+0.5)

    # 平移的距离
    transform_dist = [-x_min, -y_min]

    transform_array = np.array([[1, 0, transform_dist[0]],
                                [0, 1, transform_dist[1]],
                                [0, 0, 1]])
    # 投影变换
    result_img = cv2.warpPerspective(
        img1, transform_array.dot(H), (x_max-x_min, y_max-y_min))
    # cv2.imshow('1', result_img)

    # 图像拼接
    result_img[transform_dist[1]:transform_dist[1]+h2,
               transform_dist[0]:transform_dist[0]+w2] = img2

    return result_img


# 读取图像
img1 = cv2.imread('.//src//map1.png')
img2 = cv2.imread('.//src//map2.png')

# 修改尺寸
img1 = cv2.resize(img1, (640, 480))
img2 = cv2.resize(img2, (640, 480))

inputs = np.hstack((img1, img2))
# 获得单应性矩阵
H = get_homo(img1, img2)

# 进行图像拼接
result_img = stitch_img(img1, img2, H)
cv2.imshow('input', inputs)
cv2.imshow('result', result_img)
cv2.waitKey(0)

1647672304105

​ · ——学习ing 2022.03.17

OpenCV特征的场景

  • 图像搜索,如以图搜图
  • 拼图游戏
  • 图像拼接,将两长有关联的图拼接到一起

1647313977164

拼图方法

  1. 寻找特征
  2. 特征是唯一的
  3. 可追踪的
  4. 能比较的

总结

  • 平坦部分很难找到它在原图中的位置
  • 边缘相比平坦要好找一些,但也不能一下确定
  • 角点可以一下就能找到其在原图的位置

更新ing

十、图像分割

待学习...

十一、机器学习

待学习...

空文件

简介

自学OpenCV,实现车辆检测与计数,学习更新ing 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Python
1
https://gitee.com/KieranLRen/open-cv.git
git@gitee.com:KieranLRen/open-cv.git
KieranLRen
open-cv
OpenCV
master

搜索帮助