SSD-tensorflow Windows环境下,mAP的计算
SSD-tensorflow Windows环境下,mAP的计算
我的SSD是tensorflow版的,该版本里面提供的eval_ssd_network.py不能使用,或是没有mAP计算的代码,根据查找资料,明白了计算的原理,并最终在demo的代码上进行修改,生成可用的获取detection_boxes。
1.该方法的原理
- 使用训练好的模型检测test图片,并将检测生成的类别name,score,boxes保存到TXT文本中。
- 读取test图片的annotations,将ground truth保存在TXT文本中
- 计算检测的boxes和ground truth的iou,大于阈值就认为TP,小于的为FP,然后根据原理计算precision和AP,最后计算mAP。
代码如下:
2. 利用demo生成detection boxes
上代码之前,需要首先介绍一下,所需参数和文件夹,demo为存储所有test图片的文件夹,然后在该文件夹下需要创建一个voc_classes.txt存储你所有的类别。我的类别一共5个,所以我的内容为下图所示。
# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing CSDN address:https://blog.csdn.net/zzZ_CMing
# -*- 2018/07/20; 15:19
# -*- python3.6
import os
import math
import random
import numpy as np
import tensorflow as tf
import cv2
#import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from nets import ssd_vgg_300, ssd_common, np_methods
from preprocessing import ssd_vgg_preprocessing
from notebooks import visualization
import sys
from timeit import default_timer as timer
from PIL import Image, ImageFont, ImageDraw
import time
import xml.etree.ElementTree as ET
sys.path.append('../')
slim = tf.contrib.slim
# TensorFlow session: grow memory when needed. TF, DO NOT USE ALL MY GPU MEMORY!!!
gpu_options = tf.GPUOptions(allow_growth=True)
config = tf.ConfigProto(log_device_placement=False, gpu_options=gpu_options)
isess = tf.InteractiveSession(config=config)
# 定义数据格式,设置占位符
net_shape = (300, 300)
# 输入图像的通道排列形式,'NHWC'表示 [batch_size,height,width,channel]
data_format = 'NHWC'
# 预处理,以Tensorflow backend, 将输入图片大小改成 300x300,作为下一步输入
img_input = tf.placeholder(tf.uint8, shape=(None, None, 3))
# 数据预处理,将img_input输入的图像resize为300大小,labels_pre,bboxes_pre,bbox_img待解析
image_pre, labels_pre, bboxes_pre, bbox_img = ssd_vgg_preprocessing.preprocess_for_eval(
img_input, None, None, net_shape, data_format, resize=ssd_vgg_preprocessing.Resize.WARP_RESIZE)
# 拓展为4维变量用于输入
image_4d = tf.expand_dims(image_pre, 0)
# 定义SSD模型
# 是否复用,目前我们没有在训练所以为None
reuse = True if 'ssd_net' in locals() else None
# 调出基于VGG神经网络的SSD模型对象,注意这是一个自定义类对象
ssd_net = ssd_vgg_300.SSDNet()
# 得到预测类和预测坐标的Tensor对象,这两个就是神经网络模型的计算流程
with slim.arg_scope(ssd_net.arg_scope(data_format=data_format)):
predictions, localisations, _, _ = ssd_net.net(image_4d, is_training=False, reuse=reuse)
# 导入新训练的模型参数
ckpt_filename = './train_model/model.ckpt-50000' # 注意xxx代表的数字是否和文件夹下的一致
# ckpt_filename = '../checkpoints/VGG_VOC0712_SSD_300x300_ft_iter_120000.ckpt'
isess.run(tf.global_variables_initializer())
saver = tf.train.Saver()
saver.restore(isess, ckpt_filename)
# 在网络模型结构中,提取搜索网格的位置
# 根据模型超参数,得到每个特征层(这里用了6个特征层,分别是4,7,8,9,10,11)的anchors_boxes
ssd_anchors = ssd_net.anchors(net_shape)
"""
每层的anchors_boxes包含4个arrayList,前两个List分别是该特征层下x,y坐标轴对于原图(300x300)大小的映射
第三,四个List为anchor_box的长度和宽度,同样是经过归一化映射的,根据每个特征层box数量的不同,这两个List元素
个数会变化。其中,长宽的值根据超参数anchor_sizes和anchor_ratios制定。
"""
# 主流程函数
def process_image(img, select_threshold=0.4, nms_threshold=.01, net_shape=(300, 300)):
# select_threshold:box阈值——每个像素的box分类预测数据的得分会与box阈值比较,高于一个box阈值则认为这个box成功框到了一个对象
# nms_threshold:重合度阈值——同一对象的两个框的重合度高于该阈值,则运行下面去重函数
# 执行SSD模型,得到4维输入变量,分类预测,坐标预测,rbbox_img参数为最大检测范围,本文固定为[0,0,1,1]即全图
rimg, rpredictions, rlocalisations, rbbox_img = isess.run([image_4d, predictions, localisations, bbox_img],
feed_dict={img_input: img})
# ssd_bboxes_select()函数根据每个特征层的分类预测分数,归一化后的映射坐标,
# ancohor_box的大小,通过设定一个阈值计算得到每个特征层检测到的对象以及其分类和坐标
rclasses, rscores, rbboxes = np_methods.ssd_bboxes_select(
rpredictions, rlocalisations, ssd_anchors,
select_threshold=select_threshold, img_shape=net_shape, num_classes=6, decode=True)
"""
这个函数做的事情比较多,这里说的细致一些:
首先是输入,输入的数据为每个特征层(一共6个,见上文)的:
rpredictions: 分类预测数据,
rlocalisations: 坐标预测数据,
ssd_anchors: anchors_box数据
其中:
分类预测数据为当前特征层中每个像素的每个box的分类预测
坐标预测数据为当前特征层中每个像素的每个box的坐标预测
anchors_box数据为当前特征层中每个像素的每个box的修正数据
函数根据坐标预测数据和anchors_box数据,计算得到每个像素的每个box的中心和长宽,这个中心坐标和长宽会根据一个算法进行些许的修正,
从而得到一个更加准确的box坐标;修正的算法会在后文中详细解释,如果只是为了理解算法流程也可以不必深究这个,因为这个修正算法属于经验算
法,并没有太多逻辑可循。
修正完box和中心后,函数会计算每个像素的每个box的分类预测数据的得分,当这个分数高于一个阈值(这里是0.5)则认为这个box成功
框到了一个对象,然后将这个box的坐标数据,所属分类和分类得分导出,从而得到:
rclasses:所属分类
rscores:分类得分
rbboxes:坐标
最后要注意的是,同一个目标可能会在不同的特征层都被检测到,并且他们的box坐标会有些许不同,这里并没有去掉重复的目标,而是在下文
中专门用了一个函数来去重
"""
# 检测有没有超出检测边缘
rbboxes = np_methods.bboxes_clip(rbbox_img, rbboxes)
rclasses, rscores, rbboxes = np_methods.bboxes_sort(rclasses, rscores, rbboxes, top_k=400)
# 去重,将重复检测到的目标去掉
rclasses, rscores, rbboxes = np_methods.bboxes_nms(rclasses, rscores, rbboxes, nms_threshold=nms_threshold)
# 将box的坐标重新映射到原图上(上文所有的坐标都进行了归一化,所以要逆操作一次)
rbboxes = np_methods.bboxes_resize(rbbox_img, rbboxes)
return rclasses, rscores, rbboxes
def _get_class(classes_path_):
classes_path = os.path.expanduser(classes_path_)
with open(classes_path) as f:
class_names = f.readlines()
class_names = [c.strip() for c in class_names]
return class_names
def writeboxestotxt(file,image,out_classes,out_scores,out_boxes):
'''
Args:
image: 原图
out_classes: 输出类别
out_scores: 输出类别的得分
out_boxes: 输出的预测框
Returns:无
'''
height = img.shape[0]
width = img.shape[1]
for i, c in reversed(list(enumerate(out_classes))):
predicted_class = str(class_names[out_classes[i]-1])
box = out_boxes[i]
score = out_scores[i]
label = '{} {:.2f}'.format(predicted_class, score)
ymin = int(box[0] * height)
xmin = int(box[1] * width)
ymax = int(box[2] * height)
xmax = int(box[3] * width)
# 写入检测位置
file.write(predicted_class + ' ' + str(score) + ' ' + str(xmin) + ' ' + str(
ymin) + ' ' + str(xmax) + ' ' + str(ymax) + '\n')
end = timer()
print('time consume:%.3f s ' % (end - start))
# 测试的文件夹
path = './demo/'
# 创建创建一个存储检测结果的dir
detection_results = './detection-results'
if not os.path.exists(detection_results):
os.makedirs(detection_results)
# result如果之前存放的有文件,全部清除
for i in os.listdir(detection_results):
path_file = os.path.join(detection_results, i)
if os.path.isfile(path_file):
os.remove(path_file)
# 创建一个记录检测结果的文件
class_txt = path + '/voc_classes.txt'
class_names = _get_class(class_txt)
image_names = sorted(os.listdir(path))
#num_images = len(image_names)#获取图片的个数
for filename in os.listdir(path):
image_path = path + '/' + filename
portion = os.path.split(image_path)
image_id = filename.split('.')
file = open(detection_results + '/' + image_id[0] + '.txt', "a")
#file.write(portion[1] + ' ')
start = timer() # 开始计时
img = mpimg.imread(path + filename)
rclasses, rscores, rbboxes = process_image(img)
image = Image.open(image_path)
#file.write('find ' + str(len(rbboxes)) + ' target(s) \n')
writeboxestotxt(file,image, rclasses, rscores, rbboxes)#将检测的图片记录到TXT文件中
file.close()
# r_image.show() 显示检测结果
image_save_path = './result/result_' + portion[1]
print('detect result save to....:' + image_save_path)
visualization.plt_bboxes(img, rclasses, rscores, rbboxes)#可视化
该代码运行之后,会生成一个detection-results文件夹,存储按照图片名称创建的TXT,里面存储的内容为如下所示:
3.生成ground truth boxes代码
改代码是参考一个b站博主的视频中提供的代码,但是现在我找不到网址了,如果作者看到,请联系我,我添加引用。
原理是,读取VOC2007中生成的test.txt文件中图片的名称,然后从xml文件中把ground truth的内容读取到TXT文件中。
import sys
import os
import glob
import xml.etree.ElementTree as ET
image_ids = open('./VOC2007/ImageSets/Main/test.txt').read().strip().split()
if not os.path.exists("./ground-truth"):
os.makedirs("./ground-truth")
if not os.path.exists("./ground-truth"):
os.makedirs("./ground-truth")
for image_id in image_ids:
with open("./ground-truth/"+image_id+".txt", "a") as new_f:
root = ET.parse("./VOC2007/Annotations/"+image_id+".xml").getroot()
for obj in root.findall('object'):
obj_name = obj.find('name').text
bndbox = obj.find('bndbox')
left = bndbox.find('xmin').text
top = bndbox.find('ymin').text
right = bndbox.find('xmax').text
bottom = bndbox.find('ymax').text
new_f.write("%s %s %s %s %s\n" % (obj_name, left, top, right, bottom))
print("Conversion completed!")
代码运行之后会生成一个ground-truth文件中,内容为按照test文件的图片名称生成的TXT文件,内容如下图所示:
4.生成images-optional文件所需的test图片
import os
import shutil
jpgeImages_folder = './VOC2007/JPEGImages/' # 自己存储图片的文件夹的路径
testImage_path = './input/images-optional' # 自己另存为测试图片的文件夹的路径
f = open("./VOC2007/ImageSets/Main/test.txt","r") #设置文件对象
datalist = []
line = f.readline()
line = line[:-1]
datalist.append(line+'.jpg')
while line: #直到读取完文件
line = f.readline() #读取一行文件,包括换行符
line = line[:-1] #去掉换行符,也可以不去
datalist.append(line+'.jpg')
f.close() #关闭文件
f2 = open("./VOC2007/ImageSets/Main/test.txt","r") #设置文件对象
xmlList = []
lineXml = f2.readline()
lineXml = lineXml[:-1]
xmlList.append(lineXml+'.xml')
while lineXml: #直到读取完文件
lineXml = f2.readline() #读取一行文件,包括换行符
lineXml = lineXml[:-1] #去掉换行符,也可以不去
xmlList.append(lineXml+'.xml')
f2.close() #关闭文件
#读取指定的文件夹,JPEGImages
def file_name(file_dir):
L = []
for root, dirs, files in os.walk(file_dir):
for file in files:
if os.path.splitext(file)[1] == '.jpg':
L.append(os.path.join(root, file))
return L
#读取指定的文件夹,Annotations
def xmlfile_name(file_dir):
L = []
for root, dirs, files in os.walk(file_dir):
for file in files:
if os.path.splitext(file)[1] == '.xml':
L.append(os.path.join(root, file))
return L
#复制test文件中image文件到特定文件夹
image_dirs = file_name(jpgeImages_folder)
ncount = 0
for i in range(0, len(image_dirs)):
curLine = image_dirs[i].strip().split("/")
d = curLine[-1]
if d == datalist[ncount]:
ncount = ncount+1
print(image_dirs[i])
shutil.copy(image_dirs[i], testImage_path+d)
5.运行main文件
该main文件是参考https://github.com/Cartucho/mAP的main文件,将步骤2、3、4生成的文件放到input的对应位置,然后运行就行了。
6.效果图和记录文件
在output文件夹下会生成如下的图片,运行成功。
本文地址:https://blog.csdn.net/qq_28664681/article/details/107874757
上一篇: 看代码看代码
下一篇: 新手小白当肝帝的第一次签到
推荐阅读
-
windows环境下mysql数据库的主从同步备份步骤(单向同步)
-
windows下java环境变量的设置方法
-
windows环境下mysql数据库的主从同步备份步骤(单向同步)
-
Windows下JSP开发环境的配置
-
Windows环境下的MYSQL5.7配置文件定位图文分析
-
Windows环境下安装PHP Pear的方法图文教程
-
解决Windows环境下安装 mysql-8.0.11-winx64 遇到的问题
-
4种Windows系统下Laravel框架的开发环境安装及部署方法详解
-
Windows系统下使用flup搭建Nginx和Python环境的方法
-
windows环境下php配置memcache的具体操作步骤