OpenCV入门

个人学习用,侵删
来源:各种帖子、视频课

OpenCV

【黑马程序员人工智能教程_10小时学会图像处理OpenCV入门教程-哔哩哔哩】

OpenCV简介

图像是什么: 图是物体反射或投射光的分布,像是人的视觉系统所接受的图在人脑中形成的印象、认识

模拟图像: 通过某种物理量的(光、电)的强弱变化来记录图像亮度信息。易受到干扰,不够清晰

数字图像: 亮度值用离散数值表示

8位图像(人眼对灰度的敏感度在16~32位之间)

灰度值 0~255, 0最黑255最白(理解为反射能量多少)

分类: 二值图像(0/1)、灰度图(256级灰度)、彩色图(rgb,8位uintt)

基本操作

  1. 读取图像
cv.imread()
# cv.IMREAD_COLOR 默认参数;cv.IMREAD_GRAYSCALE 灰度图;cv.IMREAD_UNCHANGED 包括alpha通道的加载图像模式
# 路径错误不会报错!!返回None
  1. 显示图像
cv.imshow("窗口名称", 要显示的图像 )
cv.waitKey(0) # 参数0永远等待

​ 也可用matplotlib展示

​ OpenCV存储用 b g r , plt是b g r,需要翻转

  1. 保存图像
cv.imwrite("文件名、保存路径", img)
  1. 绘制直线、圆、矩形、 文字
  2. 获取/修改像素点
px = img[100,100] # 返回三元组
blue = img[100,100,0] # 仅获取B通道值
img[100,100] = [0,0,255] # 改色
  1. 获取图像属性
img.shape # 形状 (200,200,3)200*200,3通道
img.dtype #数据类型
img.size # 像素数
  1. 通道拆分
b, g, r = cv.split( img ) # 拆分
img = cv2.merge(b, g, r ) # 合并
  1. 色彩空间改变
gray = cv2.cvtColor( img, cv.COLOR_BGR2GRAY) # 转Gray
hsv = cv2.cvtColor( img, cv.COLOR_BGR2HSV) # 转HSV(色相、饱和度、亮度)
  1. 图像的加法
# 能加的前提是像素一样
cv.add( img1, img2) # cv2 是饱和操作(超过255只取255)
# numpy是取模操作(超过255取余255)
  1. 图像混合
cv2.addWeighted( img1, 0.7, img2, 0.3, 0) # 0.7*img + 0.3*img2 + 0
# 同样要求像素相同

几何变换

  1. 缩放
cv2.resize( src, dsize, fx=0, fy=0, interpolation=cv2.INTER_LINEAR)
# 输入图像,
# 绝对尺寸(指定大小),
# 相对尺寸(dsize设为None,然后将fx和fy设置为比例因子(缩放倍数))
# interpolation插值方法:cv2.INTER_LINEAR双线性插值法 / cv2.INTER_NEAREST最近邻插值 / cv2.INTER_AREA像素区域重采样(默认) / cv2.INTER_CUBIC双三次插值
  • == TODO:这几种插值法都是什么原理?==
  1. 平移
cv2.warpAffine( img, M, dsize )
# M 2×3矩阵 np.float32类型的numpy数组
# 1 0 tx
# 0 1 ty 
# 平移方向、平移距离 (原点在左上角,右下是正方向)
# dsize 平移后输出图像的大小
  1. 旋转
    1. 要修改每个像素点的坐标:先根据旋转角度和旋转中心获取旋转矩阵
    2. 修正原点坐标:根据旋转矩阵进行变换
M = cv2.getRotationMatrix(center, angle, scale)
# 生成旋转矩阵:旋转中心坐标、旋转角度(而非弧度)、缩放比例

result = cv2.warpAffine( img, M, (w,h) )
# 进行旋转变换
  1. 仿射变换

平行的还平行,相连的仍相连。别的不一定(角度)

说一千道一万,就是原来的每个点都×一个变换矩阵

仿射变换矩阵 M = [A B],A是2×2的线性变换矩阵(改变形状),B是2×1的平移矩阵(改变位置)

M [ x y 1 ] = [ A B ] × [ x y 1 ] = A [ x y ] + B M\begin{bmatrix}x\\y\\1\end{bmatrix} =\begin{bmatrix}A&B\end{bmatrix}\times\begin{bmatrix} x\\y\\1\end{bmatrix}= A\begin{bmatrix} x\\y \end{bmatrix}+ B M xy1 =[AB]× xy1 =A[xy]+B

# 仿射变换
import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread("scene.jpg")

h, w, ch = img.shape

pts_before = np.float32([[50,50],[200,50],[50,200]])
pts_after  = np.float32([[100,100],[200,50],[100,250]])
# 需要三对仿射变换前后的点的坐标

M = cv2.getAffineTransform(pts_before, pts_after)
# 生成仿射变换矩阵M
rst = cv2.warpAffine( img, M, (w,h))
# 完成仿射变换

cv2.imshow("img",img)
cv2.imshow("rst",rst)
cv2.waitKey(0)
  1. 透射变换

就是投影
投影中心、原图像平面、新图像平面
通用变换公式:
[ x ′ y ′ z ′ ] = [ u v w ] [ a 00 a 01 a 02 a 10 a 11 a 12 a 20 a 21 a 22 ] \left[\begin{array}{lll} x^{\prime} & y^{\prime} & z^{\prime} \end{array}\right]=\left[\begin{array}{lll} u & v & w \end{array}\right]\left[\begin{array}{lll} a_{00} & a_{01} & a_{02} \\ a_{10} & a_{11} & a_{12} \\ a_{20} & a_{21} & a_{22} \end{array}\right] [xyz]=[uvw] a00a10a20a01a11a21a02a12a22
其中, (u, v) 是原始的图像像素坐标, w 取值为1,
( x = x ′ / z ′ , y = y ′ / z ′ ) \left(x=x^{\prime} / z^{\prime}, y=y^{\prime} / z^{\prime}\right) (x=x/z,y=y/z)
是透射变换后的结果。后面的矩阵称为透视变换矩阵,一般情况下,我们将其分为三部分:
T = [ a 00 a 01 a 02 a 10 a 11 a 12 a 20 a 21 a 22 ] = [ T 1 T 2 T 3 a 22 ] T=\left[\begin{array}{lll} a_{00} & a_{01} & a_{02} \\ a_{10} & a_{11} & a_{12} \\ a_{20} & a_{21} & a_{22} \end{array}\right]=\left[\begin{array}{ll} T 1 & T 2 \\ T 3 & a_{22} \end{array}\right] T= a00a10a20a01a11a21a02a12a22 =[T1T3T2a22]
其中:T1表示对图像进行线性变换,T2对图像进行平移,T3表示对图像进行投射变换, a_{22} 一般设为1.

在opencv中找到4个点(任意3个不共线),通过cv.getPerspectiveTransform获取变换矩阵T, 将cv.warpPerspective应用于此3*3矩阵

import numpy as np 
import cv2 as cv

# 1 read the image
img = cv.imread("")

# 2 透射变换
rows, cols = img.shape[:2]
# 透射前 点的坐标
pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])  
# 透射后
pts2 = np.float32([[100,145],[300,100],[80,290],[310,300]]) # 透射后
T = cv.getPerspectiveTransform(pts1,pts2)
dst = cv.warpPerspective(img,T,(cols,rows))

# 3 图像显示
  1. 图像金字塔
    好像就是图像分辨率的提升/降低
# 上采样 就是提升分辨率
up_img = cv.pyrUp(img)
# 下采样 就是降低分辨率
down_img = cv.pyrDown(img)

形态学

连通性

连通性:位置相邻,灰度值满足特定的相似性准则

4 邻接: 上下左右, N4§ -> 4连通
D邻接:4个对角, ND§
8 邻接:全8个, N8§ -> 8连通

m连通:要么4邻接,要么D邻接且二者4领域不满足相似性准则

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

形态学操作

  1. 腐蚀:
    蚕食高亮部分
    消除物体边界点,使目标缩小,可以消除小于结构元素的噪声点。
    用结构元素与图像做匹配(与),匹配成功的中心设为高亮
  2. 膨胀:
    扩张高亮部分
    同上(或操作,模版/原图有一处是高亮则高亮)
# 1 读取图像

# 2  创建5*5全1的核结构
kernel = np.ones((5,5), np.uint8)

# 3 图像腐蚀和膨胀
erosion = cv.erode(img, kernel) 
dilate = cv.dilate(img, kernel)
# 可以在这两个函数后面再传入一个参数iterations表示腐膨几轮
  1. 开闭运算
    将膨胀和腐蚀按照一定的次序进行处理
    此二者不可逆(先开后闭不能得到原图像)
    1. 开运算:先腐蚀后膨胀,能消除噪点,去除小干扰快
    2. 闭运算:先膨胀后腐蚀,消除物体里面的空洞,填充闭合区域
cv.morphologyEx(img, op, kernel)
# op:处理方式,开cv.MORPH_OPEN,闭cv.MORPH_CLOSE
# kernel 核结构
  1. 礼帽和黑帽
    1. 礼帽:原图 - 开运算结果
      分离外部亮点
    2. 黑帽:闭运算结果 - 原图
      分离内部暗点
# 还是上面开闭运算的函数,op取
# cv.MORPH_TOPHAT:礼帽
# cv.MORPH_BLACKHAT:黑帽

图像噪声

  1. 椒盐噪声(脉冲噪声):随机出现的白点or黑点。可能是影像讯号收到突然的干扰,如数位转换器or位元传输错误等(失效的感应器导致像素值为最小,饱和的感应器导致像素值为最大)。
  2. 高斯噪声:噪声密度函数(各种灰度值的概率)服从高斯分布(正态分布)。

图像平滑

去除高频信息,保留低频信息

  1. 均值滤波
    其实就是用一个区域里点值的平均代替其中心点的值
    优点:算法简单计算快
    缺点:丢失细节
# cv.blur(src, ksize, anchor, borderType)
# src: 输入图像
# ksize:卷积核的大小
# anchor:默认(-1,-1)表示核中心
# borderType:边界类型

# 1 输入图像
img = cv.imread("")
# 2 均值滤波
blur = cv.blur(img, (5,5))
# 3 图像显示,略
  1. 高斯滤波
    理论有点复杂,没太明白,先学会用吧
    均值滤波算术平均,高斯滤波加权平均(重点在权重矩阵怎么算)
  • TODO: 这块算法流程都没搞明白
# cv2.GaussianBlur(src, ksize, sigmax, sigmay, borderType)
# ksize 高斯卷积核的大小,长宽均为奇数,可不同
# sigmaX/sigmaY:两个方向上的标准差。Y方向上默认为0,表示与sigmaX相同
# borderType:填充边界类型
  1. 中值滤波
    中值滤波是一种典型的非线性滤波技术,基本思想是用像素点领域灰度值的中值来代替该点的灰度值。
    对椒盐噪声尤其有用,因其不受领域内极大极小值的干扰
# cv.medianBlur(src, ksize)

直方图

灰度直方图

dims:需要统计的特征数目(灰度)
bins:子区段的数目(组数)
range:要统计特征的取值范围

# cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])

# images,原图像 传入函数时用[]括起来 [img] 
# channels,灰度图[0] 彩色图BGR对应[0][1][2] 
# mask,掩模图像 用于设置要统计的区域 
# histSize,bins 也要用[]
# range, [0,255]

histr = cv.calcHist( [img], [0], None, [256], [0,256])

掩膜

类似掩码,要得到的置1

# 创建蒙版
mask = np.zeros(img.shape[:2], np.uint8) # 用图像的长宽
mask[100:200, 300:400] = 255 # =1也可
# 掩膜
masked_img = cv.bitwise_and(img, img, mask=mask)
# 做直方图
mask_histr = cv.calcHist([img],[0],mask,[256],[0,256])

均衡化

把小范围集中的分散
像素分布广-> 对比度高
在曝光不足or过度的图像中可以更好地突出细节

dst = cv.equalizeHist(img)

自适应均衡化

把整幅图分成很多tiles(opencv默认每个tile大小8*8),然后分别均衡化再用双线性差值拼接。
具体算法复杂,暂且知道what就行。

  • TODO: 自适应均衡化具体怎么做的?
# cv.createCLAHE(clipLimit, tileGridSize)
# clipLimit 对比度限制,默认40
# tileGridSize 分块的大小,默认8*8

# 创建自适应均衡化的对象
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
# 应用于图像
cl1 = clahe.apply(img)

边缘检测

  • 基于搜索:通过寻找图像一阶导数最大值来检测边界,然后利用计算结果估计边缘的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值。代表算法:Sobel算子、Scharr算子
  • 基于零穿越:寻找二阶导数变号零点(即图像拐点)。待变算法:Laplacian算子

Sobel检测算子

离散函数的一阶导数用下代替:
f ′ ( x ) = f ( x + 1 ) − f ( x − 1 ) 2 f'(x) =\frac{f(x+1)-f(x-1)}{2} f(x)=2f(x+1)f(x1)
对要处理的图像在两个方向上求导

水平变化:将图像与奇数大小的模板进行卷积,结果为Gx
G x = [ − 1 0 1 − 2 0 2 − 1 0 1 ] ∗ I G_x=\begin{bmatrix} -1 & 0 & 1\\ -2 & 0 & 2\\ -1 & 0 & 1 \end{bmatrix} * I Gx= 121000121 I
垂直变化同理
G y = [ − 1 − 2 − 1 0 0 0 1 2 1 ] ∗ I G_y=\begin{bmatrix} -1 & -2 & -1\\ 0 & 0 & 0\\ 1 & 2 & 1 \end{bmatrix} * I Gy= 101202101 I
结合以上两个结果求出
G = G x 2 + G y 2 G=\sqrt{G_x^{2} + G_y^{2} } G=Gx2+Gy2
统计极大值所在的位置,就是图像的边缘

当内核大小为3时,Sobel内核可能产生比较明显的误差。为解决此问题,采用Scharr函数,与Sobel一样块,结果更精准。但该函数仅作用于大小为3的内核。

其计算方法为:
G x = [ − 3 0 3 − 10 0 10 − 3 0 3 ] ∗ I G_x = \begin{bmatrix} -3 & 0 & 3\\ -10 & 0 & 10\\ -3 & 0 & 3 \end{bmatrix} * I Gx= 31030003103 I

G y = [ − 3 − 10 − 3 0 0 0 3 10 3 ] ∗ I G_y = \begin{bmatrix} -3 & -10 & -3\\ 0 & 0 & 0\\ 3 & 10 & 3 \end{bmatrix} * I Gy= 30310010303 I

  • TODO: 为什么他俩卷积就是导数了?
  • TODO:这个核是怎么算出来的?
# Sobel边缘检测

# cv.Sobel( src, ddepth, dx, dy, dst, ksize, scale, delta, borderType )
# src 传入图像
# ddepth 图像的深度(?)
# dx、dy 求导的阶数,0表示在这个方向上没有求导
# ksize 卷积核Sobel算子的大小,必须为奇数1357,默认3。如果ksize=-1自动变为3就是利用Scharr算子
# scale 缩放系数,默认无缩放
# borderType 图像边界的模式,默认cv2.BORDER_DEFAULT
# Sobel函数求导后有负值/大于255的值,要先使用16位有符号整型,cv2.CV_16S。处理完后再使用cv2.convertScaleAbs()函数将其转回原来的uint8格式。Sobel算子是在两个方向上计算的,最后还要用cv2.addWeighted()将图像混合

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread("", cv.IMREAD_GRAYSCALE)
# 计算Sobel卷积结果
x = cv.Sobel(img, cv.CV_16S, 1, 0)
y = cv.Sobel(img, cv.CV_16S, 0, 1)
# 数据转换
Scale_absX = cv.convertScaleAbs(x)
Scale_absY = cv.convertScaleAbs(y)
# 结果混合
result = cv.addWeighted(Scale_absX, 0.5, Scale_absY, 0.5, 0)
# 图像显示 
plt.figure(figsize=(10,8), dpi=100)
plt.subplot(121),
plt.imshow(img, cmap=plt.cm.gray)
plt.title('原图')
plt.xticks([])
plt.yticks([])
plt.subplot(122),
plt.imshow(result, cmap=plt.cm.gray)
plt.title('Sobel滤波后结果')
plt.xticks([])
plt.yticks([])
plt.show()

Laplacian算子

利用二阶导数来检测边缘:
Δ s r c = ∂ 2 s r c ∂ x 2 + ∂ 2 s r c ∂ y 2 \Delta src= \frac{\partial^2src}{\partial x^{2}}+\frac{\partial^2src}{\partial y^{2}} Δsrc=x22src+y22src
对于不连续函数的二阶导数:
f ′ ′ ( x ) = f ′ ( x + 1 ) − f ′ ( x ) = f ( x + 1 ) + f ( x − 1 ) − 2 f ( x ) f''(x)=f'(x+1)-f'(x)=f(x+1)+f(x-1)-2f(x) f′′(x)=f(x+1)f(x)=f(x+1)+f(x1)2f(x)
使用的卷积核:
k e r n e l = [ 0 1 0 1 − 4 1 0 1 0 ] kernel=\begin{bmatrix} 0 & 1 &0 \\ 1 & -4 & 1\\ 0 & 1 &0 \end{bmatrix} kernel= 010141010

  • TODO:依然不懂卷积核这块
# laplacian = cv2.Laplacian( src, ddepth, ksize)
# ddepth:图像的深度,-1表示采用原图像相同的深度。目标图像必须大于等于原图像的深度
# ksize:卷积核大小,奇数1357

result = cv.Laplacian(img, cv.CV_16S)
Scale_abs = cv.convertScaleAbs(result)

Canny边缘检测

被认为是最优的边缘检测算法

  • Step1:噪声去除。使用5*5高斯滤波器去除噪声

  • Step2:计算图像梯度。利用Sobel算子计算水平/竖直方向的一阶导数,再找到边界的梯度和方向(高数内容)
    G r a d i e n t ( G ) = G x 2 + G y 2 Gradient(G) = \sqrt{G_x^2+G_y^2} Gradient(G)=Gx2+Gy2

    A n g l e ( θ ) = a r c t a n ( G y G x ) Angle(θ)=arctan(\frac{G_y}{G_x}) Angle(θ)=arctan(GxGy)

    如果某个像素点是边缘,则其梯度方向(垂直、水平、2个对角线)总与边缘垂直。

  • Step3:非极大值抑制。获得梯度的方向和大小之后,对整幅图进行扫描,去除非边界上的点,对每一个像素进行检查,判断该点是否为周围具有相同梯度方向中的点中最大的。

    • TODO:这一步没懂
  • Step4:滞后阈值。当图像的灰度值>maxVal时认定为边界,<minVal时被抛弃,介于二者之间查看其是否与某个被确定为边界的点相连。

# canny = cv2.Canny(image, threshold1, threshold2)
# threshold: minVal和maxVal

模板匹配

将模板从上往下、从左至右移动,每次移动一个像素,计算模板和原图对应区域的相似程度。

# result = cv2.matchTemplate(img, template, method)
# method: 
# 1. CV_TM_SQDIFF 平方差匹配:计算模板与图像之间的平方差,最好为0,值越大匹配度越差
# 2. CV_TM_CCORR 相关匹配:利用模板与图像间的乘法进行匹配,数值越大匹配程度越高
# 3. CV_TM_CCOEFF 相关系数匹配:1表示完美匹配,-1表示最差匹配

# 完成匹配后,使用cv2.minMaxLoc()方法查找最大/小值所在位置即为最佳匹配位置

模板匹配不适用于尺度变换、视角变换、旋转变化、光照变化之后的图像。此时要使用关键点匹配算法(如SIFT、SURF),先通过关键点检测算法获取模板和测试图片中的关键点,然后使用关键点匹配算法处理。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('ori.jpg')
temp  = cv.imread('temp.jpg')
h,w = temp.shape[:2]

# 相关系数匹配,1最好,-1最差
res = cv.matchTemplate(img, temp, cv.TM_CCOEFF)
# 最大最小值及其位置
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)

top_left = max_loc
bottom_right = (top_left[0]+w, top_left[1]+h)
cv.rectangle(img, top_left, bottom_right, (255,0,255), 2)

plt.imshow(img[:,:,::-1]) # 实现左右翻转(bgr变成rgb)
plt.title('Template Matching')
plt.xticks([])
plt.yticks([])
plt.show()

霍夫变换

提取图像中的直线&圆

https://www.cnblogs.com/php-rearch/p/6760683.html

笛卡尔坐标系的点成线 -> 霍夫坐标系的线交点 -> 极坐标系的线交点

在极坐标系中表示直线的方程为
ρ = x c o s θ + y s i n θ \rho = xcos\theta +ysin\theta ρ=xcosθ+ysinθ
遍历图中的点(x,y),每次linguisti令θ从0变化到180度,求对应ρ。最终出现次数最多的ρθ组合就可求出对应直线方程

霍夫线检测

边缘检测获取一些点 -> 每个点都对应一条ρ、θ的曲线(ρ=xcosθ+ysinθ) -> ρ、θ坐标系里图像的每个交点都对应笛卡尔坐标系里的一条直线

# cv.HoughLines(img, rho, theta, threshold)
# img
# rho\theta:ρ和θ的精确度
# threshold:阈值,只有累加器中的值高于此,才能被认为是直线
import numpy as np
import random 
import cv2 as cv
import matplotlib.pyplot as plt

img = cv.imread("")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 边缘检测
edges = cv.Canny(gray, 50, 150)
# 霍夫直线检测
lines = cv.HoughLines(edges,, 0.8, np.pi/180, 150)
# 图像显示(极坐标转笛卡尔坐标)
for line in lines:
	rho, theta = line[0]
	a = np.cos(theta)
	b = np.sin(theta)
	x0 = a * rho
	y0 = b * rho 
	x1 = int(x0 + 1000*(-b))
	y1 = int(y0 + 1000*(a))
	x2 = int(x0 - 1000*(-b))
	y2 = int(y0 - 1000*(a))
    cv.line(img, (x1,y1), (x2,y2), (0,255,0))
plt.figure(figsize=(10,8),dpi=100)
plt.imshow(img[:,:,::-1])
plt.title("Hough Lines")
plt.xticks([])
plt.yticks([])
plt.show()	 
  • TODO:画图坐标x1y1x2y2这里是对的吗?跑一下验证

霍夫圆检测

OpenCV-Python教程:霍夫变换~圆形(HoughCircles) – 桔子code (juzicode.com)

太复杂了(捂脸)

标准霍夫圆检测法是在(r,a,b)组成的三维空间累加器上进行,效率低(直线是ρθ二维)。

opencv使用霍夫梯度法进行圆形检测

  • step1:检测圆心。圆心是法线的交汇处,设置一个阈值,在某点相交的直线条数大于这个阈值就认为该交点处为圆心
  • step2:利用圆心推导出半径。圆心到圆周上各点距离是相同的,设置一个阈值,只要相同距离的数量大于该阈值,就认为该距离是该圆心的半径

原则上霍夫变换可以检测任何形状,但复杂的形状需要的参数多,霍夫空间维数多,不太好。霍夫梯度法可以有效减少维度,提高效率

# cv2.HoughCircles(image, method, dp, minDist, param1=100, param2=100, minRadius=0, maxRadius=0)
# image:8bit单通道图像
# method:使用霍夫变换圆检测算法,当前有cv2.HOUGH_GRADIENT和cv2.HOUGH_GRADIENT_ALT 2种方法,后者是前者的改进方法。
# dp:检测圆心的累加器精度和图像精度比的倒数,比如dp=1时累加器和输入图像有相同的分辨率,dp=2时累加器是输入图像一半大的宽高;method=cv2.HOUGH_GRADIENT_ALT时推荐设置dp=1.5。
# minDist:圆心之间的最小距离。如果两个圆心之间距离太小就视为一个圆心
# param1:特定方法参数,和method配合;当method=cv2.HOUGH_GRADIENT或method=cv2.HOUGH_GRADIENT_ALT时,该参数是canny检测的高阈值,低阈值是该参数的一半;method=cv2.HOUGH_GRADIENT_ALT时,内部使用Scharr计算图像梯度,这个值通常要设置得更大。
# param2:检测圆心和确定半径时所共有的阈值
# minRadius:检测圆半径的最小值
# maxRadius
# 返回:输出圆向量(圆心横坐标,圆心纵坐标,圆半径),是一个三维数组

由于霍夫圆检测对噪声敏感,故首先要对图像进行中值滤波

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

# 读取图像
img = cv.imread("circle.jpg")
# 转换为灰度图像
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 进行中值模糊,去噪点
gray_blurred = cv.medianBlur(gray, 7)
# 霍夫圆检测
circles = cv.HoughCircles(gray_blurred, cv.HOUGH_GRADIENT, 1, 200, param1=100, param2=50, minRadius=0, maxRadius=100)

# 确保检测到了圆
if circles is not None:
    # 四舍五入成int(画圆要int)
    circles = np.round(circles[0, :]).astype("int")
    for (x, y, r) in circles:
        # 绘制圆
        cv.circle(img, (x, y), r, (0, 255, 0), 2)
        # 绘制圆心
        cv.circle(img, (x, y), 2, (0, 255, 0), -1)

# 使用matplotlib显示图像
plt.figure(figsize=(10, 8), dpi=100)
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))  # 注意这里使用cv.cvtColor转换颜色通道
plt.title("Hough Circle")
plt.xticks([])
plt.yticks([])
plt.show()

cv.imread读进来的是三通道(bgr),cvtColor成gray后是单通道,画圆、降噪要在gray上操作,但输出图像时要三通道的图(rgb)。这就是为什么读入图像后还要倒腾两遍

角点检测

三维场景重建运动估计、目标跟踪、目标识别、图像配准与匹配

Harris算法

Harris角点检测

思想:通过图像的局部小窗口观察图像。角点的特征是窗口沿任意方向移动都会导致图像灰度的明显变化

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

二元函数 ( f(x, y) ) 在点 ( (a, b) ) 处的泰勒展开公式可以写成如下形式:
f ( x , y ) = f ( a , b ) + ∂ f ∂ x ( a , b ) ( x − a ) + ∂ f ∂ y ( a , b ) ( y − b ) + 1 2 ! [ ∂ 2 f ∂ x 2 ( a , b ) ( x − a ) 2 + 2 ∂ 2 f ∂ x ∂ y ( a , b ) ( x − a ) ( y − b ) + ∂ 2 f ∂ y 2 ( a , b ) ( y − b ) 2 ] + ⋯ f(x, y) = f(a, b) + \frac{\partial f}{\partial x}(a, b)(x-a) + \frac{\partial f}{\partial y}(a, b)(y-b) + \frac{1}{2!}\left[ \frac{\partial^2 f}{\partial x^2}(a, b)(x-a)^2 + 2\frac{\partial^2 f}{\partial x \partial y}(a, b)(x-a)(y-b) + \frac{\partial^2 f}{\partial y^2}(a, b)(y-b)^2 \right] + \cdots f(x,y)=f(a,b)+xf(a,b)(xa)+yf(a,b)(yb)+2!1[x22f(a,b)(xa)2+2xy2f(a,b)(xa)(yb)+y22f(a,b)(yb)2]+

对于图像中的一个点 (x, y) ,当图像在该点周围移动一个小的位移向量 (u, v) 时,自相关函数(能量函数)C(u, v) 可以通过计算移动前后图像灰度的变化来得到。这个函数可以表示为:
C ( u , v ) = ∑ x , y w ( x , y ) [ I ( x + u , y + v ) − I ( x , y ) ] 2 C(u, v) = \sum_{x, y} w(x, y) \left[ I(x+u, y+v) - I(x, y) \right]^2 C(u,v)=x,yw(x,y)[I(x+u,y+v)I(x,y)]2

其中, w(x, y) 是一个窗口函数(通常是一个高斯窗口),用于给图像的不同区域分配不同的权重,I(x, y) 是点(x, y) 的灰度值。

为了简化计算,我们对( I(x+u, y+v) )进行Taylor展开,只考虑一阶项和二阶项:
I ( x + u , y + v ) ≈ I ( x , y ) + u I x + v I y I(x+u, y+v) \approx I(x, y) + u I_x + v I_y \\ I(x+u,y+v)I(x,y)+uIx+vIy
将上述展开式代入自相关函数,并对其进行简化,可以得到一个关于位移向量 (u, v) 的二次函数:
C ( u , v ) = ∑ x , y w ( x , y ) [ I ( x + u , y + v ) − I ( x , y ) ] 2 ≈ w ( x , y ) ∑ x , y [ u I x + v I y ] 2 = [ u v ] ⋅ w ( x , y ) ∑ x , y [ I x 2 I x I y I x I y I y 2 ] ⋅ [ u v ] = [ u v ] ⋅ M ⋅ [ u v ] \begin{align} C(u, v) & = \sum_{x, y} w(x, y) \left[ I(x+u, y+v) - I(x, y) \right]^2 \\ & \approx w(x, y) \sum_{x, y} \left [u I_x+v I_y \right ]^2 \\ &= \begin{bmatrix} u & v \end{bmatrix} \cdot w(x, y) \sum_{x, y} \begin{bmatrix} I_x^2 & I_xI_y \\ I_xI_y & I_y^2 \end{bmatrix} \cdot \begin{bmatrix} u \\ v \end{bmatrix}\\ &= \begin{bmatrix} u & v \end{bmatrix} \cdot M \cdot \begin{bmatrix} u \\ v \end{bmatrix} \\ \end{align} C(u,v)=x,yw(x,y)[I(x+u,y+v)I(x,y)]2w(x,y)x,y[uIx+vIy]2=[uv]w(x,y)x,y[Ix2IxIyIxIyIy2][uv]=[uv]M[uv]
其中,(写成分块矩阵的方式是方便后面计算)
M = w ( x , y ) ∑ x , y [ I x 2 I x I y I x I y I y 2 ] = [ A B B C ] M = w(x, y) \sum_{x, y} \begin{bmatrix} I_x^2 & I_xI_y \\ I_xI_y & I_y^2 \end{bmatrix}\\ = \begin{bmatrix} A & B \\ B & C \end{bmatrix} M=w(x,y)x,y[Ix2IxIyIxIyIy2]=[ABBC]
C为二次型,M为实对称矩阵。可对C进行标准化:
C ( u ′ , v ′ ) = [ u ′ v ′ ] ⋅ [ λ 1 0 0 λ 2 ] ⋅ [ u ′ v ′ ] 即: C ( u ′ , v ′ ) = λ 1 u ′ 2 + λ 2 v ′ 2 C(u',v')= \begin{bmatrix} u' & v' \end{bmatrix} \cdot \begin{bmatrix} \lambda_1 & 0 \\ 0 & \lambda_2 \end{bmatrix} \cdot \begin{bmatrix} u' \\ v' \end{bmatrix} \\ 即:C(u',v')=\lambda_1u'^2+\lambda_2v'^2 C(u,v)=[uv][λ100λ2][uv]即:C(u,v)=λ1u′2+λ2v′2

  • TODO:从这步开始就跟不上思路了

化简到这一步就和(u,v),也就是方向的关系没那么大了。虽然最初(u,v)只能表示一个方向,但引入特征值λ后C的变换主要受特征值λ影响

正定二次型,特征值λ一定是正数

平坦区域:两个特征值都小,且近似相等,能量函数在各个方向上都较小;
边缘区域:一个特征值大,另一个特征值小,能量函数在某一方向上增大,其他方向较小;
角点区域:两个特征值都大,且近似相等,能量函数在所有方向上都增大。

这样一来,我们就可以仅通过矩阵M的特征值,来评估图像是否存在角点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但Harris角点的计算方法甚至不需要用到特征值,只需要计算一个Harris响应值R
R = d e t ( M ) − k ( t r a c e ( M ) ) 2 其中, d e t ( M ) = λ 1 λ 2 是矩阵 M 的行列式,代表特征值的乘积 t r a c e ( M ) = λ 1 + λ 2 是矩阵 M 的迹,代表特征值的和 k 是一个常数,用于调整行列式和迹的相对重要性。一般取 0.04 − 0.06 R = det(M) - k(trace(M))^2\\ \begin{align} &其中,det(M) = \lambda_1 \lambda_2 是矩阵 M 的行列式,代表特征值的乘积\\ &trace(M) = \lambda_1 + \lambda_2 是矩阵M 的迹,代表特征值的和\\ &k 是一个常数,用于调整行列式和迹的相对重要性。一般取0.04-0.06 \end{align} R=det(M)k(trace(M))2其中,det(M)=λ1λ2是矩阵M的行列式,代表特征值的乘积trace(M)=λ1+λ2是矩阵M的迹,代表特征值的和k是一个常数,用于调整行列式和迹的相对重要性。一般取0.040.06

此公式是创造出来的,因其能反映:上述矩阵M的特征值 和 图像是否存在角点 2的关系

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • TODO:这里好像涉及到最优化问题,熟悉又陌生,后面专门看一下
  • TODO:如果是矩形窗口的话,平行于窗口边缘的直线岂不是检测不出来?哦对,所以需要w(x,y)正态分布
代码实现
# dst = cv2.cornerHarris(src, blockSize, ksize, k)
# src:数据类型为float32的输入图像
# blockSize:角点检测的邻域大小
# ksize:sobel求导使用的核的大小
# k:0.04~0.06
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread("")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 输入图像必须是float32类型
gray = np.float32(gray)
# 角点检测
dst = cv.cornerHarris(gray, 2, 3, 0.04)
# 设置阈值,将角点绘制出来(把满足条件的点涂红色)
img[dst>0.001*dst.max()] = [0, 0, 255]
# 图像显示
plt.figure(figsize=(10, 8), dpi=100)
plt.title("Harris角点检测")
plt.xticks([])
plt.yticks([])
plt.show()

Shi-Tomas算法

在Harris的R响应函数基础上,若两个特征值中较小的一个大于最小阈值,则会得到强角点。

Shi-Tomasi 发现角点的稳定性其实和矩阵 M 的较小特征值有关,于是直接用较小的那个特征值作为分数。这样就不用调整k值了。如果矩阵M的两个特征值中较小的那一个大于设定的阈值,那么这个点是角点;如果两个特征值都小于阈值,那较小的特征值也必定小于阈值,这个点就是平坦区域的点;如果其中一个特征值大于阈值而另外一个特征值小于阈值,那么这个点就是边缘点。所以我们只需要判断矩阵M的两个特征值中较小的那一个特征值是否是大于阈值,如果大于阈值这个点就是强角点。

# corners = cv2.goodFeaturesToTrack( image, maxCorners, qualityLevel, minDistance)
# image:输入灰度图像
# maxCorners:获取角点数目最大值
# qualityLevel:最低可接受的角点质量水平,0-1之间
# minDistance:角点之间最小欧式距离,太近视为一个点
# corners:返回搜索到点的坐标(按质量排序)

.ravel() 将多维数组压平成一维

SIFT/SURF算法

Harris和Shi-Tomasi具有旋转不变性,但不具备尺度不变性

SIFT:Scale-invariant feature transform

  • TODO:跳过了,这也太复杂了

视频

基本视频操作

  1. 读取并播放
cap = cv2.VideoCapture( filepath )
# 创建VideoCapture对象,用于捕获视频

retval = cap.get(propId)
# 获取视频某些属性  proId:0~18
# 0:播放到第几秒了
# 1:播放到哪一帧了
# 2:从0到1播放的相对位置
# 3:视频流的帧宽度
# 4:视频流的帧高度
# 5:帧率
# 6:编解码四字符代码(avi?mp4?)
# 7:视频文件的帧数

cap.set( propId, value )
# 修改视频的某些属性

flag = cap.isOpened()
# 判断是否读取成功

ret, frame = cap.read()
# 获取视频的下一帧
# ret:True/ False,表示获取成功还是失败
# Frame:获取到的帧

cap.release()
# 将视频释放
# 实例
import cv2 as cv

cap = cv.VideoCapture("bike.mp4")

while ( cap.isOpened() ):
    ret, frame = cap.read()
    if ret == True:
        cv.imshow("frame", frame )
    if cv.waitKey(25) & 0xFF == ord("q"):
        break

cap.release()
cv.destroyAllWindows()

cv.waitKey(25) & 0xFF == ord("q")

& 0xFF的按位与操作只取cv2.waitKey(1)返回值最后八位,因为有些系统cv2.waitKey(1)的返回值不止八位

ord(‘q’)表示q的ASCII值

总体效果:按下q键后break

  1. 保存
out = cv2.VideoWriter( filename, fourcc, fps, frameSize )
# fourcc:指定视频编解码器的四字节代码
# fps:帧率
# frameSIze:帧大小

# 其中,fourcc由如下函数设置
retval = cv2.VideoWriter_fourcc( c1, c2, c3, c4)
# 参数分别是4个字符
# win中,DIVX(.avi)
# os中,MJPG(.mp4), DIVX(.avi), X264(.mkv)
# 在OpenCV 4.8.0.76版本中,cv2.VideoWriter_fourcc函数已被废弃,取而代之的是cv2.VideoWriter.fourcc属性。

参数前的单* 可将参数以元组形式传入

(*'MJPG') 也就相当于('M', 'J', 'P', 'G' )

# 例子
import cv2 as cv
import numpy as np 

cap = cv.VideoCapture( "bike.mp4" )

frame_width, frame_height = int(cap.get(3)), int(cap.get(4))

output = cv.VideoWriter( 'output.avi', cv.VideoWriter.fourcc( *'MJPG' ), 10, (frame_width, frame_height) )

while( output.isOpened() ):
    ret, frame = cap.read()
    if ret == True:
        output.write(frame)
    else:
        break

cap.release()
output.release()
cv.destroyAllWindows()

视频追踪

ROI:region of interest 感兴趣区域、目标区域

Meanshift

均值漂移算法

原理: 移动窗口到点集密度最大的区域当中。通过窗口内所有点计算出重心,每次将窗口形心移动至重心处。(这个操作看起来很合乎常识,不过在数学上它是可以被证明的,而且证明过程比想象中复杂一丢丢)(opencv中的meanshift严格意义上来说是最简化版本的算法,结合着数学原理讲反而不好理解)

img

meanshift算法除了应用在视频追踪当中,在聚类平滑等等各种涉及到数据以及非监督学习的场合当中均有重要应用。

步骤:

  1. 读取视频文件
  2. 设置ROI:在第一帧图像上设置ROI
  3. 计算直方图:计算ROI的HSV直方图,并进行归一化
  4. 目标追踪:设置窗口搜索停止条件,直方图反向投影,进行目标追踪,并在目标位置绘制矩形框
ret, track_window = cv2.meanShift( probIamge, window, criteria )
# probImage: ROI,即目标的直方图反向投影(cv.calcBackProject计算得到)
# window:初始搜索窗口
# criteria:终止条件(最大迭代次数、窗口中心飘移最大距离)
# ret:

  • TODO:看了很多文章,还是不能很好地理解反向直方图的作用
  • TODO:当然了,meanshift的数学原理也没太看懂
import cv2 as cv

# 1 获取视频
cap = cv.VideoCapture("ball2.mp4")

# 2 获取第一帧图像,指定ROI
ret, frame = cap.read()
frame_height = cap.get(4)
frame_weight = cap.get(3) # 3宽4高
# ROI
r, c, h, w = int(frame_height * 0.8), 30, int(frame_height*0.2),  int(frame_weight * 0.2) # 这里我不知道怎么找第一帧小球的位置,只能估摸着来
track_window = (c, r, w, h) # 下面meanshift追踪要用
roi = frame[r:r+h, c:c+h]

# 3 计算直方图
# 3.1 转换色彩空间
hsv_roi = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
# 3.2 去除低亮度的值
# 3.3 计算直方图
roi_hist = cv.calcHist( [hsv_roi], [0], None, [180], [0,180])
# 3.4 归一化
cv.normalize(roi_hist, roi_hist, 0, 255, cv.NORM_MINMAX)

# 4 目标追踪
# 4.1 设置窗口搜索终止条件: 最大迭代次数,窗口中心漂移最小值
term_crit = ( cv.TERM_CRITERIA_EPS | cv.TERM_CRITERIA_COUNT, 10, 1 )

while( True ):
    # 4.2 对每一帧处理
    ret, frame = cap.read()
    if ret :
        # 4.3 对每一帧计算直方图的反向投影
        hsv = cv.cvtColor( frame, cv.COLOR_BGR2HSV )
        dst = cv.calcBackProject([hsv], [0], roi_hist, [0,100], 1)
        # 4.4 再进行meanshift追踪
        ret, track_window = cv.meanShift( dst, track_window, term_crit )
        # 4.5 将追踪的位置绘制在视频上,展示
        x, y, w, h = track_window
        img2 = cv.rectangle( frame, (x,y), (x+w,y+h), 255, 2 )
        cv.imshow('frame', img2)
        if cv.waitKey(60) & 0xFF == ord('q'):
            break
    else:
        break

# 5 资源释放
cap.release()
cv.destroyAllWindows()
Camshift

meanshift问题:窗口大小固定,不能根据目标大小的变化来改变窗口的角度&大小

camshift原理:首先应用meanshift,一旦meanshift收敛,就更新窗口大小,计算最佳拟合椭圆的方向,从而根据目标的位置带线啊哦更新搜索窗口。

# 进行Camshift追踪
ret, track_window = cv2.Camshift( dst, track_window, term_crit )
# 绘制追踪结果
pts = cv2.boxPoints( ret ) # 得到一系列点
pts = np.int0( pts )
img2 = cv2.polylines( frame, [pts], True, 255, 2) # 根据这些点绘制多边形

缺点:不准

人脸检测

原理:

OpenCV中自带一幸运脸好的检测器,包括面部、眼睛、猫脸等,保存在xml文件中

步骤:

  1. 读取图片转换为灰度图
  2. 实例化人脸、眼睛检测的分类器对象
# 实例化级联分类器
classifier = cv2.CascadeClassifier( "haarcascade_frontalface_default.xml" )
# 加载分类器
classifier.load( 'haarcascade_frontalface_default.xml' )
  1. 进行人脸、眼睛检测
rect = classifier.detectMultiScale( gray, scaleFactor, minNeighbors, minSize, maxSize )
- scaleFactor: 前后两次扫描中搜索窗口的比例系数
- minneighbors: 目标至少被检测到这么多次才能被认为是目标
- minsize/maxsize: 目标的最小尺寸和最大尺寸
import cv2 as cv
from matplotlib import pyplot as plt

# 1 读入图片
img = cv.imread("chum.jpg")
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 2 实例化OpenCV人脸和眼睛识别的分类器
face_cas = cv.CascadeClassifier( "C:\\Users\\mcfly\\AppData\\Local\\Programs\\Python\\Python310\\Lib\\site-packages\\cv2\\data\\haarcascade_frontalface_default.xml" )
face_cas.load("C:\\Users\\mcfly\\AppData\\Local\\Programs\\Python\\Python310\\Lib\\site-packages\\cv2\\data\\haarcascade_frontalface_default.xml")

eyes_cas = cv.CascadeClassifier( "C:\\Users\\mcfly\\AppData\\Local\\Programs\\Python\\Python310\\Lib\\site-packages\\cv2\\data\\haarcascade_eye.xml" )
eyes_cas.load("C:\\Users\\mcfly\\AppData\\Local\\Programs\\Python\\Python310\\Lib\\site-packages\\cv2\\data\\haarcascade_eye.xml")  # 这里要把路径写全,不然报错

# 3 调用识别人脸
faceRects = face_cas.detectMultiScale( gray, scaleFactor=1.2, minNeighbors=3, minSize=(32,33) )
for faceRect in faceRects:
    x, y, w, h = faceRect
    # 框出人脸
    cv.rectangle(img, (x, y), (x+h, y+w), (0,255,0), 3)
    # 4 在识别出的人脸中进行眼睛判定
    roi_color = img[y:y+h, x:x+w]
    roi_gray = gray[y:y+h, x:x+w]
    eyes = eyes_cas.detectMultiScale(roi_gray)
    for (ex, ey, ew, eh ) in eyes:
        cv.rectangle( img, (ex,ey), (ex+ew, ey+eh), (0,0,255), 2)

# 5 绘制
plt.figure(figsize=(8, 6), dpi=100)
plt.imshow( img[:,:,::-1] )
plt.title("Face Recognition")
plt.xticks([])
plt.yticks([])
plt.show()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值