Dynamically Editing Pipeline Config for Tensorflow Object Detection

Question:

I’m using tensorflow object detection API, and I want to be able to edit config file dynamically in python, which looks like this. I thought of using protocol buffers library in python, but I’m not sure how to go about.

model {
ssd {
num_classes: 1
image_resizer {
  fixed_shape_resizer {
    height: 300
    width: 300
  }
}
feature_extractor {
  type: "ssd_inception_v2"
  depth_multiplier: 1.0
  min_depth: 16
  conv_hyperparams {
    regularizer {
      l2_regularizer {
        weight: 3.99999989895e-05
      }
    }
    initializer {
      truncated_normal_initializer {
        mean: 0.0
        stddev: 0.0299999993294
      }
    }
    activation: RELU_6
    batch_norm {
      decay: 0.999700009823
      center: true
      scale: true
      epsilon: 0.0010000000475
      train: true
    }
  }
 ...
 ...

}

Is there a simple/easy way to change specific values for fields like height in image_resizer -> fixed_shape_resizer from say 300 to 500? And write back the file with modified values without changing anything else?

EDIT:
Though answer provided by @DmytroPrylipko worked for most of the parameters in the config, I face some issues with “composite field”..

That is, if we have configuration like:

train_input_reader: {
  label_map_path: "/tensorflow/data/label_map.pbtxt"
  tf_record_input_reader {
    input_path: "/tensorflow/models/data/train.record"
  }
}

And I add this line to edit input_path:

 pipeline_config.train_input_reader.tf_record_input_reader.input_path = "/tensorflow/models/data/train100.record"

It throws error:

TypeError: Can't set composite field

Answers:

Yes, using Protobuf Python API is quite easy:

edit_pipeline.py:

import argparse

import tensorflow as tf
from google.protobuf import text_format
from object_detection.protos import pipeline_pb2


def parse_arguments():                                                                                                                                                                                                                                                
    parser = argparse.ArgumentParser(description='')                                                                                                                                                                                                                  
    parser.add_argument('pipeline')                                                                                                                                                                                                                                   
    parser.add_argument('output')                                                                                                                                                                                                                                     
    return parser.parse_args()                                                                                                                                                                                                                                        


def main():                                                                                                                                                                                                                                                           
    args = parse_arguments()                                                                                                                                                                                                                                          
    pipeline_config = pipeline_pb2.TrainEvalPipelineConfig()                                                                                                                                                                                                          

    with tf.gfile.GFile(args.pipeline, "r") as f:                                                                                                                                                                                                                     
        proto_str = f.read()                                                                                                                                                                                                                                          
        text_format.Merge(proto_str, pipeline_config)                                                                                                                                                                                                                 

    pipeline_config.model.ssd.image_resizer.fixed_shape_resizer.height = 300                                                                                                                                                                                          
    pipeline_config.model.ssd.image_resizer.fixed_shape_resizer.width = 300                                                                                                                                                                                           

    config_text = text_format.MessageToString(pipeline_config)                                                                                                                                                                                                        
    with tf.gfile.Open(args.output, "wb") as f:                                                                                                                                                                                                                       
        f.write(config_text)                                                                                                                                                                                                                                          


if __name__ == '__main__':                                                                                                                                                                                                                                            
    main() 

The way I call the script:

TOOL_DIR=tool/tf-models/research

(
   cd $TOOL_DIR
   protoc object_detection/protos/*.proto --python_out=.
)

export PYTHONPATH=$PYTHONPATH:$TOOL_DIR:$TOOL_DIR/slim

python3 edit_pipeline.py pipeline.config pipeline_new.config

Composite fields

In case of repeated fields, you must treat them as arrays (e.g. use extend(), append() methods):

pipeline_config.train_input_reader.tf_record_input_reader.input_path[0] = '/tensorflow/models/data/train100.record'

Eval Input reader error

This is a common error trying to edit the composite field. ( “no attribute tf_record_input_reader found” in case of eval_input_reader )

It’s mentioned below in @latida’s answer.
Fix that by setting it as an array field.

pipeline_config.eval_input_reader[0].label_map_path  = label_map_full_path
pipeline_config.eval_input_reader[0].tf_record_input_reader.input_path[0] = val_record_path
Answered By: Dmytro Prylipko
pipeline_config.eval_input_reader[0].label_map_path  = label_map_full_path
pipeline_config.eval_input_reader[0].tf_record_input_reader.input_path[0] = val_record_path
Answered By: latida

This is the same above code with small changes that suit the tensorflow V2.

import argparse

import tensorflow as tf
from google.protobuf import text_format
from object_detection.protos import pipeline_pb2

def parse_arguments():                                                                                                                                                                                                                                                
    parser = argparse.ArgumentParser(description='')                                                                                                                                                                                                                  
    parser.add_argument('pipeline')                                                                                                                                                                                                                                   
    parser.add_argument('output')                                                                                                                                                                                                                                     
    return parser.parse_args()                                                                                                                                                                                                                                        


def main():                                                                                                                                                                                                                                                           
    args = parse_arguments()                                                                                                                                                                                                                                          
    pipeline_config = pipeline_pb2.TrainEvalPipelineConfig()                                                                                                                                                                                                          

    with tf.io.gfile.GFile(args.pipeline, "r") as f:                                                                                                                                                                                                                     
        proto_str = f.read()                                                                                                                                                                                                                                          
        text_format.Merge(proto_str, pipeline_config)                                                                                                                                                                                                                 

    pipeline_config.model.ssd.image_resizer.fixed_shape_resizer.height = 300                                                                                                                                                                                          
    pipeline_config.model.ssd.image_resizer.fixed_shape_resizer.width = 300                                                                                                                                                                                           

    config_text = text_format.MessageToString(pipeline_config) 
                                                                                                                                                                                                   
    with tf.io.gfile.GFile(args.output, "wb") as f:                                                                                                                                                                                                                
        f.write(config_text)                                                                                                                                                                                                                                          

if __name__ == '__main__':                                                                                                                                                                                                                                            
    main() 
Answered By: SenthurLP

I’ve found this to be a useful approach for overriding the object detection pipeline.config:

from object_detection.utils import config_util
from object_detection import model_lib_v2

PIPELINE_CONFIG_PATH = 'path_to_your_pipeline.config'

# Load the pipeline config as a dictionary
pipeline_config_dict = config_util.get_configs_from_pipeline_file(PIPELINE_CONFIG_PATH)

# OVERRIDE EXAMPLES
# Example 1: Override the train tfrecord path
pipeline_config_dict['train_input_config'].tf_record_input_reader.input_path[0] = 'your/override/path/to/train.record'
# Example 2: Override the eval tfrecord path
pipeline_config_dict['eval_input_config'].tf_record_input_reader.input_path[0] = 'your/override/path/to/test.record'

# Convert the pipeline dict back to a protobuf object
pipeline_config = config_util.create_pipeline_proto_from_configs(pipeline_config_dict)

# EXAMPLE USAGE:
# Example 1: Run the object detection train loop with your overrides (has to be string representation)
model_lib_v2.train_loop(config_override=str(pipeline_config))
# Example 2: Save the pipeline config to disk
config_util.save_pipeline_config(config, 'path/to/save/new/pipeline.config)
Answered By: Nic Scozzaro

I’m very new to programming but I wanted to share something that I was trying to do because the original question also gives me trouble.

Let’s say I already have a config file on my computer and I’ve compiled all of my TF2 Object Detection protos but now I want to change the preprocessing. I use python.

First, I’ll get my config file as a python dictionary

from object_detection.utils import config_util

# PATH_TO_MY_CONFIG_FILE can be whatever the path is to your config file 
configs = config_util.get_configs_from_pipeline_file(PATH_TO_MY_CONFIG_FILE)

Next, I’d like to check what data augmentations / preprocessing I currently have.

print(configs['train_config'].data_augmentation_options)
print(type(configs['train_config'].data_augmentation_options[0]))

[random_horizontal_flip {
}
, random_scale_crop_and_pad_to_square {
output_size: 512
scale_min: 0.10000000149011612
scale_max: 2.0
}
]

<class ‘object_detection.protos.preprocessor_pb2.PreprocessingStep’>

It looks to me like configs[‘train_config’].data_augmentation_options is a python list containing instances of the class PreprocessingStep that is from the file preprocessor_pb2.py

When I previously tried running code from the TF2 Object Detection github repo there was some code that compiled protos for me. The protocompiler took my preprocessor.proto file and created a new file from it called preprocessor_pb2.py that is now on my computer. In this python file there is a class called PreprocessingStep.

I will use this script to make a new preprocessing step for my configuration.

from object_detection.protos import preprocessor_pb2

# Construct a new PreprocessingStep object
my_new_data_augmentation = preprocessor_pb2.PreprocessingStep()
# I would like to randomly change some color images to gray with %20 probability
my_new_data_augmentation.random_rgb_to_gray.probability = 0.2
print(my_new_data_augmentation)

random_rgb_to_gray {
probability: 0.20000000298023224
}

A PreprocessingStep step object has a lot of different fields. You can look inside the file preprocessor_pb2.py for a list of fields you can modify or you can look at the preprocessor.proto on the TF2 OD Github at the list of messages (These are the text in red). The messages are the names of the fields you can change.

Once you’ve selected a field to modify you can further set the optional parameters. In my example ‘probability’ is the only parameter you can modify for the ‘random_rgb_to_gray’ preprocessing step (I think).

Also, I don’t think you can set your PreprocessingStep object to have multiple preprocessing fields. For example, if I did

my_new_data_augmentation.random_rgb_to_gray.probability = 0.2
my_new_data_augmentation.random_adjust_hue.max_delta = 0.1
print(my_new_data_augmentation)

random_adjust_hue {
max_delta: 0.10000000149011612
}

things will just get overwritten.

I guess, now my preprocessing step is random_adjust_hue. I will go ahead and add this to my data augmentations now using list.append()

configs['train_config'].data_augmentation_options.append(my_new_data_augmentation)
print(configs['train_config'].data_augmentation_options)

[random_horizontal_flip {
}
, random_scale_crop_and_pad_to_square {
output_size: 512
scale_min: 0.10000000149011612
scale_max: 2.0
}
, random_adjust_hue {
max_delta: 0.10000000149011612
}
]

Now I can save my configs using the usual config_util.create_pipeline_proto_from_configs() method.

Like I said I’m very new to programming and I don’t know if this idea causes some bugs that I’m not aware of but I wanted to share this in case somebody else finds this helpful as a starting place to build on I guess.

Answered By: Whatevs

Thanks to others post in this thread, this is what I’m doing

My model variables:

##change chosen model to deploy different models available in the TF2 object detection zoo
MODELS_MATRIX = {
    'SSD-MobileNet-v2-320x320': {
        'name': 'ssd_mobilenet_v2_320x320_coco17_tpu-8',
        'file': 'ssd_mobilenet_v2_320x320_coco17_tpu-8.tar.gz',
        'checkpoint': 'checkpoint/ckpt-0',
        'url': 'http://download.tensorflow.org/models/object_detection/tf2/20200711/ssd_mobilenet_v2_320x320_coco17_tpu-8.tar.gz'
    },
    'SSD-MobileNet-V2-FPNLite-640x640': {
        'name': 'ssd_mobilenet_v2_fpnlite_640x640_coco17_tpu-8',
        'file': 'ssd_mobilenet_v2_fpnlite_640x640_coco17_tpu-8.tar.gz',
        'checkpoint': 'checkpoint/ckpt-0',
        'url': 'http://download.tensorflow.org/models/object_detection/tf2/20200711/ssd_mobilenet_v2_fpnlite_640x640_coco17_tpu-8.tar.gz'
    },
    'SSD-ResNet101-V1-FPN-640x640': {
        'name': 'ssd_resnet101_v1_fpn_640x640_coco17_tpu-8',
        'file': 'ssd_resnet101_v1_fpn_640x640_coco17_tpu-8.tar.gz',
        'checkpoint': 'checkpoint/ckpt-0',
        'url': 'http://download.tensorflow.org/models/object_detection/tf2/20200711/ssd_resnet101_v1_fpn_640x640_coco17_tpu-8.tar.gz'
    }
}    

# local paths
local_pre_trainied_models_path = '/content/workspace/training/pre-trained-models'
local_models_path = '/content/workspace/training/models'

# model variables
chosen_model = 'SSD-ResNet101-V1-FPN-640x640'
model_url    = MODELS_MATRIX[chosen_model]['url']
model_file   = MODELS_MATRIX[chosen_model]['file']
model_name   = MODELS_MATRIX[chosen_model]['name']

model_fine_tune_checkpoint = local_pre_trainied_models_path +'/'+ MODELS_MATRIX[chosen_model]['name'] + '/' + MODELS_MATRIX[chosen_model]['checkpoint']
model_fine_tune_checkpoint_type = "detection"

# Set this to false if you are not training on a TPU
model_use_bfloat16 = False

# For faster training time, images should be resized to 300x300 and then annotated
# Images should contain the objects of interest at various scales, angles, lighting conditions, locations
# For acceptable results - [email protected] of 0.9 the model was trained with batch size of 24
# and 5000 steps. this takes about 1h using 2 augmentations. 
# using 5 augmentations it takes about 2h 
# A step means using a single batch of data. larger batch, less steps required
model_num_steps = 13000  

#Number of evaluation steps.
model_num_eval_steps = 50
model_batch_size     = 16
model_iou_threshold  = 0.50
model_num_classes    = 1

# tf records and labels
model_train_record_file = '/content/workspace/training/annotations/train/license-plates.tfrecord'
model_train_label_map_pbtxt_file = '/content/workspace/training/annotations/train/license-plates_label_map.pbtxt'

model_test_record_file = '/content/workspace/training/annotations/test/license-plates.tfrecord'
model_test_label_map_pbtxt_file = '/content/workspace/training/annotations/test/license-plates_label_map.pbtxt'


Configuring my pipeline`:

import tensorflow as tf
from google.protobuf import text_format
from object_detection.protos import pipeline_pb2

custom_pipeline_file = f"{local_models_path}/anrp_{model_name}/pipeline.config"

pipeline_conf = pipeline_pb2.TrainEvalPipelineConfig()

# merge conf
with tf.io.gfile.GFile(custom_pipeline_file, "r") as f:                                                                                                                                                                                                                     
    proto_str = f.read()
    text_format.Merge(proto_str, pipeline_conf)

# model
pipeline_conf.model.ssd.num_classes = model_num_classes
pipeline_conf.model.ssd.post_processing.batch_non_max_suppression.iou_threshold = model_iou_threshold

# train_config
pipeline_conf.train_config.batch_size = model_batch_size
pipeline_conf.train_config.num_steps = model_num_steps
pipeline_conf.train_config.fine_tune_checkpoint = model_fine_tune_checkpoint
pipeline_conf.train_config.fine_tune_checkpoint_type = model_fine_tune_checkpoint_type
pipeline_conf.train_config.use_bfloat16 = model_use_bfloat16

# train_input_reader
pipeline_conf.train_input_reader.label_map_path = model_train_label_map_pbtxt_file
pipeline_conf.train_input_reader.tf_record_input_reader.input_path[0] = model_train_record_file

# eval_config
pipeline_conf.eval_config.metrics_set[0] = "coco_detection_metrics"
pipeline_conf.eval_config.use_moving_averages = False


# eval_input_reader
pipeline_conf.eval_input_reader[0].label_map_path = model_test_label_map_pbtxt_file
pipeline_conf.eval_input_reader[0].tf_record_input_reader.input_path[0] = model_test_record_file

config_text = text_format.MessageToString(pipeline_conf) 

# debug
print(pipeline_conf)
print(config_text)

# override my conf
# with tf.io.gfile.GFile(custom_pipeline_file, "wb") as f:                                                                                                                                                                                                                
#     f.write(config_text)