OpenCV实现特征匹配
1.cornerHarris()实现角点检测
Open 中的函数 cv2.cornerHarris() 可以用来进行角点检测。参数如下:
• img - 数据类型为 float32 的输入图像。
• blockSize - 角点检测中要考虑的领域大小。
• ksize - Sobel 求导中使用的窗口大小
• k - Harris 角点检测方程中的自由参数,取值参数为 [0,04,0.06].
import numpy as np
import cv2 as cv
filename = './image/chessboard.png'
img = cv.imread(filename)
x, y = img.shape[0:2]
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
gray = np.float32(gray)
dst = cv.cornerHarris(gray, 2, 3, 0.04)
a1 = dst > 0.01 * dst.max()
count1 = 0
for i in range(a1.shape[0]):
for j in range(a1.shape[1]):
if a1[i][j]:
count1 += 1
print("膨胀前角点占的位置", count1)
# 函数可以对输入图像用特定结构元素进行膨胀操作,该结构元素确定膨胀操作过程中的邻域的形状,各点像素值将被替换为对应邻域上的最大值:
kernel = np.ones((16, 16))
dst = cv.dilate(dst, kernel)
# 对找到的点进行标记
a2 = dst > 0.01 * dst.max()
count2 = 0
for i in range(a2.shape[0]):
for j in range(a2.shape[1]):
if a2[i][j]:
count2 += 1
print("膨胀后角点占的位置", count2)
print("膨胀前后比值", count2 / count1)
img[dst > 0.01 * dst.max()] = [0, 0, 255]
img_scale = cv.resize(img, (int(y / 6), int(x / 6)))
cv.imshow('dst', img_scale)
if cv.waitKey(0) & 0xff == 27:
cv.destroyAllWindows()
2.拉普拉斯锐化图片
from PIL import Image
import numpy as np
from matplotlib import pyplot as plt
# 读入原图像
img = Image.open('./image/lenna.jpg')
# 为了减少计算的维度,因此将图像转为灰度图
img_gray = img.convert('L')
# 得到转换后灰度图的像素矩阵
img_arr = np.array(img_gray)
h = img_arr.shape[0] # 行
w = img_arr.shape[1] # 列
# 拉普拉斯算子锐化图像,用二阶微分
new_img_arr = np.zeros((h, w)) # 拉普拉斯锐化后的图像像素矩阵
for i in range(2, h - 1):
for j in range(2, w - 1):
new_img_arr[i][j] = img_arr[i + 1, j] + img_arr[i - 1, j] + img_arr[i, j + 1] + img_arr[i, j - 1] - 4 * img_arr[
i, j]
# 拉普拉斯锐化后图像和原图像相加
laplace_img_arr = np.zeros((h, w)) # 拉普拉斯锐化图像和原图像相加所得的像素矩阵
for i in range(0, h):
for j in range(0, w):
laplace_img_arr[i][j] = new_img_arr[i][j] + img_arr[i][j]
img_laplace = Image.fromarray(np.uint8(new_img_arr))
img_laplace2 = Image.fromarray(np.uint8(laplace_img_arr))
plt.subplot(131), plt.imshow(img_gray, "gray")
plt.subplot(132), plt.imshow(img_laplace, "gray")
plt.subplot(133), plt.imshow(img_laplace2, "gray")
plt.legend()
plt.show()
(2)OpenCV算子laplace锐化
Open 中的函数 cv2.Laplacian() 可以用来进行锐化。参数如下
src – 原图像
ddepth – 图像的深度, -1 表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度
【可选参数】
dst – 目标图像
ksize – 算子的大小,必须为1、3、5、7。默认为1
scale – 是缩放导数的比例常数,默认情况下没有伸缩系数
delta – 是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中
borderType – 是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT。。
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread("./image/lenna.jpg", 0)
gray_lap = cv.Laplacian(img, cv.CV_16S, ksize=1)
dst = cv.convertScaleAbs(gray_lap)
img2 = dst + img
plt.subplot(131), plt.imshow(img, "gray")
plt.subplot(132), plt.imshow(dst, "gray")
plt.subplot(133), plt.imshow(img2, "gray")
plt.show()
3. SIFT确定特征描述子
1.sift = cv2.xfeatures2d.SIFT_create() 实例化
参数说明:sift为实例化的sift函数
2.kp = sift.detect(gray, None) 找出图像中的关键点
参数说明: kp表示生成的关键点,gray表示输入的灰度图,
3. ret = cv2.drawKeypoints(gray, kp, img) 在图中画出关键点
参数说明:gray表示输入图片, kp表示关键点,img表示输出的图片
4.kp, dst = sift.compute(kp) 计算关键点对应的sift特征向量
参数说明:kp表示输入的关键点,dst表示输出的sift特征向量,通常是128维的
import numpy as np
import cv2 as cv
img = cv.imread('./image/home.jpg')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
sift = cv.xfeatures2d.SIFT_create()
kp = sift.detect(gray, None)
img = cv.drawKeypoints(gray, kp, img)
kp, dst = sift.compute(gray, kp)
cv.imshow("SIFT", img)
cv.imwrite('./image/sift_keypoints.jpg', img)
cv.waitKey(0)
cv.destroyAllWindows()
4.Surf确定特征
import numpy as np
import cv2 as cv
img = cv.imread('./image/butterfly.jpg', 0)
# 创建SURF对象。你可以在此处或以后指定参数。
# 这里设置海森矩阵的阈值为400
surf = cv.xfeatures2d.SURF_create(400)
# 我们将其设置为50000。记住,它仅用于表示图片。
# 在实际情况下,最好将值设为300-500
surf.setHessianThreshold(40000)
# 计算关键点并检查其数量。
kp, des = surf.detectAndCompute(img, None)
# 计算特征描述子的个数,可以通过调节海森矩阵阈值来调整
print(len(kp))
img2 = cv.drawKeypoints(img, kp, None, (255, 0, 0), 4)
cv.imshow('surf', img2)
cv.waitKey(0)
cv.destroyAllWindows()
5.ORB确定特征
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
img1 = cv.imread('./image/box.png', 0) # queryImage
img2 = cv.imread('./image/box_in_scene.png', 0) # trainImage
# 初始化ORB
orb = cv.ORB_create()
# 确定两个图上的关键点
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# create BFMatcher object,BF是用来匹配特征点的
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
# Match descriptors.
matches = bf.match(des1, des2)
# Sort them in the order of their distance.
matches = sorted(matches, key=lambda x: x.distance)
# Draw first 10 matches.
img3 = cv.drawMatches(img1, kp1, img2, kp2, matches[:20], None, flags=2)
cv.imshow('ORB', img3)
cv.waitKey(0)
cv.destroyAllWindows()
6.对比SIFT SURF ORB
import cv2 as cv
from matplotlib import pyplot as plt
import numpy as np
img = cv.imread("./image/butterfly.jpg")
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 设置三个特征提取处理对象
SIFT = cv.xfeatures2d.SIFT_create(200)
SURF = cv.xfeatures2d.SURF_create(40000)
ORB = cv.ORB_create(50)
# 确定关键点和特征描述子
kp_SIFT, des_SIFT = SIFT.detectAndCompute(img_gray, None)
kp_SURF, des_SURF = SURF.detectAndCompute(img_gray, None)
kp_ORB, des_ORB = ORB.detectAndCompute(img_gray, None)
print("SIFT找到特征点:{};SURF找到特征点:{},ORB找到特征点:{}".format(len(kp_SIFT), len(kp_SURF), len(kp_ORB)))
# 绘制特征描述子
plt.figure(figsize=(80, 20), dpi=100)
img_SIFT = cv.drawKeypoints(img, kp_SIFT, None, [0, 0, 255], 4)
img_SURF = cv.drawKeypoints(img, kp_SURF, None, [0, 0, 255], 4)
img_ORB = cv.drawKeypoints(img, kp_ORB, None, [0, 0, 255], 4)
# 绘制图像
P1 = plt.subplot(131)
P1.set_title('SIFT', size=100), plt.imshow(img_SIFT)
P2 = plt.subplot(132)
P2.set_title('SURF', size=100), plt.imshow(img_SURF)
P3 = plt.subplot(133)
P3.set_title('ORB', size=100), plt.imshow(img_ORB)
plt.show()
未有深度学习之前
1.传统图像分割的方法
图像分割指的是根据灰度、颜色、纹理和形状等特征把图像划分成若干互不交迭的区域,并使这些特征在同一区域内呈现出相似性,而在不同区域间呈现出明显的差异性。经典的数字图像分割算法一般是基于灰度值的两个基本特性之一:不连续性和相似性
1.1 基于阈值的分割
设定某一灰度阈值T,能将图像分成两部分:大于T的像素群和小于T的像素群定一个合适的阈值就可准确地将图像分割开来。
1.2 基于边缘的分割方法
基于边缘检测的图像分割方法的基本思路是先确定图像中的边缘像素,然后再把这些像素连接在一起就构成所需的区域边界。
有五种算子:
1、roberts算子
2、prewitt算子
3、sobel算子
4、LoG算子(二阶微分算子)
1. 图像与高斯滤波器进行卷积,既平滑了图像又降低了噪声,孤立的噪声点和较小的结构组织将被滤除。
2. 在边缘检测时则仅考虑那些具有局部梯度最大值的点为边缘点,用拉普拉斯算子将边缘点转换成零交叉点,通过零交叉点的检测来实现边缘检测。
3. 缺点是在过滤噪声的同时使得原有的边缘一定程度上被平滑了。
5、canny算子
Canny算子检测边缘点的方法基本思想是寻找图像梯度的局部最大值。
遵循这三个准则,Canny算子设计实现的步骤如下:
(1)首先用高斯滤波模板进行卷积以平滑图像;
(2)利用微分算子,计算梯度的幅值和方向;
(3)对梯度幅值进行非极大值抑制。即遍历图像,若某个像素的灰度值与其梯度方向上前后两个像素的灰度值相比不是最大,那么这个像素值置为0,即不是边缘;
(4)使用双阈值算法检测和连接边缘。即使用累计直方图计算两个阈值,凡是大于高阈值的一定是边缘;凡是小于低阈值的一定不是边缘。如果检测结果大于低阈值但又小于高阈值,那就要看这个像素的邻接像素中有没有超过高阈值的边缘像素,如果有,则该像素就是边缘,否则就不是边缘。
从左到右依次是原图、roberts、prewitt、sobel、LoG、canny
边缘检测还是比较适用于噪声比较小的图像中,对于复杂图像不是很合适,二阶微分算子对噪声十分的敏感
1.3 基于区域的分割方法
此类方法是将图像按照相似性准则分成不同的区域,主要包括种子区域生长法、区域分裂合并法和分水岭法等几种类型。
1.3.1 区域生长法
图(a)为原始图像,数字表示像素的灰度。以灰度值为8的像素为初始的生长点,记为f(i,j)。在8邻域内,生长准则是待测点灰度值与生长点灰度值相差为1或0.那么图(b)是第一次区域生长后,f(i-1,j)=7、f(i,j-1)=7、f(i,j+1)=7和生长点灰度值相差都是1,因而被合并。图©是第二次生长后,f(i+1,j)=6被合并。图(d)为第三次生长后,f(i+1,j-1)=5、f(i+2,j)=6被合并,至此,已经不存在满足生长准则的像素点,生长停止。
1.3.2 分水岭算法
彩色图像灰度化,然后再求梯度图,最后在梯度图的基础上进行分水岭算法,求得分段图像的边缘线。
分水岭算法的整个过程:
1.把梯度图像中的所有像素按照灰度值进行分类,并设定一个测地距离阈值。
2. 找到灰度值最小的像素点(默认标记为灰度值最低点,也可以自己标记),让threshold从最小值开始增长,这些点为起始点。
3.水平面在增长的过程中,会碰到周围的邻域像素,测量这些像素到起始点(灰度值最低点)的测地距离,如果小于设定阈值,则将这些像素淹没,否则在这些像素上设置大坝,这样就对这些邻域像素进行了分类。
4. 随着水平面越来越高,会设置更多更高的大坝,直到灰度值的最大值,所有区域都在分水岭线上相遇,这些大坝就对整个图像像素的进行了分区。
分水岭算法需要考虑过度分割
1.4 基于图论的分割方法
1.4.1Graph Cuts分割
Graph cuts是一种十分有用和流行的能量优化算法,在计算机视觉领域普遍应用于**前背景分割(Image segmentation)、立体视觉(stereo vision)、抠图(Image matting)**等。
(1)首先建立两种顶点
第一种普通顶点对应于图像中的每个像素。每两个邻域顶点(对应于图像中每两个邻域像素)的连接就是一条边。
另外两个终端顶点,叫S(source:代表前景)和T(sink:代表背景)。每个普通顶点和这2个终端顶点之间都有连接,组成第二种边(前景和背景的点是人为选择的,)
(2)获得割
Graph Cuts中的Cuts是指这样一个边的集合,该集合中所有边的断开会导致残留”S”和”T”图的分开,所以就称为“割”
(3)找最小割
如果一个割,它的边的所有权值之和最小,那么这个就称为最小割,这个最小割把图的顶点划分为两个不相交的子集S和T,其中s ∈S,t∈ T和S∪T=V 。这两个子集就对应于图像的前景像素集和背景像素集,那就相当于完成了图像分割。
计算权重的方法是什么?
其中,R(A)为区域项(regional term),B(A)为边界项(boundary term),而λ就是区域项和边界项之间的重要因子,决定它们对能量的影响大小。用通俗点的话来说:终端顶点与各个像素点之间的虚线就是R(A),各像素点之间的实线是B(A),系数λ是一个权重系数,决定了哪个因素影响更大一些。
如果λ为0,那么就只考虑边界因素,不考虑区域因素。E(A)表示的是权值,即损失函数,也叫能量函数,图割的目标就是优化能量函数使其值达到最小。
1.4.2 GrabCut分割
Grab相当于是优化版本的Graph CUT:
Graph cut 算法是根据该像素点(lp)灰度值,在前景和背景[这个前景和背景需要事先进行标记的,你需要提前告诉Graph,哪个是背景?哪个是前景]中的灰度值直方图所占的比例来计算前景和背景概率的
但是Grab cut是根据高斯混合模型(GMM)来计算的,即每次迭代中计算在前景聚类和背景聚类的概率。。
具体步骤如下:
1、输入一个矩形。矩形外的所有区域肯定是背景。矩形框内的东西是未知的。同样用户确定前景和背景的任何操作都不会被程序改变。
2、计算机会对我们的输入图像做一个初始化标记。它会标记前景和背景像素。
3、使用一个高斯混合模型(GMM)对前景和背景建模
4、根据我们的输入,GMM会学习并创建新的像素分布。对那些分类未知的像素(可能是前景也可能是背景),可以根据他们与已知分类(如背景)的像素关系来进行分类(就想在做聚类操作)。
5、这样就会根据像素的分布创建一幅图。图中的节点就是像素点。除了像素点做节点之外还有两个节点:Source_node和Sink_node。所有的前景像素都和Source_node相连。所有的背景像素都和sink_node相连。
6、将像素连接到Source_node/end_node的边的权重由他们属于同一类的概率决定。两个像素之间的权重由边的信息或者两个像素的相似性来决定。如果两个像素的颜色有很大的不同,那么它们之间的边的权重就会很小。
7、使用mincut算法对上面的图进行分割。它会根据最低成本方程将图像分为Source_node和Sink_node。成本方程就是被剪掉的所有边的权重之和。在裁剪之后,所有连接到Source_node的像素被认为是前景,所有连接到Sink_node的像素被认为是背景。
8、继续这个过程直到分类收敛。
(2)什么是GMM**
类似于聚类概念,根据点的分布,进行分类
确定GMM模型的迭代方法是K-Means:
K表示取的聚类中心的个数
迭代的过程就是把前景里面属于背景的聚类中心归到背景里面去。从而只留下前景
(3)GrabCut的本质:
----迭代进行Graph Cuts
----优化前景和背景的颜色模型
----能量随着不断迭代变小
----分割结果越来越好
2.人脸检测
2.1 Haar级联分类器
Haar分类器 = Haar-like特征 + 积分图方法 + AdaBoost +级联;
一个很完整的讲Haar级联分类器实现人脸检测的帖子
2.1.1Haar-like回顾
特征模板内有白色和黑色两种矩形,并定义该模板的特征值为白色矩形内的像素和减去黑色矩形内的像素和。共有15个模板:
要选多少个Haar-like特征?
根据15个模板以及图片大小,模板尺度,位置不同,有很多选择:
2.1.2 Adaboost级联分类器
Adaboost是一种基于级联分类模型的分类器。下图是级联分类模型的图示:
其中每一个stage都代表一级强分类器。当检测窗口通过所有的强分类器时才被认为是正样本,否则拒绝。实际上,不仅强分类器是树状结构,强分类器中的每一个弱分类器也是树状结构。由于每一个强分类器对负样本的判别准确度非常高,所以一旦发现检测到的目标位负样本,就不在继续调用下面的强分类器,减少了很多的检测时间。因为一幅图像中待检测的区域很多都是负样本,这样由级联分类器在分类器的初期就抛弃了很多负样本的复杂检测,所以级联分类器的速度是非常快的;只有正样本才会送到下一个强分类器进行再次检验,这样就保证了最后输出的正样本的伪正(false positive)的可能性非常低。
级联分类器是如何训练的呢?
首先需要训练出每一个弱分类器,然后把每个弱分类器按照一定的组合策略,得到一个强分类器,组合策略大致如下:
训练一个弱分类器就是在当前权重分布的情况下,弱分类器估计错的样本的权重会不断加大,,确定f 的最优阈值,使得这个弱分类器对所有训练样本的分类误差最低。
弱分类器,就是在这海量的特征中选取一个特征,用这个特征能够区分出人脸or非人脸,且错误率最低,一个弱学习器的要求仅仅是:它能够以稍低于50%的错误率来区分人脸和非人脸图像。
1个弱分类器就是一个基本和上图类似的决策树,最基本的弱分类器只包含一个Haar-like特征。
我们训练出多个强分类器,然后按照级联的方式把它们组合在一块,就会得到我们最终想要的Haar分类器。
2.1.3人脸识别步骤
从上面所述内容我们可以总结Haar分类器训练的五大步骤:
1、准备人脸、非人脸样本集;
2、计算特征值和积分图;
3、筛选出T个优秀的特征值(即最优弱分类器);
4、把这个T个最优弱分类器传给AdaBoost进行训练。
5、级联,也就是强分类器的强强联手。
在开始前,一定要记住,以20*20窗口为例,就有78,460的特征数量,筛选出T个优秀的特征值(即最优弱分类器),然后把这个T个最优弱分类器传给AdaBoost进行训练得到一个强分类器,最后将强分类器进行级联。
2.2 HOG+SVM行人检测(目标检测)
一个讲的比较细的帖子可以回顾
2.2.1 HOG实现步骤
(1)图像标准化(调节图像的对比度)
主要是减少光照因素的影响,降低图像局部的阴影和光照变化所造成的影响,采用Gamma校正法对输入图像的颜色空间进行标准化(或者说是归一化)。
(2)图像平滑
对于灰度图像,一般为了去除噪点,所以会先利用高斯函数进行平滑,但是有些情况下不做平滑效果更佳。
(3)边缘方向计算
计算图像每个像素点的梯度、包括方向和大小,有专门的幅值和方向公式
(4)直方图计算
先是将图像划分成小的细胞单元(细胞单元可以是矩形的或者环形的),比如大小为8×8,然后统计每一个细胞单元的梯度直方图,即可以得到一个细胞单元的描述符
然后将几个细胞单元组成一个block,例如2×2个细胞单元组成一个block,将一个block内每个细胞单元的描述符串联起来即可以得到一个block的HOG描述符。
注意:一般采用180度范围下的9bin直方图(一个bin=20度)
(5)对block归一化
由于局部光照的变化,以及前景背景对比度的变化,使得梯度绝对值的变化范围非常大,这就需要对梯度做局部对比度归一化。归一化能够进一步对光照、阴影、边缘进行压缩,使得特征向量对光照、阴影和边缘变化具有鲁棒性。
(6)样本HOG特征提取
最后一步就是对一个样本中所有的块进行HOG特征的提取,并将它们结合成最终的特征向量送入分类器。
以例子计算特征:
例如:对于128×64的输入图片(后面我所有提到的图像大小指的是h×w),每个块由2×2个cell组成,每个cell由8×8个像素点组成,每个cell提取9个bin大小的直方图,以1个cell大小为步长,那么水平方向有15个扫描窗口,垂直方向有7个扫描窗口,也就是说,一共有15∗7∗2∗2∗9=3780个特征。
2.2.2 用SVM区分行人与非行人的HOG特征
2.3 DPM
DPM可以看做是HOG(Histogrrams of Oriented Gradients)的扩展,大体思路与HOG一致。先计算梯度方向直方图,然后用SVM(Surpport Vector Machine )训练得到物体的梯度模型(Model)。有了这样的模板就可以直接用来分类了,简单理解就是模型和目标匹配。DPM只是在模型上做了很多改进工作。
检测流程:
(1)对于任意一张输入图像,提取其DPM特征图,然后将原始图像进行高斯金字塔上采样,然后提取其DPM特征图。
(2)对于原始图像的DPM特征图和训练好的Root filter做卷积操作,从而得到Root filter的响应图。
(3)对于2倍图像的DPM特征图,和训练好的Part filter做卷积操作,从而得到Part filter的响应图。
(4)然后对其精细高斯金字塔的下采样操作,这样Root filter的响应图和Part filter的响应图就具有相同的分辨率了。
(5)然后将其进行加权平均,得到最终的响应图。亮度越大表示响应值越大。
2.4 参考资料:
Adaboost算法详解(haar人脸检测)
DPM(Deformable Parts Model)–原理(一)
DPM(Deformable Part Model)原理详解
leetcode(111 112)
leetcode111 二叉树的最小深度
就递归找最小就可以了,但是要记得一个子叶的终点是他没有子叶了,而不是当前为空。
同时要考虑root==None的情况
class Solution:
def minDepth(self, root: TreeNode) -> int:
if root is None:
return 0
if (not root.left) and (not root.right):
return 1
deepl=self.minDepth(root.left)
deepr=self.minDepth(root.right)
if deepr==0:
deepr=100000
if deepl==0:
deepl=100000
return min(deepl,deepr)+1
leetcode112 112. 路径总和
递归实现,没啥难点
class Solution:
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if root is None:
return False
if (not root.right) and (not root.left):
if targetSum-root.val==0:
return True
else:
return False
targetSum-=root.val
flagl=self.hasPathSum(root.left,targetSum)
flagr=self.hasPathSum(root.right,targetSum)
return (flagl)or(flagr)