OpenCV 学习笔记 07 目标检测与识别

有些话、适合烂在心里 提交于 2020-11-27 03:25:56

目标检测与识别是计算机视觉中最常见的挑战之一。属于高级主题。

本章节将扩展目标检测的概念,首先探讨人脸识别技术,然后将该技术应用到显示生活中的各种目标检测。

1 目标检测与识别技术

为了与OpenCV 学习笔记 05 人脸检测和识别进行区分;需重新说明一下什么是目标检测。

目标检测是一个程序,它用来确定图像的某个区域是否有要识别的对象,对象识别是程序识别对象的能力。识别通常只处理已检测到对象的区域。若人们总是会在有人脸图像的区域去识别人脸。

在计算机视觉中有很多目标检测和识别的技术,本章会用到:

  • 梯度直方图(Histogram of Oriented Gradient, HOG)
  • 图像金字塔(image pyramid)
  • 滑动窗口(sliding window)

与特征检测算法不同,这些算法是互补的。如在梯度直方图(HOG)中会使用滑动窗口技术。

1.1 HOG 描述符

HOG 是一个特征描述符,因此 HOG 与 SIFT、SURF 和 ORB 属于同一类型的描述符。

在图像和视觉处理中常常会进行目标检测,其实目标检测的内部机制都差不多,如人脸识别的 LBPH 描述符:

  • 第一步:将图像划分成多个部分
  • 第二步:计算各个部分的梯度

HOG 不是基于颜色值而是基于梯度来计算直方图。

HOG 所得到的特征描述符能够为特征匹配目标检测(或目标识别)提供非常重要的信息。

HOG 提取特征的过程:先将目标图像分成小单元,每个小单元是 16*16 的像素块;每个单元都包含了视觉表示,该视觉表示按按八个方向(N、NW、W、SW、S、SE、E、NE)所计算的颜色梯度。

每个单元的八个值就为直方图,因此,每个单元都会有唯一的标识(signature)。将直方图外推(extrapolation)成描述符是相当复杂的过程:计算每个单元的局部直方图,这些单元会合成较大的区域,也称为块(block);块可由任意多个单元组成,但 Dalal 和 Triggs ( HOG 的发明人)发现当进行人检测时,一个块包含 2 * 2 的单元时可得到最好的效果。按块构成特征向量是为了便于归一化,同时也考虑到了光照和阴影(shadowing)的变化(一个单元的区域太小,不能检测到这样的变化)。这样做减少了图像与块之间光照和阴影的差异,从而提高了检测精度。

仅仅比较两幅图的单元是行不通的,除非这两幅图像相同(从大小和数据两方面而言)还有两个主要的问题需要解决:位置和尺度。

1.1.1 尺度问题

对于这个问题,可以通过两个例子来说明:若要检测的目标(比如自行车)是较大图像中的一部分,要对两幅图像进行比较。如果在比较过程中找不到一组相同的梯度,则检测就会失败(即使两幅图像都有自行车)。

1.1.2 位置问题

在解决了尺度问题后,还有另外一个问题,要检测的目标可能位于图像的任何位置,所以需要扫描图像的各个部分,以确保能找到感兴趣的区域,且在这些区域中去尝试检测目标。即使待检测图像中的目标和训练图像中的目标一样大,也需要通过某种方式让 OpenCV 定位该目标。因此,只对有可能存在目标的区域进行比较,而该图像的其余部分会被丢弃。

为了解决上述问题,需要熟悉图像金字塔和滑动窗口的概念。

1.1.2.1 图像金字塔

 计算机视觉中的许多算法都会用到金字塔(pyramid)的概念。

图像金字塔是图像的多尺度表示,下图有助于理解这个概念。

图像的多尺度表示(或图像金字塔)有助于解决不同尺度下的目标检测问题。

此外,学习目标分类器(这个功能可用 OpenCV 来检测目标)也可能用到图像金字塔;因为目标分类器是用图像数据库(该数据库分为正匹配 positive match 和负匹配 negative match 构成)训练得到的,但是,图像数据库(也成为训练数据集)的正匹配中同一目标不可能都一样大。

基于上述因素,所以多种尺度图像就非常有必要。

下面介绍如何构建图像金字塔:

1 - 获取图像

2 - 使用任意尺度的参数来调整(缩小)图像大小

3 - 平滑图像(使用高斯模糊)

4 - 如果图像比最小尺寸还大,则从第一步重复该过程。

 

在第五章中的 CascadeClassifier 对象所使用的 detectMultiScale 方法就涉及这些内容。级联分类器对象尝试在输入图像的不同尺度下检测对象。detectMultiScale() 函数中的参数 scaleFactor 表示一个比率,即在每层金字塔中所获得的图像与上层图像的比率。scaleFactor 参数越小,金字塔的层级越多,计算量越大,计算会更慢,结果在某种程度上越准确。

1.1.2.2 滑动窗口

滑动窗口是用于计算机视觉的一种技术,包括滑动窗口的检测和使用图像金字塔对各部分进行检测,这是为了在多尺度下检测对象。

滑动窗口通过扫描较大图像的较小区域来解决定位问题,进而在同一图像的不同尺度下重复扫描。

该技术需将图像分解成多个部分,然后丢掉那些不太可能包含对象的部分,并对可能区域进行分类。

该技术存在有一个问题:区域重叠(overlapping region)。区域重叠指的是在图像执行人脸检测时使用滑动窗口,每个窗口每次都会丢掉( slide of )几个像素,这就意味着滑动窗口可以对同一张人脸的四个不同位置进行正匹配;当然,只需要一个匹配结果,而不是四个,此外,这里对有良好评分的图像区域不感兴趣,而是对有最高评分的图像区域感兴趣。

这带来了另一个问题,非最大抑制,它是指给定一组重叠区域,可以用最大评分来抑制所有未分类区域。

1.1.3 非极大抑制

非极大值抑制(Non-maximum suppression, NMS)释义为抑制不是极大值的元素,搜索局部的极大值。

如在对象检测中,滑动窗口经提取特征 --> 分类器分类识别后,每个窗口都会得到一个分类和分数,但滑动窗口会导致很多窗口与其他窗口存在包含或大部分交叉的情况,这时就需要用到 NMS 来选取那些邻域里分数最高(某类对象的概率最大),并抑制这些分数低的窗口。

也可理解为:目标检测的过程中,同一目标位置上会产生大量的候选框,这些候选框相互之间可能会有重叠,此时我们需要利用非极大值抑制找到最佳的目标边界框,消除冗余的边界框。

非极大值抑制在计算机视觉中广泛应用,例如边缘检测、人脸检测、目标检测( DPM,YOLO,SSD,Faster R-CNN )等。

以下图为例:

a 图为人脸检测的候选框结果,并在标注了置信度得分(confidence score),如果不适用非极大值抑制,就会有多个候选框出现。

b 图为使用非极大值抑制的结果,符合人脸检测的预期结果。

其代码如下:

import cv2
import numpy as np

"""
    Non-max Suppression Algorithm

    @param list  Object candidate bounding boxes
    @param list  Confidence score of bounding boxes
    @param float IoU threshold

    @return Rest boxes after nms operation
"""
def nms(bounding_boxes, confidence_score, threshold):
    # If no bounding boxes, return empty list
    if len(bounding_boxes) == 0:
        return [], []

    # Bounding boxes
    boxes = np.array(bounding_boxes)

    # coordinates of bounding boxes
    start_x = boxes[:, 0]
    start_y = boxes[:, 1]
    end_x = boxes[:, 2]
    end_y = boxes[:, 3]

    # Confidence scores of bounding boxes
    score = np.array(confidence_score)

    # Picked bounding boxes
    picked_boxes = []
    picked_score = []

    # Compute areas of bounding boxes
    areas = (end_x - start_x + 1) * (end_y - start_y + 1)

    # Sort by confidence score of bounding boxes
    order = np.argsort(score)

    # Iterate bounding boxes
    while order.size > 0:
        # The index of largest confidence score
        index = order[-1]

        # Pick the bounding box with largest confidence score
        picked_boxes.append(bounding_boxes[index])
        picked_score.append(confidence_score[index])

        # Compute ordinates of intersection-over-union(IOU)
        x1 = np.maximum(start_x[index], start_x[order[:-1]])
        x2 = np.minimum(end_x[index], end_x[order[:-1]])
        y1 = np.maximum(start_y[index], start_y[order[:-1]])
        y2 = np.minimum(end_y[index], end_y[order[:-1]])

        # Compute areas of intersection-over-union
        w = np.maximum(0.0, x2 - x1 + 1)
        h = np.maximum(0.0, y2 - y1 + 1)
        intersection = w * h

        # Compute the ratio between intersection and union
        ratio = intersection / (areas[index] + areas[order[:-1]] - intersection)

        left = np.where(ratio < threshold)
        order = order[left]

    return picked_boxes, picked_score


# Image name
image_name = 'nms.jpg'

# Bounding boxes
bounding_boxes = [(187, 82, 337, 317), (150, 67, 305, 282), (246, 121, 368, 304)]
confidence_score = [0.9, 0.75, 0.8]

# Read image
image = cv2.imread("img_nms.jpg")

# Copy image as original
org = image.copy()

# Draw parameters
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
thickness = 2

# IoU threshold
threshold = 0.4

# Draw bounding boxes and confidence score
for (start_x, start_y, end_x, end_y), confidence in zip(bounding_boxes, confidence_score):
    (w, h), baseline = cv2.getTextSize(str(confidence), font, font_scale, thickness)
    cv2.rectangle(org, (start_x, start_y - (2 * baseline + 5)), (start_x + w, start_y), (0, 255, 255), -1)
    cv2.rectangle(org, (start_x, start_y), (end_x, end_y), (0, 255, 255), 2)
    cv2.putText(org, str(confidence), (start_x, start_y), font, font_scale, (0, 0, 0), thickness)

# Run non-max suppression algorithm
picked_boxes, picked_score = nms(bounding_boxes, confidence_score, threshold)

# Draw bounding boxes and confidence score after non-maximum supression
for (start_x, start_y, end_x, end_y), confidence in zip(picked_boxes, picked_score):
    (w, h), baseline = cv2.getTextSize(str(confidence), font, font_scale, thickness)
    cv2.rectangle(image, (start_x, start_y - (2 * baseline + 5)), (start_x + w, start_y), (0, 255, 255), -1)
    cv2.rectangle(image, (start_x, start_y), (end_x, end_y), (0, 255, 255), 2)
    cv2.putText(image, str(confidence), (start_x, start_y), font, font_scale, (0, 0, 0), thickness)

# Show image
cv2.imshow('Original', org)
cv2.imshow('NMS', image)
cv2.waitKey(0)

代码转自:非极大值抑制(Non-Maximum Suppression)

参考 非极大值抑制(Non-Maximum Suppression,NMS)

对于上述如何确定窗口的评分,需要一个分类系统来确定某一特征是否存在,且对这种分类会有一个置信度评分,一般而言都是采用支持向量机(SVM)来分类。

1.1.4 支持向量机SVM

在机器学习中,支持向量机( support vector machine, SVM)是在分类与回归分析中分析数据的监督式学习模型与相关的学习算法。给定一组训练实例,每个训练实例被标记为属于两个类别中的一个或另一个,SVM训练算法创建一个将新的实例分配给两个类别之一的模型,使其成为非概率二元线性分类器。SVM模型是将实例表示为空间中的点,这样映射就使得单独类别的实例被尽可能宽的明显的间隔分开。然后,将新的实例映射到同一空间,并基于它们落在间隔的哪一侧来预测所属类别。

除了进行线性分类之外,SVM还可以使用所谓的核技巧有效地进行非线性分类,将其输入隐式映射到高维特征空间中。

当数据未被标记时,不能进行监督式学习,需要用非监督式学习,它会尝试找出数据到簇的自然聚类,并将新数据映射到这些已形成的簇。将支持向量机改进的聚类算法被称为支持向量聚类[2],当数据未被标记或者仅一些数据被标记时,支持向量聚类经常在工业应用中用作分类步骤的预处理。

摘自 支持向量机 维基百科。

1.2 检测人

OpenCV 自带的HOGDescriptor 函数可检测人。

实例如下:

import cv2

# 确定某矩形是否完全包含在另一个矩形中
def is_inside(o, i):
    ox, oy, ow, oh = o
    ix, iy, iw, ih = i
    return ox > ix and oy > iy and ox+ow < ix+iw and oy + oh < iy + ih

# 绘制矩形来框住检测到的人
def draw_person(image, person):
    x, y, w, h = person
    cv2.rectangle(img, (x, y), (x+w, y + h), (0, 255, 255), 2)


# 导入图像,
img = cv2.imread("face.jpg")
# 实例化HOGDescriptor对象,作为检测人的检测器
hog = cv2.HOGDescriptor()
# 设置线性SVM分类器的系数
hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())

# 该和人脸算法不一样,不需要在使用目标检测方法前将原始图像转换为灰度形式
# 该方法返回一个与矩形相关的数组,用户可用该数组在图形上绘制形状
# 若图形上的矩形存在有包含与被包含的关系,说明检测出现了错误
# 被包含的图形应该被丢弃,此过程由is_inside来实现
# 在输入图像中检测不同大小的对象。检测到的对象作为列表返回
found, w = hog.detectMultiScale(img)

found_filtered = []

# 遍历检测结果,丢弃不含有检测目标区域的矩形。
for ri, r in enumerate(found):
    for qi, q in enumerate(found):
        if ri != qi and is_inside(r, q):
            break
        else:
            found_filtered.append(r)

for person in found_filtered:
    draw_person(img, person)

cv2.imshow("people detection", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

运行:

 其实我们发现结果并不好,我又用了几个图形来测试,该段代码实在是差强人意。

由于该段该类的重要性还不知道,学到后面,如果重要的话,再将该类认真学习一下。

该段内容可参考

80行Python实现-HOG梯度特征提取

HOG + SVM 实现行人识别——这个非常好

由于这个作者好久没更新,不知道他这个blog 什么时候就注销了,为此先完全转载,待后期学习时候,再研读并验证,具体内容如下:


 

1 系统简介

输入图片或视频,实现对图片或视频中的行人的识别。如

2 程序设计

2.1 import

import cv2 as cv
import random
import glob
import os
import numpy as np
from imutils.object_detection import non_max_suppression

2.2 读取训练数据

本程序使用的是由 CSDN 网友整理过的 INRIA 数据集,原文链接。 使用下面的代码读取样本:

#get pos
posimgs = []
count = 0
posfilenames = glob.iglob(os.path.join(posfoldername,'*'))
for posfilename in posfilenames:
    posimg = cv.imread(posfilename,1)
    posres = posimg[16:16+imgh,13:13+imgw]
    posimgs.append(posres)
    count += 1
print('pos = '+str(count)+'\n')

#get neg
negimgs = []
count = 0
negfilenames = glob.iglob(os.path.join(negfoldername,'*'))
for negfilename in negfilenames:
    negimg = cv.imread(negfilename,1)
    for i in range(10):
        #负样本图片过少,由于图片足够大,随机切10次很大几率得到的图片不相同,可以将一张图片当两张使用
        if((negimg.shape[1] >= imgw) & (negimg.shape[0] >= imgh)):
            y = int(random.uniform(0,negimg.shape[1] - imgw))
            x = int(random.uniform(0,negimg.shape[0] - imgh))
            negres = negimg[x:x+imgh,y:y+imgw]
            negimgs.append(negres)
            count+=1
print('neg = '+str(count)+'\n')

其中,对负样本随机裁剪 10 次来获得,使用负样本量更大。 接下来,为每个样本打上标签,并使用 opencv 提供的方法来计算 HOG 向量:

#get features & labels
features = []
labels = []
hog = cv.HOGDescriptor()

for i in range(len(posimgs)):
    features.append(hog.compute(posimgs[i]))
    labels.append(1)
for j in range(len(negimgs)):
    features.append(hog.compute(negimgs[j]))
    labels.append(-1)

if len(features) == len(labels):
    print('features = '+str(len(features))+'\n')

2.3 构建并训练SVM模型

#function for create svm
def svm_create():
    svm = cv.ml.SVM_create()
    svm.setType(cv.ml.SVM_EPS_SVR)
    svm.setKernel(cv.ml.SVM_LINEAR)
    criteria = (cv.TERM_CRITERIA_MAX_ITER + cv.TERM_CRITERIA_EPS, 1000, 1e-3)
    svm.setTermCriteria(criteria)
    svm.setP(0.1)
    svm.setC(0.01)
    return svm

svm0 = svm_create()
print('Training svm0...\n')
svm0.train(np.array(features),cv.ml.ROW_SAMPLE,np.array(labels))
sv0 = svm0.getSupportVectors()
rho0, _, _ = svm0.getDecisionFunction(0)
sv0 = np.transpose(sv0)
hog.setSVMDetector(np.append(sv0,[[-rho0]],0))
hog.save('hogsvm.bin')
print('Finished!!!!\n')

2.4 使用 Hardexample 优化模型

在此,使用 5 次 Hardexample 方法,以使模型更优:

# hardexample
for k in range(5):
    #get hardexample
    hoghe = cv.HOGDescriptor()
    hoghe.load('hogsvm.bin')
    hardexamples = []
    hefilenames = glob.iglob(os.path.join(hefoldername,'*'))
    for hefilename in hefilenames:
        heimg = cv.imread(hefilename,1)
        rects,weight = hog.detectMultiScale(heimg,0,scale = 1.03)#参数可调
        for (x,y,w,h) in rects:
            heres = heimg[y : y + h, x : x + w]
            hardexamples.append(cv.resize(heres,(64,128)))

    for k in range(len(hardexamples)):
        features.append(hog.compute(hardexamples[k]))
        labels.append(-1)

    if len(features) == len(labels):
        print('allfeatures = '+str(len(features))+'\n')

    #train hardexample(allfeatures)
    svm = svm_create()
    print('Training svm...\n')
    svm.train(np.array(features),cv.ml.ROW_SAMPLE,np.array(labels))
    sv = svm.getSupportVectors()
    rho, _, _ = svm.getDecisionFunction(0)
    sv = np.transpose(sv)
    hog.setSVMDetector(np.append(sv,[[-rho]],0))
    hog.save('hogsvm.bin')
    print('Finished!!!!\n')

3 输入视频用于识别

# real time predict
capture = cv.VideoCapture('v/test.mp4')  # 视频文件路径
rate = 24  # 帧率
stop = False
delay = int(1000/rate)

while not stop:
    hogtest = cv.HOGDescriptor()
    hogtest.load('hogsvm.bin')
    rval, frame = capture.read()
    rects,weights = hogtest.detectMultiScale(frame,scale = 1.03)#参数可调

    #weight
    weights = [weight[0] for weight in weights]
    weights = np.array(weights)

    #这里返回的四个值表示的是开始始位置(x,y),长宽(xx,yy),所以做以下处理
    for i in range(len(rects)):
        r = rects[i]
        rects[i][2] = r[0] + r[2]
        rects[i][3] = r[1] + r[3]


    choose = non_max_suppression(rects, probs = weights, overlapThresh = 0.5)#参数可调(可以把overlapThresh调小一点,也不要太小)

    for (x,y,xx,yy) in choose:
        cv.rectangle(frame, (x, y), (xx, yy), (0, 0, 255), 2)
    cv.imshow('Video', frame)
    if cv.waitKey(delay) >= 0:
        stop=True
capture.release()
cv.destroyAllWindows()

 


 

 1.3 创建和训练目标检测器

使用内建类 / 函数很容易得到可应用的简易模型,这使得人脸检测和人检测变得容易;但是仅能实现人检测或人脸检测还远远不够,我们需要了解如何得到这些(人检测器的)特征,且知道如何修改这些特征。此外,能够将这些逻辑及特征拓展到其他领域(如植物、动物、汽车等)。

在实际生活中,我们的检测更为具体,如汽车车牌等。

那么如何构建分类器?使用 SVM 和词袋(Bag-Of-Word,BOW)技术。

1.3.1 词袋BOW

词袋BOW的概念起源于语言分析和信息检索领域,计算机视觉会使用词袋BOW的升级版本。

BOW 用来计算文章中每个词出现的次数,再用这些次数构成向量来重新表示该文档。

应用示例:

Document 1 :I like OpenCV and I like Python

Document 2 :I like C++ and Python

Document 3 :I don't like artichokes 

对于上述三个文档,我们可以使用这些值来建立字典(或代码本)

{
    I:4,
    like:4,
    OpenCV:2,
    and:2,
    Python:2,
    C++:1,
    don't:1,
    artichokes:1
}

该字典有 8 个键值对,使用这 8 个键值对所构成的向量来重新表示原始文件,每个向量包含字典中的所有单词,向量中的每个元素表示文档中每个单词出现的次数,具体表示如下:

[ 2,2,1,1,1,0,0,0 ]

[ 1,1,0,1,1,1,0,0 ] 

[ 1,1,0,0,0,0,1,1 ]

 这些向量可看成是文档的直方图表示或被当作特征,这些特征用来训练分类器。

上面的BOW用法仅仅是基于语言分析和信息检索领域的,下文详细介绍BOW在机器视觉中的具体应用

1.3.2 计算机视觉中的BOW

回顾:

1)熟悉了图像特征的概念,且使用过图像特征提取方法(SIFT 和 SURF),这些特征可与其他图像特征进行匹配

2)理解了支持向量机的作用:给SVM提供一组特征数据,训练分类模型;然后可依据该模型预测其他数据属于哪一类别

 

BOW 方法的实现步骤如下:

1 - 喂入待训练的样本数据集

2 - 提取数据集中的(每幅图)描述符

3 - 将每幅图对应的描述符添加到BOW训练器中

4 - 将描述符聚类到 K 簇中(聚类的中心就是 " 视觉单词 " )

备注:上述过程需要提供视觉单词(visual word)字典,最终实现识别出单词并在图像中的定位;从某种程度上来讲,单词字典越多越好。

接下来需要测试一下分类器,并尝试进行检测,具体过程为:

1 - 给定测试图像

2 - 提取特征

3 - 量化特征到最近簇心得距离,形成直方图

 概念补充:K-means 聚类

K-mean 聚类是用于数据分析的向量量化方法,对于给定数据集,k 表示要分割的数据集中的簇数。术语 “ means ” 指的是数学中的均值,从可视化表示的角度来看,簇的均值其实是这个簇中点的几何中心。

clustering 指的是将数据集的点组合到各个簇中。

BagOfWordsKMeansTrainer 是一种用于执行目标检测的类,目的为使用词袋方法训练视觉词汇的类 “ kmeans() - based class to train a visual vocabulary using the bag-of-words approach " 

 2 汽车检测

为了训练数据,需要有足够大的数据集,且训练图像的大小要一样。

把书中的代码学习了一般。

第一部分是没有更变内容的。

import cv2
import numpy as np
# from os.path import join

# 定义训练图像的路径
datapath = "D:/project/opencv3/c7/CarData/TrainImages"


# 函数return测试图像的完整路径,
# 测试图像的命名规则pos-n.pgm / neg-n.pmg
def path(cls, i):
    return "%s/%s%d.pgm" % (datapath, cls, i+1)

pos, neg = "pos-", "neg-"

# 创建SIFT实例,
# detect提取关键点
# extract提取特征
detect = cv2.xfeatures2d.SIFT_create()
extract = cv2.xfeatures2d.SIFT_create()
print(help(detect))
print(help(extract))

# 当看到SIFT时,就可断定会涉及一些特征匹配算法
# 范例接下来会创建基于FLANN匹配器的实例
# OpenCV3在Python版本中,FLANN没有enum值。
# enum值algorithm=1表示用FLANN_INDEX_KDTREE算法
flann_params = dict(algorithm=1, trees=5)
matcher = cv2.FlannBasedMatcher(flann_params, {})

# 初始化BOW提取器,视觉词汇将作为BOW类输入
extract_bow = cv2.BOWImgDescriptorExtractor(extract, matcher)

# 为了从图像中提取SIFT特征,需要通过一个方法来获取图像特征
# 并以灰度格式读取图像,然后返回描述符
def extract_sift(fn):
    im = cv2.imread(fn, 0)
    # print(help(extract.compute))
    return extract.compute(im, detect.detect(im))[1]


# 以上是为训练BOW训练器做好了一切准备

# 创建BOW训练器,并指定训练器簇数为40,
bow_kmeans_trainer = cv2.BOWKMeansTrainer(40)
# 每个类都从数据集中读取 8 张图像(8个正样本、8个负样本)
for i in range(8):
    bow_kmeans_trainer.add(extract_sift(path(pos, i)))
    bow_kmeans_trainer.add(extract_sift(path(neg, i)))

# 创建视觉单词词汇需要调用训练器上的cluster函数,
# 该函数执行k-means分类并返回词汇
voc = bow_kmeans_trainer.cluster()
# 将为BOWImgDescriptorExtractor指定返回的词汇
# 以便它能从测试图像中提取描述符
extract_bow.setVocabulary(voc)

# 函数返回基于BOW的描述符提取器计算得到的描述符
# 函数参数fn为一幅图像的路径
def bow_features(fn):
    im = cv2.imread(fn, 0)
    return extract_bow.compute(im, detect.detect(im))

# 创建训练数据数组和标签数组
# 并用BOWImgDescriptorExtractor产生的描述符填充它们
# 接下来的方法生成相应的正负样本图像的标签
# 1 正匹配,-1负匹配
traindata, trainlabels = [], []
for i in range(20):
    traindata.extend(bow_features(path(pos, i))); trainlabels.append(1)
    traindata.extend(bow_features(path(neg, i))); trainlabels.append(-1)

# 创建一个SVM实例
svm = cv2.ml.SVM_create()
# 通过将训练数据和标签放到Numpy数组中来进行训练
svm.train(np.array(traindata), cv2.ml.ROW_SAMPLE, np.array(trainlabels))


# 函数用来显示predict方法效果
def predict(fn):
    f = bow_features(fn)
    p = svm.predict(f)
    print(fn, "\t", p[1][0][0])
    return p


# 定义两个样本图像的路径
# 并将路径中的图像文件读取出来放到Numpy数组中
car = "D:/project/opencv3/c7/CarData/TestImages/test-0.pgm"
notcar = "D:/project/opencv3/c7/CarData/TestImages/test-1.pgm"
car_img = cv2.imread(car)
notcar_img = cv2.imread(notcar)
# 将这些图像传给训练好的SVM,并获得预测结果
# 期望汽车图像都能检测到汽车(此时predict()结果为1)
# 期望无汽车的图像都检测不到汽车(结果应为-1)
car_predict = predict(car)
not_car_predict = predict(notcar)

# 最后在屏幕上展现图像,希望能看到每个图像都有正确的文字说明
font = cv2.FONT_HERSHEY_SIMPLEX

if (car_predict[1][0][0] == 1.0):
    cv2.putText(car_img,'Car Detected',(10,30), font, 1,(0,255,0),2,cv2.LINE_AA)

if (not_car_predict[1][0][0] == -1.0):
    cv2.putText(notcar_img,'Car Not Detected',(10,30), font, 1,(0,0, 255),2,cv2.LINE_AA)

cv2.imshow('BOW + SVM Success', car_img)
cv2.imshow('BOW + SVM Failure', notcar_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

可以跑的通,但是还有问题没有解决;想把这部分代码封装的更好些,发现还是有难度的。封装的目的主要是不想让图像特征提取和BOW训练模型杂糅,发现真心不好分。下面是写了一半的。

附上compute() 函数的源码。

compute(image, keypoints[, descriptors]) -> keypoints, descriptors
    .   @brief Computes the descriptors for a set of keypoints detected in an image (first variant) or image set
    .   (second variant).
    .   
    .   @param image Image.
    .   @param keypoints Input collection of keypoints. Keypoints for which a descriptor cannot be
    .   computed are removed. Sometimes new keypoints can be added, for example: SIFT duplicates keypoint
    .   with several dominant orientations (for each orientation).
    .   @param descriptors Computed descriptors. In the second variant of the method descriptors[i] are
    .   descriptors computed for a keypoints[i]. Row j is the keypoints (or keypoints[i]) is the
    .   descriptor for keypoint j-th keypoint.
    
    
    
    compute(images, keypoints[, descriptors]) -> keypoints, descriptors
    .   @overload
    .   
    .   @param images Image set.
    .   @param keypoints Input collection of keypoints. Keypoints for which a descriptor cannot be
    .   computed are removed. Sometimes new keypoints can be added, for example: SIFT duplicates keypoint
    .   with several dominant orientations (for each orientation).
    .   @param descriptors Computed descriptors. In the second variant of the method descriptors[i] are
    .   descriptors computed for a keypoints[i]. Row j is the keypoints (or keypoints[i]) is the
    .   descriptor for keypoint j-th keypoint.

 

 自己想修改的部分代码

import os
import cv2

'''
思路
读取图像文件
提取图像文件描述符
BOW训练器进行训练
图像测试
将图像输出到屏幕
'''

# ===文件路径===

# 定义训练/测试数据总路径
datapath = "D:/project/opencv3/c7/CarData/TrainImages"
# 定义图像后缀格式
IMG_FORMAT = ".pgm"
# 定义正匹配图像名称的特征字段
POG = "pos"
# 定义负匹配图像名称的特征字段
NEG = "neg"
# 定义图像分类函数
# 当图像中含有汽车时pos 绑定 1
# 当图像中未含汽车时neg 绑定 -1

pos_list = []
neg_list = []
def create_data(datapath):
    '''
    主要目的是筛选目标图像并保存到二维列表中(文件路径,1/-1)
    训练/测试文件数据都是.pgm结尾
    文件夹中都是图像文件格式
    为了数据的严谨性,先检测数据后缀格式,
    再对数据进行分类,
    含有汽车的图像名称中都带有pos字样,对应值为1
    不含有汽车的图像名称中都含有neg字样,对应值为-1

    '''
    # 遍历给定图像文件夹路径,将符合格式的图像添加到files列表中
    for dirpath, dirnames, filenames in os.walk(datapath):
        for filename in filenames:
            # 判断图像是否以.pgm结尾
            if filename.endswith(IMG_FORMAT):
                img_path = os.path.join(dirpath, filename)
                # 将是.pgm格式图像与标签label形成键值对存入字典中
                # 当文件名中带有pos字段时,属于正匹配,字典值为1
                if POG in filename:
                    pos_list.append((img_path: 1))
                elif NEG in filename:
                    neg_list.append((img_path: 1))
                else:
                    continue
            else:
                continue
    # print('dict_pos:', dict_pos)
    # print('dict_neg:', dict_neg)
    return pos_list, neg_list


# ===提取图像描述符===
# 获取图像的关键点和描述符
# 为词袋BOW计算做准备
def create_data(img, num):
    '''
    函数实现获取图像的关键点和描述符
    img - 被提取关键点和描述符的图像
    num - 图像的数量
    '''
    # 创建正样本/负样本图像描述符列表
    pos_descriptors = []
    neg_descriptors = []
    # 判断输入数量是否超过数据库图像的总数
    # 默认正样本数量等于负样本数量
    pos_list, neg_list = create_data(datapath)
    list_num = len(pos_list)
    if num > list_num:
        return "数量超过数据总量!"

    # 实例化SIFT,检测局部特征
    sift = cv2.xfeatures2d.SIFT_create()

    # 特征匹配算法,本代码创建的是单应性匹配器FLANN
    # algorithm=FLANN_INDEX_KDTREE等价于algorithm=1
    flann_params = dict(algorithm=1, trees=5)
    search_params = dict(checks=50)
    flann = cv2.FlannBasedMatcher(flann_params, search_params)
    for i in num:
        # 读取图像的灰度图
        pos_img = cv2.imread(pos_list[i][0], 0)
        neg_img = cv2.imread(neg_list[i][0], 0)
        # 计算图像的关键点keypoints和描述符descriptors
        # 并返回图像的描述符descriptors
        pos_keypoint, pos_descriptor = sift.detectAndCompute(pos_img, None)
        neg_keypoint, neg_descriptor = sift.detectAndCompute(pos_img, None)

        # 将图像描述符和图像标签添加到响应的列表中,
        pos_descriptors.append((pos_descriptor, pos_list[i][1]))
        neg_descriptors.append((neg_descriptor, neg_list[i][1]))

    return pos_descriptors, neg_descriptors


# ===训练BOW训练器===
# 未完不续...

 

原书中的所有代码 《 Learning_OpenCV_3_Computer_Vision_with_Python_Second_Edition_Code.zip》

链接:https://pan.baidu.com/s/1kMHxoQMndE6R_8XN9PnEhA
提取码:62yq

 

参考文章

https://juejin.im/post/5b0e70686fb9a00a1451c8e7

https://boge.me/blog/article/3/HOG-SVM-Shi-Xian-Xing-Ren-Shi-Bie/

https://pengzhaoqing.github.io/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/hog/

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!