<div id="post_detail">
<div class="post">
<h2>
<a id="cb_post_title_url" href="https://www.cnblogs.com/caffeaoto/p/8758962.html">用Tensorflow做蝴蝶检测</a>
</h2>
<div class="postbody">
<div id="cnblogs_post_body" class="blogpost-body"><p>报名了一个蝴蝶检测比赛,一共给了700多张图,包含94种蝴蝶类别,要求检测出图片中的蝴蝶并正确分类。</p>
1.
https://www.cnblogs.com/caffeaoto/p/8758962.html
拿到数据集后,第一部就是将700多张图分成了 483张训练样本和238张测试样本(由于数据集中,有15种类别的蝴蝶只有一张,所以在测试样本中,仅包含了79种蝴蝶类别)
2.利用一个现有的包含蝴蝶类别的模型直接对测试集中的蝴蝶进行检测(相当于二分类),这里选用的是“ faster_rcnn_inception_resnet_v2_atrous_oid_2018_01_28 ”模型。该模型是在Open Image 数据集上训练的,总共有545个不同物体类别。
先是看的 object_detection/object_detection_tutorial.ipynb 这里直接导入的是frozen model,导致无法修改阈值,所以师兄换了一种模型导入方式,可以把检测的阈值调低,以提高检测到的蝴蝶数目
然后我需要做的是对检测的结果进行评估(计算Percison),但是走了点弯路。
tensorflow教程中的流程是,先把测试集转成TFrecord格式,然后再inference之后,直接在后面加入检测到的结果。但是在模型的输入处出现的问题,因为原本Frozen Model的输入是一个tensor,而师兄修改后的模型输入直接就是 image,所以我不知道怎么将输入进行转换。这里还需要后续的学习。于是我就换了一个方向,以师兄的code为基础,每次从文件夹中提出一张image,进行inference,然后我就根据image信息,解析其对应的xml文件中的信息,并写成tf_example的格式;同时将模型的输出dets添加到刚刚生成的example中。这样就解决了之前的问题,顺利把 annotation+detection结果保存成了 TFrecord格式。
下一步就是利用 object_detection/metrics/offline_eval_map_corloc.py 进行评估,但是出现了两个问题,导致我一度陷入僵局。第一个就是出现了 " ground_truth_group_of .size :None type has no attrbute to size ",我以为是我的TFrecord出现了问题,但是最后发现是因为
decoded_dict = data_parser.parse(example)
这里解析的时候,由于我原本TFrecord中并没有写入 standard_fields.TfExampleFields.object_group_of.object_group_of 信息,所以在解析的时候,这个内容就被填上了None ,所以不存在size,导致上面的问题产生。
self.optional_items_to_handlers = { fields.InputDataFields.groundtruth_difficult: Int64Parser(fields.TfExampleFields.object_difficult), fields.InputDataFields.groundtruth_group_of: Int64Parser(fields.TfExampleFields.object_group_of)
查下 open image 中的特有的group_of参数是什么意思: Indicates that the box spans a group of objects (e.g., a bed of flowers or a crowd of people). We asked annotators to use this tag for cases with more than 5 instances which are heavily occluding each other and are physically touching.
也就是说,带有group_of标记的说明,该框中包含了5个以上的物体,如拥挤的人群,一个铺满鲜花的床等等。
还有一个问题就是,我的GroundTruth中只有蝴蝶和背景两类,但是原本模型的label_map中却包含545类,所以其余的类别是没有GT的,这样在程序中有一个判断:
# object_detection/utils/object_detection_evaluation.py if (self.num_gt_instances_per_class == 0).any(): logging.warn( 'The following classes have no ground truth examples: %s', np.squeeze(np.argwhere(self.num_gt_instances_per_class == 0)) + self.label_id_offset)
我后来找到后,直接将其注释掉,最终跑通了。
该模型在蝴蝶单类的检测Precision=0.728
以下主要解释下评估的代码,防止以后忘记。假设模型输出validation_detections.tfrecord已保存在
models/research/butterfly路径下
第一步是生成配置文件:
# From models/research/butterfly SPLIT=validation # or test
mkdir -p ${SPLIT}_eval_metrics
echo "
label_map_path: ‘…/object_detection/data/oid_bbox_trainable_label_map.pbtxt’
tf_record_input_reader: { input_path: ‘${SPLIT}_detections.tfrecord’ }
" >
S
P
L
I
T
e
v
a
l
m
e
t
r
i
c
s
/
{SPLIT}_eval_metrics/
SPLITevalmetrics/{SPLIT}_input_config.pbtxt
echo "
metrics_set: ‘open_images_detection_metrics’
" >
S
P
L
I
T
e
v
a
l
m
e
t
r
i
c
s
/
{SPLIT}_eval_metrics/
SPLITevalmetrics/{SPLIT}_eval_config.pbtxt
然后运行评估程序:
# From tensorflow/models/research/butterfly SPLIT=validation # or test
PYTHONPATH=
P
Y
T
H
O
N
P
A
T
H
:
PYTHONPATH:
PYTHONPATH:(readlink -f …)
python -m object_detection/metrics/offline_eval_map_corloc
–eval_dir=KaTeX parse error: Expected 'EOF', got '\ ' at position 22: …}_eval_metrics \̲ ̲ #结果保存的路径 …{SPLIT}_eval_metrics/KaTeX parse error: Expected 'EOF', got '\ ' at position 27: …l_config.pbtxt \̲ ̲ </span>--in…{SPLIT}_eval_metrics/${SPLIT}_input_config.pbtxt #输入的路径
首先来看下主程序 models/research/object_detection/metrics/offline_eval_map_corloc.py
import csv
import os
import re
import tensorflow as tf
from object_detection import evaluator
from object_detection.core import standard_fields
from object_detection.metrics import tf_example_parser
from object_detection.utils import config_util
from object_detection.utils import label_map_util
...
def read_data_and_evaluate(input_config, eval_config):
略
def write_metrics(metrics, output_dir):
...
def main(argv): del argv required_flags = ['input_config_path', 'eval_config_path', 'eval_dir'] #对应输入的三个参数 for flag_name in required_flags: if not getattr(FLAGS, flag_name): raise ValueError('Flag --{} is required'.format(flag_name))
configs = config_util.get_configs_from_multiple_files(
eval_input_config_path=FLAGS.input_config_path,
eval_config_path=FLAGS.eval_config_path)
eval_config = configs[‘eval_config’]
input_config = configs[‘eval_input_config’]
metrics = read_data_and_evaluate(input_config, eval_config) #主要实现部分在这里
# Save metrics
write_metrics(metrics, FLAGS.eval_dir)
具体来看下
read_data_and_evaluate(input_config, eval_config):
def read_data_and_evaluate(input_config, eval_config): """Reads pre-computed object detections and groundtruth from tf_record.
Args:
input_config: input config proto of type 输入配置文件
object_detection.protos.InputReader.
eval_config: evaluation config proto of type 评估配置文件
object_detection.protos.EvalConfig.
Returns:
Evaluated detections metrics. 返回:评估结果
Raises:
ValueError: if input_reader type is not supported or metric type is unknown.
“”"
if input_config.WhichOneof(‘input_reader’) == ‘tf_record_input_reader’:
input_paths = input_config.tf_record_input_reader.input_path
label_map </span>=<span style="color: #000000;"> label_map_util.load_labelmap(input_config.label_map_path)#载入label_map
max_num_classes </span>= max([item.id <span style="color: #0000ff;">for</span> item <span style="color: #0000ff;">in</span><span style="color: #000000;"> label_map.item]) #获得最大的类别对应id (545)
categories </span>=<span style="color: #000000;"> label_map_util.convert_label_map_to_categories(
label_map, max_num_classes) #list类型,eg. categories[110]={'id':111,'name':'Butterfly'}
object_detection_evaluators </span>=<span style="color: #000000;"> evaluator.get_evaluators(
eval_config, categories)
</span><span style="color: #008000;">#</span><span style="color: #008000;"> Support a single evaluator</span>
object_detection_evaluator =<span style="color: #000000;"> object_detection_evaluators[0] #对应object_detection_evaluation.OpenImagesDetectionEvaluator
skipped_images </span>=<span style="color: #000000;"> 0
processed_images </span>=<span style="color: #000000;"> 0
</span><span style="color: #0000ff;">for</span> input_path <span style="color: #0000ff;">in</span><span style="color: #000000;"> _generate_filenames(input_paths):
tf.logging.info(</span><span style="color: #800000;">'</span><span style="color: #800000;">Processing file: {0}</span><span style="color: #800000;">'</span><span style="color: #000000;">.format(input_path))
record_iterator </span>= tf.python_io.tf_record_iterator(path=<span style="color: #000000;">input_path) #读取 validation_detection.tfrecord
data_parser </span>=<span style="color: #000000;"> tf_example_parser.TfExampleDetectionAndGTParser()
</span><span style="color: #0000ff;">for</span> string_record <span style="color: #0000ff;">in</span><span style="color: #000000;"> record_iterator: #迭代器,一共238个测试样本,每次读取一个样本检测结果
tf.logging.log_every_n(tf.logging.INFO, </span><span style="color: #800000;">'</span><span style="color: #800000;">Processed %d images...</span><span style="color: #800000;">'</span>, 1000<span style="color: #000000;">,
processed_images)
processed_images </span>+= 1<span style="color: #000000;">
example </span>=<span style="color: #000000;"> tf.train.Example()
example.ParseFromString(string_record) #解析TFrecord--> example.features.feature 中以字典形式存放数据
decoded_dict </span>=<span style="color: #000000;"> data_parser.parse(example) #对TFrecord进一步解析,还原:groundtruth_boxes、groundtruth_classes、detection_boxes、detection_classes、detection_scores<br> <br>
</span><span style="color: #0000ff;">if</span><span style="color: #000000;"> decoded_dict:<br> <strong>#对应 object_detection/utils/object_detection_evaluation.py 中的 class OpenImagesDetectionEvaluator(),默认iou_threshold=0.5</strong>
object_detection_evaluator.add_single_ground_truth_image_info(
decoded_dict[standard_fields.DetectionResultFields.key],
decoded_dict)
object_detection_evaluator.add_single_detected_image_info(
decoded_dict[standard_fields.DetectionResultFields.key],
decoded_dict)
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">:
skipped_images </span>+= 1<span style="color: #000000;">
tf.logging.info(</span><span style="color: #800000;">'</span><span style="color: #800000;">Skipped images: {0}</span><span style="color: #800000;">'</span><span style="color: #000000;">.format(skipped_images))
</span><span style="color: #0000ff;">return</span><em><strong><span style="color: #000000;"> object_detection_evaluator.evaluate()
raise ValueError(‘Unsupported input_reader_config.’)
可以看出,主要的评测过程又放在了
object_detection/utils/object_detection_evaluation.py
第一个是 class OpenImagesDetectionEvaluator(ObjectDetectionEvaluator): 继承自 class ObjectDetectionEvaluator(DetectionEvaluator) ,而这个class 又继承自 class DetectionEvaluator(object)
所以我们从上往下看这几个函数,先是基类 DetectionEvaluator(object):Line:42
class DetectionEvaluator(object): """Interface for object detection evalution classes.
Example usage of the Evaluator:
evaluator = DetectionEvaluator(categories)
即挨个添加 GT和detections ,最后一起evaluate()
Detections and groundtruth for image 1.
evaluator.add_single_groundtruth_image_info(…)
evaluator.add_single_detected_image_info(…)
Detections and groundtruth for image 2.
evaluator.add_single_groundtruth_image_info(…)
evaluator.add_single_detected_image_info(…)
metrics_dict = evaluator.evaluate()
“”"
metaclass = ABCMeta
def init(self, categories):
“”"Constructor.
Args:
categories: A list of dicts, each of which has the following keys -
'id': (required) an integer id uniquely identifying this category.
'name': (required) string representing category name e.g., 'cat', 'dog'.
</span><span style="color: #800000;">"""</span><span style="color: #000000;">
self._categories </span>=<span style="color: #000000;"> categories
@abstractmethod
def add_single_ground_truth_image_info(self, image_id, groundtruth_dict):
“”"Adds groundtruth for a single image to be used for evaluation.
Args:
image_id: A unique string/integer identifier for the image.
groundtruth_dict: A dictionary of groundtruth numpy arrays required
for evaluations.
</span><span style="color: #800000;">"""</span>
<span style="color: #0000ff;">pass</span><span style="color: #000000;">
@abstractmethod
def add_single_detected_image_info(self, image_id, detections_dict):
“”"Adds detections for a single image to be used for evaluation.
Args:
image_id: A unique string/integer identifier for the image.
detections_dict: A dictionary of detection numpy arrays required
for evaluation.
</span><span style="color: #800000;">"""</span>
<span style="color: #0000ff;">pass</span><span style="color: #000000;">
@abstractmethod
def evaluate(self):
“”“Evaluates detections and returns a dictionary of metrics.”""
pass
@abstractmethod
def clear(self):
“”“Clears the state to prepare for a fresh evaluation.”""
pass
然后class ObjectDetectionEvaluator(DetectionEvaluator) Line:104class ObjectDetectionEvaluator(DetectionEvaluator):
"""A class to evaluate detections."""
def init(self,
categories,
matching_iou_threshold=0.5,
evaluate_corlocs=False,
metric_prefix=None,
use_weighted_mean_ap=False,
evaluate_masks=False):
“”“Constructor.
Args:
xxxxxx
Raises:
ValueError: If the category ids are not 1-indexed.
“””
…#这个地方是最关键的,后面会一直用到
self._evaluation = ObjectDetectionEvaluation(
num_groundtruth_classes=self._num_classes,
matching_iou_threshold=self._matching_iou_threshold,
use_weighted_mean_ap=self._use_weighted_mean_ap,
label_id_offset=self._label_id_offset)
…
def add_single_ground_truth_image_info(self, image_id, groundtruth_dict):
“”“Adds groundtruth for a single image to be used for evaluation.
“””
…略
self._evaluation.add_single_ground_truth_image_info(xxx)
def add_single_detected_image_info(self, image_id, detections_dict):
“”"Adds detections for a single image to be used for evaluation.
</span><span style="color: #800000;">"""</span><span style="color: #000000;">
...<br></span></pre>
self._evaluation.add_detected_image_info(xxx)
def evaluate(self): """Compute evaluation result. """ ...
(per_class_ap, mean_ap, _, _, per_class_corloc, mean_corloc) = (self._evaluation.evaluate())
...
def clear(self): """Clears the state to prepare for a fresh evaluation.""" self._evaluation = ObjectDetectionEvaluation( num_groundtruth_classes=self._num_classes, matching_iou_threshold=self._matching_iou_threshold, use_weighted_mean_ap=self._use_weighted_mean_ap, label_id_offset=self._label_id_offset) self._image_ids.clear()
最后是OpenImagesDetectionEvaluator(ObjectDetectionEvaluator) Line:376
class OpenImagesDetectionEvaluator(ObjectDetectionEvaluator): """A class to evaluate detections using Open Images V2 metrics.Open Images V2 introduce group_of type of bounding boxes and this metric handles those boxes appropriately.
“”"
def init(self,
categories,
matching_iou_threshold=0.5,
evaluate_corlocs=False):
“”"Constructor.
Args:
categories: A list of dicts, each of which has the following keys -
'id': (required) an integer id uniquely identifying this category.
'name': (required) string representing category name e.g., 'cat', 'dog'.
matching_iou_threshold: IOU threshold to use for matching groundtruth
boxes to detection boxes.
evaluate_corlocs: if True, additionally evaluates and returns CorLoc.
</span><span style="color: #800000;">"""</span><span style="color: #000000;">
super(OpenImagesDetectionEvaluator, self).</span><span style="color: #800080;">__init__</span><span style="color: #000000;">(
categories,
matching_iou_threshold,
evaluate_corlocs,
metric_prefix</span>=<span style="color: #800000;">'</span><span style="color: #800000;">OpenImagesV2</span><span style="color: #800000;">'</span><span style="color: #000000;">)
def add_single_ground_truth_image_info(self, image_id, groundtruth_dict):
“”"Adds groundtruth for a single image to be used for evaluation.
</span><span style="color: #800000;">"""</span>
<span style="color: #0000ff;">if</span> image_id <span style="color: #0000ff;">in</span><span style="color: #000000;"> self._image_ids:
</span><span style="color: #0000ff;">raise</span> ValueError(<span style="color: #800000;">'</span><span style="color: #800000;">Image with id {} already added.</span><span style="color: #800000;">'</span><span style="color: #000000;">.format(image_id))
groundtruth_classes </span>=<span style="color: #000000;"> (
groundtruth_dict[standard_fields.InputDataFields.groundtruth_classes] </span>-<span style="color: #000000;">
self._label_id_offset)
</span><span style="color: #008000;">#</span><span style="color: #008000;"> If the key is not present in the groundtruth_dict or the array is empty</span>
<span style="color: #008000;">#</span><span style="color: #008000;"> (unless there are no annotations for the groundtruth on this image)</span>
<span style="color: #008000;">#</span><span style="color: #008000;"> use values from the dictionary or insert None otherwise.</span>
<span style="color: #0000ff;">if</span> (standard_fields.InputDataFields.groundtruth_group_of <span style="color: #0000ff;">in</span><span style="color: #000000;">
groundtruth_dict.keys() </span><span style="color: #0000ff;">and</span><span style="color: #000000;">
(groundtruth_dict[standard_fields.InputDataFields.groundtruth_group_of]
.size </span><span style="color: #0000ff;">or</span> <span style="color: #0000ff;">not</span><span style="color: #000000;"> groundtruth_classes.size)):
groundtruth_group_of </span>=<span style="color: #000000;"> groundtruth_dict[
standard_fields.InputDataFields.groundtruth_group_of]
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">:
groundtruth_group_of </span>=<span style="color: #000000;"> None
</span><span style="color: #0000ff;">if</span> <span style="color: #0000ff;">not</span> len(self._image_ids) % 1000<span style="color: #000000;">:
logging.warn(
</span><span style="color: #800000;">'</span><span style="color: #800000;">image %s does not have groundtruth group_of flag specified</span><span style="color: #800000;">'</span><span style="color: #000000;">,
image_id)
self._evaluation.add_single_ground_truth_image_info(
image_id,
groundtruth_dict[standard_fields.InputDataFields.groundtruth_boxes],
groundtruth_classes,
groundtruth_is_difficult_list</span>=<span style="color: #000000;">None,
groundtruth_is_group_of_list</span>=<span style="color: #000000;">groundtruth_group_of)
self._image_ids.update([image_id])<br></span></pre>
可以看出,这里只是修改了add_single_ground_truth_image_info 函数,其他都没变。而在其父类中,有把主要的工作交给了 class ObjectDetectionEvaluation(object) 来处理,这下整个代码在逐渐清晰起来。我最后会画个程序包含关系图,可能更容易理解些。
下面整个才是主要的保存 GT 和 Detection 结果的部分哦!!!
class ObjectDetectionEvaluation(object): """Internal implementation of Pascal object detection metrics."""
def init(self,num_groundtruth_classes,matching_iou_threshold=0.5,nms_iou_threshold=1.0,nms_max_output_boxes=10000,use_weighted_mean_ap=False,label_id_offset=0):
if num_groundtruth_classes < 1:
raise ValueError(‘Need at least 1 groundtruth class for evaluation.’)
<span style="font-size: 15px;"><strong>self.per_image_eval </strong></span></span><span style="font-size: 15px;"><strong>=</strong></span><span style="color: #000000;"><span style="font-size: 15px;"><strong> per_image_evaluation.PerImageEvaluation</strong></span>(
num_groundtruth_classes</span>=<span style="color: #000000;">num_groundtruth_classes,
matching_iou_threshold</span>=<span style="color: #000000;">matching_iou_threshold,
nms_iou_threshold</span>=<span style="color: #000000;">nms_iou_threshold,
nms_max_output_boxes</span>=<span style="color: #000000;">nms_max_output_boxes)
</span><span style="color: #0000ff;">def</span><span style="color: #000000;"> clear_detections(self):
self._initialize_detections()
def add_single_ground_truth_image_info(self,image_key, groundtruth_boxes, groundtruth_class_labels, groundtruth_is_difficult_list=None, groundtruth_is_group_of_list=None, groundtruth_masks=None):
def add_single_detected_image_info(self, image_key, detected_boxes,detected_scores, detected_class_labels,detected_masks=None):
scores, tp_fp_labels, is_class_correctly_detected_in_image = (
self.per_image_eval.compute_object_detection_metrics(
detected_boxes=detected_boxes,
detected_scores=detected_scores,
detected_class_labels=detected_class_labels,
groundtruth_boxes=groundtruth_boxes,
groundtruth_class_labels=groundtruth_class_labels,
groundtruth_is_difficult_list=groundtruth_is_difficult_list,
groundtruth_is_group_of_list=groundtruth_is_group_of_list,
detected_masks=detected_masks,
groundtruth_masks=groundtruth_masks))
</span><span style="color: #0000ff;">for</span> i <span style="color: #0000ff;">in</span><span style="color: #000000;"> range(self.num_class):
</span><span style="color: #0000ff;">if</span> scores[i].shape[0] ><span style="color: #000000;"> 0:
self.scores_per_class[i].append(scores[i])
self.tp_fp_labels_per_class[i].append(tp_fp_labels[i])
(self.num_images_correctly_detected_per_class
) </span>+=<span style="color: #000000;"> is_class_correctly_detected_in_image<br><br></span>
def evaluate(self):
“”"Compute evaluation result.
Returns:
A named tuple with the following fields -
average_precision: float numpy array of average precision for
each class.
mean_ap: mean average precision of all classes, float scalar
precisions: List of precisions, each precision is a float numpy
array
recalls: List of recalls, each recall is a float numpy array
corloc: numpy float array
mean_corloc: Mean CorLoc score for each class, float scalar
</span><span style="color: #800000;">"""</span>
scores =
np.concatenate(self.scores_per_class[class_index]) tp_fp_labels =
np.concatenate(self.tp_fp_labels_per_class[class_index])
precision, recall
=
metrics.compute_precision_recall( scores, tp_fp_labels, self.num_gt_instances_per_class[class_index]) self.precisions_per_class.append(precision) self.recalls_per_class.append(recall) average_precision
=
metrics.compute_average_precision(precision, recall) self.average_precision_per_class[class_index] =
average_precision
self.corloc_per_class </span>=<span style="color: #000000;"> metrics.compute_cor_loc(
self.num_gt_imgs_per_class,
self.num_images_correctly_detected_per_class)<br></span><span style="color: #000000;">
mean_ap </span>=<span style="color: #000000;"> np.nanmean(self.average_precision_per_class)
mean_corloc </span>=<span style="color: #000000;"> np.nanmean(self.corloc_per_class)
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> ObjectDetectionEvalMetrics(
self.average_precision_per_class, mean_ap, self.precisions_per_class,
self.recalls_per_class, self.corloc_per_class, mean_corloc)</span></pre>
我把不重要的部分都剃掉了,主要有两个重要的函数 1. object_detection/utils/per_image_evaluatuion.py 计算单张图的precision和recall
2. object_detection/utils/metrics.py 统计上述结果,并计算mAP等数值
1. object_detection/utils/per_image_evaluatuion.py 计算单张图的precision和recall
scores, tp_fp_labels, is_class_correctly_detected_in_image = compute_object_detection_metrics(...) --> scores, tp_fp_labels = self._compute_tp_fp(...) -->for i in range(self.num_groundtruth_classes): scores, tp_fp_labels = self._compute_tp_fp_for_single_class(...) -->(iou, ioa, scores,num_detected_boxes) = self._get_overlaps_and_scores_box_mode(...) -->detected_boxlist = np_box_list_ops.non_max_suppression(...)
-->
</div>
<p class="postfoot">
posted on <span id="post-date">2018-04-10 16:11</span> <a href="https://www.cnblogs.com/caffeaoto/">caffeauto</a> 阅读(<span id="post_view_count">1518</span>) 评论(<span id="post_comment_count">2</span>) <a href="https://i.cnblogs.com/EditPosts.aspx?postid=8758962" rel="nofollow">编辑</a> <a href="#" "AddToWz(8758962);return false;">收藏</a>
</p>
</div>
<script type="text/javascript">var allowComments=true,cb_blogId=336677,cb_entryId=8758962,cb_blogApp=currentBlogApp,cb_blogUserGuid='5299712e-ab0b-e611-9fc1-ac853d9f53cc',cb_entryCreatedDate='2018/4/10 16:11:00';loadViewCount(cb_entryId);var cb_postType=1;var isMarkdown=false;</script>
</div><a name="!comments"></a><div id="blog-comments-placeholder"><div id="comments_pager_top"></div>
Zoe_启
metrics_set: 'open_images_detection_metrics
就是上面这个内容啊
评论