传统目标检测实战:HOG+SVM

news2024/10/3 8:24:21

传统目标检测实战:HOG+SVM

文章目录

  • 传统目标检测实战:HOG+SVM
    • 1. 前言
      • 1.1 传统和深度
      • 1.2 何为传统目标检测
      • 1.3 传统目标检测方法不足
    • 2. 先验知识
    • 3. 项目框架
      • 3.1 文件架构
      • 3.2 方法简要介绍
    • 4. 工具函数(utils.py)
    • 5. 特征提取(extract_feature.py)
    • 6. 训练分类器(train.py)
    • 7. 测试(test.py)
    • 8. 困难样本挖掘(neg_mining.py)
    • 9. 总结

1. 前言

1.1 传统和深度

在深度学习出现之前,传统的目标检测方法大概分为区域选择(滑窗)、特征提取(SIFT、HOG等)、**分类器(SVM、Adaboost等)**三个部分,其主要问题有两方面:一方面滑窗选择策略没有针对性、时间复杂度高,窗口冗余;另一方面手工设计的特征鲁棒性较差。自深度学习出现之后,目标检测取得了巨大的突破,最瞩目的两个方向有:

  • 以RCNN为代表的基于Region Proposal的深度学习目标检测算法(RCNN,SPP-NET,Fast-RCNN,Faster-RCNN等),它们是two-stage的,需要先使用启发式方法(selective search)或者CNN网络(RPN)产生Region Proposal,然后再在Region Proposal上做分类与回归。
  • 以YOLO为代表的基于回归方法的深度学习目标检测算法(YOLO,SSD等),其仅仅使用一个CNN网络直接预测不同目标的类别与位置。

1.2 何为传统目标检测

首先我们先来了解一下什么是目标检测?简单来说就是把存在的目标从图片中找到并识别出来。我们发现这对于我们人来说十分简单,但对于计算机而言,它是怎么做到的呢?

  • 传统目标检测方法分为三部分:区域选择 → 特征提取 → 分类器
    即首先在给定的图像上选择一些候选的区域,然后对这些区域提取特征,最后使用训练的分类器进行分类。下面我们对这三个阶段分别进行介绍。
  1. 区域选择:这一步是为了对目标的位置进行定位。由于目标可能出现在图像的任何位置,而且目标的大小、长宽比例也不确定,所以最初采用滑动窗口的策略对整幅图像进行遍历,而且需要设置不同的尺度,不同的长宽比。这种穷举的策略虽然包含了目标所有可能出现的位置,但是缺点也是显而易见的:时间复杂度太高,产生冗余窗口太多,这也严重影响后续特征提取和分类的速度和性能。(实际上由于受到时间复杂度的问题,滑动窗口的长宽比一般都是固定的设置几个,所以对于长宽比浮动较大的多类别目标检测,即便是滑动窗口遍历也不能得到很好的区域)。

  2. 特征提取:由于目标的形态多样性,光照变化多样性,背景多样性等因素使得设计一个鲁棒的特征并不是那么容易。然而提取特征的好坏直接影响到分类的准确性。(这个阶段常用的特征有SIFT、HOG等)

  3. 分类器:主要有SVM,Adaboost等。

1.3 传统目标检测方法不足

总结一下,传统目标检测存在的两个主要问题:

  • 基于滑动窗口的区域选择策略没有针对性,时间复杂度高,窗口冗余;
  • 手工设计的特征对于多样性的变化并没有很好的鲁棒性。

2. 先验知识

  1. 机器视觉特征简单介绍:HOG、SIFT、SURF、ORB、LBP、HAAR
  2. skimage.feature–corner_harris、hog、local_binary_pattern说明
  3. SVM很好理解,就是分类器

3. 项目框架

**本文主要是针对裂缝目标检测的项目。**也可扩展到其他的目标检测。!!!

3.1 文件架构

1

  • data代表数据文件夹,下有img(原始图像有正样本(有裂缝)和负样本(无裂缝))、feature(对原始图像提取特征后,得到正样本特征和负样本特征)、model(保存训练好的分类器)、result(预测的结果)和test_img(预测时所用到的图像)。
  • 其余的py文件,下面详细介绍。

3.2 方法简要介绍

321

322
323

4. 工具函数(utils.py)

from skimage.feature import hog, local_binary_pattern

# settings for LBP
radius = 3
n_points = 8 * radius

# settings for HOG
ppc = (32, 32)
cpb = (3, 3)


def get_hog_feature(img):
    return hog(img, orientations=9, pixels_per_cell=ppc, cells_per_block=cpb, block_norm='L1', transform_sqrt=False, feature_vector=True)

def get_lbp_feature(img):
    return local_binary_pattern(img, n_points, radius)


def sliding_window(image, window_size, step_size):
    for row in range(0, image.shape[0], step_size[0]):
        for col in range(0, image.shape[1], step_size[1]):
            yield (row, col, image[row:row + window_size[0], col:col + window_size[1]])


def overlapping_area(detection_1, detection_2, show = False):
    '''
    计算两个检测区域覆盖大小,detection:(x, y, pred_prob, width, height, area)
    '''
    # Calculate the x-y co-ordinates of the
    # rectangles
    # detection_1的 top left 和 bottom right
    x1_tl = detection_1[0]
    y1_tl = detection_1[1]
    x1_br = detection_1[0] + detection_1[3]
    y1_br = detection_1[1] + detection_1[4]

    # detection_2的 top left 和 bottom right
    x2_tl = detection_2[0]
    y2_tl = detection_2[1]
    x2_br = detection_2[0] + detection_2[3]
    y2_br = detection_2[1] + detection_2[4]

    # 计算重叠区域
    x_overlap = max(0, min(x1_br, x2_br) - max(x1_tl, x2_tl))
    y_overlap = max(0, min(y1_br, y2_br) - max(y1_tl, y2_tl))
    overlap_area = x_overlap * y_overlap
    area_1 = detection_1[3] * detection_1[4]
    area_2 = detection_2[3] * detection_2[4]

    # 1. 重叠比例计算1
    # total_area = area_1 + area_2 - overlap_area
    # return overlap_area / float(total_area)

    # 2.重叠比例计算2
    area = area_1
    if area_1 < area_2: 
        area = area_2
    return float(overlap_area / area)


def nms(detections, threshold=0.5):
    '''
    抑制策略:
    1. 最大的置信值先行
    2. 最大的面积先行
    非极大值抑制减少重叠区域, detection:(x, y, pred_prob, width, height)
    '''
    if len(detections) == 0:
        return []
    # Sort the detections based on confidence score
    # 根据预测值大小排序预测结果
    detections = sorted(detections, key=lambda detections: detections[2], reverse=True)
    # print((detections[0][5], detections[-1][5]))
    # Unique detections will be appended to this list
    # 非极大值抑制后的检测区域
    new_detections=[]
    # Append the first detection
    # 默认第一个区域置信度最高是正确检测区域
    new_detections.append(detections[0])
    # Remove the detection from the original list
    # 去除以检测为正确的区域
    del detections[0]
    # For each detection, calculate the overlapping area
    # and if area of overlap is less than the threshold set
    # for the detections in `new_detections`, append the
    # detection to `new_detections`.
    # In either case, remove the detection from `detections` list.
    print(len(detections))

    for index, detection in enumerate(detections):
        if len(new_detections) >= 20:
            break
        overlapping_small = True
        # 重叠区域过大,则删除该区域,同时结束检测,过小则继续检测
        for new_detection in new_detections:
            if overlapping_area(detection, new_detection) > threshold:
                overlapping_small = False
                break
        # 整个循环中重叠区域都小那么增加
        if overlapping_small:
            new_detections.append(detection)
    return new_detections

5. 特征提取(extract_feature.py)

import numpy as np
import joblib
import os
import glob
from utils import *
import cv2

# setting for img_resize
window_size = (256, 256)

train_dataset_path = os.path.expanduser('./data/img')
feat_path = './data/feature'
feat_pos_path = os.path.join(feat_path, 'pos')
feat_neg_path = os.path.join(feat_path, 'neg')

train_dataset_pos_lists = glob.glob(os.path.join(train_dataset_path, 'pos/*.jpg'))
train_dataset_neg_lists = glob.glob(os.path.join(train_dataset_path, 'neg/*.jpg'))

# 正样本特征存储
for pos_path in train_dataset_pos_lists:
    pos_im = cv2.imread(pos_path, cv2.IMREAD_GRAYSCALE)
    pos_im = cv2.resize(pos_im, window_size)
    pos_lbp = get_hog_feature(pos_im)
    pos_lbp = pos_lbp.reshape(-1)
    feat_pos_name = os.path.splitext(os.path.basename(pos_path))[0] + '.feat'
    joblib.dump(pos_lbp, os.path.join(feat_pos_path, feat_pos_name))

# 负样本特征存储
for neg_path in train_dataset_neg_lists:
    neg_im = cv2.imread(neg_path, cv2.IMREAD_GRAYSCALE)
    neg_im = cv2.resize(neg_im, window_size)
    neg_lbp = get_hog_feature(neg_im)
    neg_lbp = neg_lbp.reshape(-1)
    feat_neg_name = os.path.splitext(os.path.basename(neg_path))[0] + '.feat'
    joblib.dump(neg_lbp, os.path.join(feat_neg_path, feat_neg_name))

6. 训练分类器(train.py)

from sklearn.svm import LinearSVC, SVC
import numpy as np
import joblib
import os
import glob

feat_path = './data/feature'
feat_pos_path = os.path.join(feat_path, 'pos')
feat_neg_path = os.path.join(feat_path, 'neg')
train_feat_pos_lists = glob.glob(os.path.join(feat_pos_path, '*.feat'))
train_feat_neg_lists = glob.glob(os.path.join(feat_neg_path, '*.feat'))
X = []
y = []

# 加载正例样本
for feat_pos in train_feat_pos_lists:
    feat_pos_data = joblib.load(feat_pos)
    X.append(feat_pos_data)
    y.append(1)
#     print('feat_pos_data.shape:', feat_pos_data.shape)

# 加载负例样本
for feat_neg in train_feat_neg_lists:
    feat_neg_data = joblib.load(feat_neg)
    X.append(feat_neg_data)
    y.append(0)
#     print('feat_neg_data.shape:', feat_neg_data.shape)

clf = LinearSVC(dual = False)
# clf = SVC()
clf.fit(X, y)
clf.score(X, y)

model_path = './data/model'
joblib.dump(clf, os.path.join(model_path, 'svm.model'))
print(len(X),X[0].shape)

7. 测试(test.py)

import numpy as np
import joblib
import os
import glob
from skimage.transform import pyramid_gaussian
import cv2
from utils import *

window_size = (256, 256)

step_size = (128, 128)

img_name = '2.jpg'
test_image = cv2.imread("./data/test_img/" + img_name, cv2.IMREAD_GRAYSCALE)

model_path = './data/model'
clf = joblib.load(os.path.join(model_path, 'svm.model'))

scale = 0
detections = []
downscale = 1.25

for test_image_pyramid in pyramid_gaussian(test_image, downscale=downscale):
    if test_image_pyramid.shape[0] < window_size[0] or test_image_pyramid.shape[1] < window_size[1]:
        break
    for (row, col, sliding_image) in sliding_window(test_image_pyramid, window_size, step_size):
        if sliding_image.shape != window_size:
            continue
        sliding_image_lbp = get_hog_feature(sliding_image)
        sliding_image_lbp = sliding_image_lbp.reshape(1, -1)
        pred = clf.predict(sliding_image_lbp)
        if pred==1:
            pred_prob = clf.decision_function(sliding_image_lbp)
            (window_height, window_width) = window_size
            real_height = int(window_height*downscale**scale)
            real_width = int(window_width*downscale**scale)
            detections.append((int(col*downscale**scale), int(row*downscale**scale), pred_prob, real_width, real_height, real_height*real_width))
    scale+=1

test_image1 = cv2.imread("./data/test/" + img_name, 1)
test_image_detect = test_image1.copy()
for detection in detections:
    col = detection[0]
    row = detection[1]
    width = detection[3]
    height = detection[4]
    cv2.rectangle(test_image_detect, pt1=(col, row), pt2=(col+width, row+height), color=(255, 0, 0), thickness=4)

print('before NMS')
cv2.imwrite("./data/result/_"+ img_name, test_image_detect)

threshold = 0.2
detections_nms = nms(detections, threshold)

test_image_detect = test_image1.copy()
for detection in detections_nms:
    col = detection[0]
    row = detection[1]
    width = detection[3]
    height = detection[4]
    cv2.rectangle(test_image_detect, pt1=(col, row), pt2=(col+width, row+height), color=(0, 255, 0), thickness=4)

print('after NMS')
cv2.imwrite("./data/result/"+ img_name, test_image_detect)

8. 困难样本挖掘(neg_mining.py)

由于预测的结果存在很大的偏差,在于训练不到位,这时候需要将那些预测错误的样本再次训练。

from sklearn.svm import LinearSVC, SVC
import matplotlib.pyplot as plt
import numpy as np
from scipy import misc
import joblib
import os
import glob
from skimage.feature import hog
feat_path = './data/feature'
feat_pos_path = os.path.join(feat_path, 'pos')
feat_neg_path = os.path.join(feat_path, 'neg')
train_feat_pos_lists = glob.glob(os.path.join(feat_pos_path, '*.feat'))
train_feat_neg_lists = glob.glob(os.path.join(feat_neg_path, '*.feat'))

model_path = './data/model'
clf = joblib.load(os.path.join(model_path, 'svm.model'))

# 128
for i in range(0, 5):
    x = []
    y = []
    cnt = 0
    for feat_neg in train_feat_neg_lists:
        feat_neg_data = joblib.load(feat_neg)
        feat_neg_data1 = feat_neg_data.reshape(1, -1)
        pred = clf.predict(feat_neg_data1)
        if pred == 1:
            cnt += 1
            x.append(feat_neg_data)
            y.append(0)
    cnt1 = 0
    for feat_pos in train_feat_pos_lists:
        feat_pos_data = joblib.load(feat_pos)
        feat_pos_data1 = feat_pos_data.reshape(1, -1)
        pred = clf.predict(feat_pos_data1)
        if pred == 0:
            cnt1 += 1
            x.append(feat_pos_data)
            y.append(1)
    clf.fit(x, y)
    print(cnt, cnt1)

# model_path = './data/model'
# joblib.dump(clf, os.path.join(model_path, 'svm.model'))

9. 总结

通过这样的目标检测代码框架,可以得出还不错的结果。有错误欢迎指出!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/337609.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

我是如何转岗成为数据分析师?

Datawhale干货 作者&#xff1a;孟禹&#xff0c;数据分析师&#xff0c;Datawhale邀约作者笔者背景介绍&#xff1a;工作5年半&#xff0c;前4年在K12在线教育负责教研和用户转化&#xff0c;21年双减之后跳槽到一家新消费品牌公司做数据分析师&#xff0c;跨了行转了岗。现在…

领英LinkedIn的个人商务会员和企业销售会员我们应该怎么选?

在详细介绍领英LinkedIn会员之前先解释下为什么要开通。也就是领英免费用户会有哪些限制以至于我们需要付费去开通会员&#xff1a;1.有限的搜索次数使用免费的 LinkedIn 帐户&#xff0c;您将在一个月内搜索约 300 次后达到商业使用限制。一旦达到商业使用限制&#xff0c;您将…

SpringBoot学习笔记【part18】拦截器与文件上传

一、拦截器Interceptor 拦截器 Interceptor 多用于登录检查与静态资源放行场景。 拦截器的实现步骤 编写一个拦截器实现 HandlerInterceptor 接口 Slf4j public class LoginInterceptor implements HandlerInterceptor {/*** 目标方法执行之前*/Overridepublic boolean preHa…

Redis 分布式基础——主从复制其实挺简单

Redis 主从同步 redis-master-slave-index 一、主从复制是啥 主从复制&#xff0c;或者叫 主从同步&#xff0c;是指将一台 Redis 服务器的数据&#xff0c;复制到其他的 Redis 服务器。前者称为 主节点(master)&#xff0c;后者称为 从节点(slave)。且数据的复制是 单向 的&…

GitLab CI-CD 学习笔记

概述 1. CI/CD CI&#xff08;持续集成&#xff09;指开发人员一天内进行多次合并和提交代码操作&#xff0c;并通过自动化测试&#xff0c;完成构建 CD&#xff08;持续部署&#xff09;指每次代码更改都会自动部署到对应环境 CI/CD 结合在一起&#xff0c;可以加快开发团…

如何写出优雅的代码

最近在做代码审查时&#xff0c;发现一个问题&#xff0c;就是代码不够优雅。代码可以有bug&#xff0c;但不能不优雅&#xff0c;毕竟代码不只是运行程序&#xff0c;凡是需要维护的代码都是给人看的&#xff0c;你的代码风格侧面反映了你的编码习惯、思维逻辑和专业性。那么如…

【强化学习】马尔可夫决策过程MDP

1.马尔可夫决策过程MDP 1.1 MDP五元组 MDP<S,A,P,R,γ>MDP<\mathcal{S},\mathcal{A},\mathcal{P},\mathcal{R},\mathcal{\gamma}>MDP<S,A,P,R,γ>&#xff0c;其中&#xff1a; S\mathcal{S}S&#xff1a;状态空间A\mathcal{A}A&#xff1a;动作空间P\mathc…

【VictoriaMetrics】VictoriaMetrics单机版部署(二进制版)

1、下载安装包git路径,本文基于1.87.1版本 进入git地址 :https://github.com/VictoriaMetrics/VictoriaMetrics/tags 2、下载其中linux下的 amd64架构

【数据结构】单向链表的练习题

目录 前言 1、删除链表中等于给定值val的所有节点。 【题目描述】 【代码示例】 【 画图理解】 2、反转一个点链表 【题目描述】 【 代码思路】 【代码示例】 【画图理解】 3、给定一个带有头节点head的非空单链表&#xff0c;返回链表的中间节点&#xff0c;如果有两个…

三维重建——NeuralRecon项目源码解读

代码链接见文末 首先,需要指明的是NeuralRecon项目不支持windows,主要是使用到的例如torchsprase等不支持windows,您也可以在网上查找相应的替代方法。 1.数据与环境配置 ScanNet数据的获取首先需要向作者发送邮件,作者会回复一个下载的脚本,在提供的代码中,提供了…

Mysql数据库09——分组聚合函数

类似pandas里面的groupby函数&#xff0c;SQL里面的GROUP BY子句也是可以达到分组聚合的效果。 常用的聚合函数有COUNT(),SUM(),AVG(),MAX(),MIN()&#xff0c;其用法看名字都看的出来&#xff0c;下面一一介绍 聚合函数 COUNT()计数 统计student表中计科系学生的人数。 SE…

基于nodejs+vue疫情网课管理系统

疫情网课也都将通过计算机进行整体智能化操作,对于疫情网课管理系统所牵扯的管理及数据保存都是非常多的,例如管理员&#xff1a;首页、个人中心、学生管理、教师管理、班级管理、课程分类管理、课程表管理、课程信息管理、作业信息管理、请假信息管理、上课签到管理、论坛交流…

企业微信应用授权,第一次不授权手机号后如何再次开启

文章目录前言一、关于企业微信应用授权1、新建一个应用2、企业微信应用授权二、第一次拒绝后如何手动开启总结前言 简单记录一下&#xff1a; 最近做了几个企业微信应用授权web&#xff0c;和普通微信公众号授权差不多&#xff0c;记录一下 一、关于企业微信应用授权 1、新建…

失手删表删库,赶紧跑路?!

在数据资源日益宝贵的数字时代公司最怕什么&#xff1f;人还在&#xff0c;库没了是粮库、车库&#xff0c;还是小金库&#xff1f;实际上&#xff0c;这里的“库”是指的数据库Ta是公司各类信息的保险柜小到企业官网和客户信息大到金融机构的资产数据和国家秘密即便没有跟数据…

Linux服务器开发-2. Linux多进程开发

文章目录1. 进程概述1.1 程序概览1.2 进程概念1.3 单道、多道程序设计1.4 时间片1.5 并行与并发1.6 进程控制块&#xff08;PCB&#xff09;2. 进程的状态转换2.1 进程的状态2.2 进程相关命令查看进程实时显示进程动态杀死进程进程号和相关函数3. 进程的创建-fork函数3.1 进程创…

抖音线索信息自动汇总到SeaTable流程搭建示例

每当抖音有新意向用户添加时&#xff0c;往往被企业视为意向客户&#xff0c;常需要运营人员查看后同步到SeaTable表单系统进行汇总&#xff0c;但运营人员时常会遗忘&#xff0c;并且难免会遗漏掉部分信息&#xff0c;导致部分意向客户无人跟进&#xff0c;最终流失。 那么&a…

GO语音-切片使用的雷区与性能优化相关

文章目录前言一、切片是什么&#xff1f;二、切片使用注意项1.避免复制数组2.切片初始化3.切片GC三、切片使用注意什么1. 大家来思考一个代码示例&#xff1a;2. 修改切片的值3. 降低切片重复申请内存总结前言 在 Go 语言中&#xff0c;切片(slice)可能是使用最为频繁的数据结…

数据结构 | 线性表

&#x1f525;Go for it!&#x1f525; &#x1f4dd;个人主页&#xff1a;按键难防 &#x1f4eb; 如果文章知识点有错误的地方&#xff0c;请指正&#xff01;和大家一起学习&#xff0c;一起进步&#x1f440; &#x1f4d6;系列专栏&#xff1a;数据结构与算法 &#x1f52…

Wise-IoU 作者导读:基于动态非单调聚焦机制的边界框损失

论文地址&#xff1a;Wise-IoU: Bounding Box Regression Loss with Dynamic Focusing Mechanism GitHub&#xff1a;https://github.com/Instinct323/wiou 摘要&#xff1a;目标检测作为计算机视觉的核心问题&#xff0c;其检测性能依赖于损失函数的设计。边界框损失函数作为…

153、【动态规划】leetcode ——416. 分割等和子集:滚动数组(C++版本)

题目描述 原题链接&#xff1a;1049. 最后一块石头的重量 II 解题思路 本题要找的是最小重量&#xff0c;我们可以将石头划分成两个集合&#xff0c;当两个集合的重量越接近时&#xff0c;相减后&#xff0c;可达到的装量就会是最小&#xff0c;此时本题的思路其实就类似于 4…