RegionProposalGenerator (version 2.0.8, 2022-April-16)

RegionProposalGenerator.py
 
Version: 2.0.8
   
Author: Avinash Kak (kak@purdue.edu)
 
Date: 2022-April-16
 
 

Download Version 2.0.8:  gztar  

 
     Total number of downloads (all versions): 591
     This count is automatically updated at every rotation of
     the weblogs (normally once every two to four days)
     Last updated: Fri Feb 23 06:01:01 EST 2024

View the main module code file in your browser  
 
Download the image datasets for RPG  
 
 
SWITCH TO VERSION 2.1.0  
 
 
 
CHANGES:

  Version 2.0.8:
 
    This version constitutes a complete implementation of a YOLO
    multi-instance object detector.  In addition to the new multi-loss
    function that I introduced in the previous public release of this module,
    the new version includes a full-blown implementation of what you need for
    validation testing.  I should also mention that I have split what used to
    be the Examples directory in the distribution into two directories:
    ExamplesObjectDetection and ExamplesRegionProposals.  Your entry point for
    learning the YOLO implementation would be the script
    multi_instance_object_detection.py in the directory
    ExamplesObjectDetection.
    
  Version 2.0.6:
 
    This version incorporates a more sophisticated loss function for
    YOLO-based multi-instance object detection in images.  In the new loss
    function, I use different criteria for the different segments of the YOLO
    vector.  [Assigning an object instance in a training image to an anchor
    box for a cell in the image involves creating a "5+C"-element YOLO vector,
    where C is the number of object classes.] I now use the Binary
    Cross-Entropy Loss (nn.BCELoss) for the first element of the YOLO vector
    that stands for the presence or the absence of an object instance in a
    specific anchor box in a specific cell.  I use mean-squared-error loss
    (nn.MSELoss) for the next four numerical elements that express the precise
    location of the object bounding-box vis-a-vis the center of the cell to
    which the object is assigned and also for the dimensions of the bounding
    box.  Finally, I use the regular Cross-Entropy loss (nn.CrossEntropyLoss)
    for the last C elements of the YOLO vector.  Using the cross-entropy loss
    for the labeling errors required augmenting the YOLO vector with one
    additional element to express the absence of an object.
 
  Version 2.0.2:
 
    This version fixes a couple of bugs in the YOLO-based logic for
    multi-instance object detection.
 
  Version 2.0.1:
 
    This module has gone through several changes since its last public-release
    version as I was experimenting with different ways of imparting to the
    students the sudden increase in model complexity as one goes from
    single-instance object detection to multi-instance object detection.
    These experiments led to the creation of two new datasets,
    PurdueDrEvalDataset and PurdueDrEvalMultiDataset, the former for playing
    with single-instance object detection and the latter for doing the same
    with multi-instance object detection.  The module also includes two inner
    classes, SingleInstanceDetector and YoloLikeDetector, the former a
    reference implementation for single instance object detection and the
    latter a YOLO-like reference implementation for multi-instance object
    detection. [By the way, "DrEval" in the names of the two datasets
    mentioned here has a connection with "Dr Evil" in the Austin Powers
    movies.]
 
  Version 1.0.5:
 
    In keeping with the tutorial nature of this module, this version includes
    methods that come in handy for batch-based processing of images. These
    methods carry names like "displaying_and_histogramming_
    images_in_batchX()" where X is 1, 2, and 3.  The rest of the module,
    especially the part that deals with constructing region proposals remains
    unchanged.
 
  Version 1.0.4:
 
    This is the first public release version of the module.
 
 
INTRODUCTION:
 
    Single-Instance vs. Multi-Instance Detection:
 
    This module was created for experimenting with the logic of object
    detection with neural networks.  On the face of it, object detection in
    images sounds like a well-defined problem that should lend itself to
    well-defined solutions.  Unfortunately, the reality is otherwise.  Yes,
    simple examples of the problem -- such as when the images contain
    single object instances and with no competing clutter in the background
    -- the problem can be solved straightforwardly with a neural network.
    However, the object detection problems that are encountered in real
    life are rarely that simple.  A practically useful framework for object
    detection must be able to recognize and localize all possible instances
    of the objects of interest in a given image.
 
    So how does one solve the problem of multi-instance object detection and
    localization with a neural network?
 
    The last half-dozen years have seen the emergence of the following three
    competition-grade neural-network based approaches for multi-instance
    object detection: R-CNN, YOLO, and SSD.  The Preamble section of my Week 8
    lecture for Purdue's Deep Learning class provides a brief overview of
    these approaches.  YOLO stands for "You Only Look Once" --- in contrast
    with R-CNN based approaches in which you may have to subject the images to
    a couple of neural networks, one for generating region proposals and the
    other for actual object detection.
 
    The main goal of the present module is to provide an educational example
    of a complete implementation of the YOLO logic for multi-instance object
    detection in images.
 
    Graph-Based Algorithms for Region Proposals:
 
    A second goal of this module is to provide implementations for a couple
    of the more modern graph-based approaches for generating region
    proposals.  At this point, the reader might ask: What is a region
    proposal?  A region proposal is a blob of pixels that is highly likely
    to contain an object instance.  Another way of saying same thing is
    that region proposals are pixel blobs that look different from the
    general background in the images.  While it is possible to use a neural
    network for generating region proposals, as demonstrated by the success
    of RPN (Region Proposal Network) in the R-CNN based multi-instance
    object detection, the RPG module is concerned primarily with the
    non-neural-network based methods -- the graph-based methods -- for
    generating region proposals.  I believe that becoming familiar with the
    non-learning based methods for constructing region proposals still has
    considerable value.  Consider, for example, the problem of detecting
    objects in satellite images where you simply do not have access to the
    amount of training data you would need for a neural-network based
    approach to work.
 
    With regard to the graph-based method for generating region proposals,
    RPG (RegionProposalGenerator) implements elements of the Selective
    Search (SS) algorithm for object detection as proposed by Uijlings, van
    de Sande, Gevers, and Smeulders.  The Selective Search algorithm sits
    on top of the graph-based image segmentation algorithm of Felzenszwalb
    and Huttenlocher (FH) whose implementation is also included in the RPG
    module.  The RPG module first processes an image with the FH
    graph-based algorithm for image segmentation to divide an image into
    pixel blobs.  The module subsequently invokes elements of the SS
    algorithm to selectively merge the blobs on the basis of three
    properties: homogeneity of the color, grayscale variance, and texture
    homogeneity.
 
    The FH algorithm is based on creating a graph-based representation of
    an image in which, at the beginning, each pixel is a single vertex and
    the edge between two vertices that stand for two adjacent pixels
    represents the difference between some pixel property (such as the
    color difference) at the two pixels.  Subsequently, for the vertex
    merging logic, each vertex u, that after the first iteration stands for
    a grouping of pixels, is characterized by a property called Int(u),
    which is the largest value of the inter-pixel color difference between
    the adjacent pixels.  In order to account for the fact that, at the
    beginning, each vertex consists of only one pixel [which would not
    allow for the calculation of Int(u)], the unary property of the pixels
    at a vertex is extended from Int(u) to MInt(u) with the addition of a
    vertex-size dependent number equal to k/|C| where "k" is a
    user-specified parameter and |C| the cardinality of the set of pixels
    represented by the vertex u in the graph.
 
    As mentioned above, initially the edges in the graph representation of
    an image are set to the color difference between the two 8-adjacent
    pixels that correspond to two different vertices.  Subsequently, as the
    vertices are merged, an edge, E(u,v), between two vertices u and v is
    set to the smallest value of the inter-pixel color difference for two
    adjacent pixels that belong to the two vertices. At each iteration of
    the algorithm, two vertices u and v are merged provided E(u,v) is less
    than the smaller of the MInt(u) or MInt(v) attributes at the two
    vertices.  My experience is that for most images the algorithm
    terminates of its own accord after a small number of iterations while
    the vertex merging condition can be satisfied.
 
    Since the algorithm is driven by the color differences between
    8-adjacent pixels, the FH algorithm is likely to create too fine a
    segmentation of an image.  The segments produced by FH can be made
    larger by using the logic of SS that allows blobs of pixels to merge
    into larger blobs provided doing so makes sense based on the inter-blob
    values for mean color levels, color variances, texture values, etc.
 
 
INSTALLATION:
 
    The RegionProposalGenerator class was packaged using setuptools.  For
    installation, execute the following command in the source directory
    (this is the directory that contains the setup.py file after you have
    downloaded and uncompressed the package):
 
            sudo python3 setup.py install
 
    On Linux distributions, this will install the module file at a location
    that looks like
 
             /usr/local/lib/python3.8/dist-packages/
 
    If you do not have root access, you have the option of working directly
    off the directory in which you downloaded the software by simply
    placing the following statements at the top of your scripts that use
    the RegionProposalGenerator class:
 
            import sys
            sys.path.append( "pathname_to_RegionProposalGenerator_directory" )
 
    To uninstall the module, simply delete the source directory, locate
    where the RegionProposalGenerator module was installed with "locate
    RegionProposalGenerator" and delete those files.  As mentioned above,
    the full pathname to the installed version is likely to look like
    /usr/local/lib/python2.7/dist-packages/RegionProposalGenerator*
 
    If you want to carry out a non-standard install of the
    RegionProposalGenerator module, look up the on-line information on
    Disutils by pointing your browser to
 
              http://docs.python.org/dist/dist.html
 
USAGE:
 
    Single-Instance and Multi-Instance Detection:
 
    If you wish to experiment with YOLO-like logic for multi-instance
    object detection, you would need to construct an instance of the
    RegionProposalGenerator class and invoke the methods shown below on
    this instance:
 
    rpg = RegionProposalGenerator(
                      dataroot = "./data/",
                      image_size = [128,128],
                      yolo_interval = 20,
                      path_saved_yolo_model = "./saved_yolo_model",
                      momentum = 0.9,
                      learning_rate = 1e-6,
                      epochs = 40,
                      batch_size = 4,
                      classes = ('Dr_Eval','house','watertower'),
                      use_gpu = True,
                  )
    yolo = RegionProposalGenerator.YoloLikeDetector( rpg = rpg )
    yolo.set_dataloaders(train=True)
    yolo.set_dataloaders(test=True)
    model = yolo.NetForYolo(skip_connections=True, depth=8) 
    model = yolo.run_code_for_training_multi_instance_detection(model, display_images=False)
    yolo.run_code_for_training_multi_instance_detection(model, display_images = True)
    
 
    Graph-Based Algorithms for Region Proposals:
 
    To generate region proposals, you would need to construct an instance
    of the RegionProposalGenerator class and invoke the methods shown below
    on this instance:
 
        rpg = RegionProposalGenerator(
                       ###  The first 6 options affect only the Graph-Based part of the algo
                       sigma = 1.0,
                       max_iterations = 40,
                       kay = 0.05,
                       image_normalization_required = True,
                       image_size_reduction_factor = 4,
                       min_size_for_graph_based_blobs = 4,
                       ###  The next 4 options affect only the Selective Search part of the algo
                       color_homogeneity_thresh = [20,20,20],
                       gray_var_thresh = 16000,           
                       texture_homogeneity_thresh = 120,
                       max_num_blobs_expected = 8,
              )
        image_name = "images/mondrian.jpg"
        segmented_graph,color_map = rpg.graph_based_segmentation(image_name)
        rpg.visualize_segmentation_in_pseudocolor(segmented_graph[0], color_map, "graph_based" )
        merged_blobs, color_map = rpg.selective_search_for_region_proposals( segmented_graph, image_name )
        rpg.visualize_segmentation_with_mean_gray(merged_blobs, "ss_based_segmentation_in_bw" )
 
 
CONSTRUCTOR PARAMETERS: 
 
    Of the 10 constructor parameters listed below, the first six are meant for
    the FH algorithm and the last four for the SS algorithm.
 
    sigma: Controls the size of the Gaussian kernel used for smoothing the
                    image before its gradient is calculated.  Assuming the
                    pixel sampling interval to be unity, a sigma of 1 gives
                    you a 7x7 smoothing operator with Gaussian weighting.
                    The default for this parameter is 1.
 
    max_iterations: Sets an upper limit on the number of iterations of the
                    graph-based FH algorithm for image segmentation.
 
    kay: This is the same as the "k" parameter in the FH algorithm.  As
                    mentioned in the Introduction above, the Int(u)
                    property of the pixels represented by each vertex in
                    the graph representation of the image is extended to
                    MInt(u) by the addition of a number k/|C| where |C| is
                    the cardinality of the set of pixels at that vertex.
 
    image_normalization_required: This applies Torchvision's image
                    normalization to the pixel values in the image.
 
    image_size_reduction_factor: As mentioned at the beginning of this
                    document, RegionProposalGenerator is really not meant
                    for production work.  The code is pure Python and, even
                    with that, not at all optimized.  The focus of the
                    module is primarily on easy understandability of what
                    the code is doing so that you can experiment with the
                    algorithm itself.  For the module to produce results
                    within a reasonable length of time, you can use this
                    constructor parameter to downsize the array of pixels
                    that the module must work with.  Set this parameter to
                    a value so that the initial graph constructed from the
                    image has no more than around 3500 vertices if you
                    don't want to wait too long for the results.
 
    min_size_for_graph_based_blobs: This declares a threshold on the
                   smallest size you'd like to see (in terms of the number
                   of pixels) in a segmented blob in the output of the
                   graph-based segmenter.  (I typically use values from 1
                   to 4 for this parameter.)
 
    color_homogeneity_thresh:  
 
                    This and the next three constructor options are meant
                    specifically for the SS algorithm that sits on top of
                    the FH algorithm for further merging of the pixel blobs
                    produced by FH.  This constructor option specifies the
                    maximum allowable difference between the mean color
                    values in two pixel blobs for them to be merged.
 
    gray_var_thresh:
 
                   This option declares the maximum allowable difference in 
                   the variances in the grayscale in two blobs if they are
                   to be merged. 
 
    texture_homogeneity_thresh:
 
                   The RegionProposalGenerator module characterizes the
                   texture of the pixels in each segmented blob by its LBP
                   (Local Binary Patterns) texture.  We want the LBP
                   texture values for two different blobs to be within the
                   value specified by this constructor option if those
                   blobs are to be merged.
 
    max_num_blobs_expected:
 
                   If you only want to extract a certain number of the
                   largest possible blobs, you can do that by giving a
                   value to this constructor option.
 
 
Inner Classes:
 
    (1)  PurdueDrEvalDataset
 
         This is the dataset to use if you are experimenting with
         single-instance object detection.  The dataset contains three
         kinds of objects in its images: Dr. Eval, and two "objects" in his
         neighborhood: a house and a watertower.  Each 128x128 image in the
         dataset contains one of these objects after it is randomly scaled
         and colored. Each image also contains substantial structured noise
         in addition to 20% Gaussian noise.  Examples of these images are
         shown in the Week 8 lecture material in Purdue's Deep Learning
         class.
 
    (2)  PurdueDrEvalMultiDataset
 
         This is the dataset to use if you are experimenting with
         multi-instance object detection.  Each image in the dataset
         contains randomly chosen multiple instances of the same three
         kinds of objects as mentioned above: Dr. Eval, house, and
         watertower.  The number of object instances in each image is
         limited to a maximum of five.  The images contain substantial
         amount of structured noise in addition to 20% random noise.
         
         The reason for why the above two datasets have "DrEval" in their
         names: After having watched every contemporary movie at Netflix
         that was worth watching, my wife and I decided to revisit some of
         old movies that we had enjoyed a long time back.  That led us to
         watching again a couple of Austin Powers movies.  If you are too
         young to know what I am talking about, these movies are spoofs on
         the James Bond movies in which the great comedian Mike Myers plays
         both Austin Powers and his nemesis Dr. Evil.  Around the same
         time, I was writing code for the two datasets mentioned above.
         One of the three objects types in these images is a human-like
         cartoon figure that I needed a name for.  So, after Dr. Evil in
         the movies, I decided to call this cartoon figure Dr Eval and to
         refer to the datasets as Dr Eval datasets. As you all know, "Eval"
         is an important word for people like us.  All programming
         languages provide a function with a name like "eval()".
 
    (3)  SingleInstanceDetector
 
         This provides a reference implementation for constructing a
         single-instance object detector to be used for the
         PurdueDrEvalDataset dataset. For the detection and regression
         network, it uses the LOADnet2 network from DLStudio with small
         modifications to account for the larger 128x128 images in the
         dataset.
 
    (4)  YoloLikeDetector   [For multi-instance detection]
 
         The code in this inner class provides an implementation for the key
         elements of the YOLO logic for multi-instance object detection.  Each
         training image is divided into a grid of cells and it is the
         responsibility of the cell that contains the center of an object
         bounding-box to provide at the output of the neural network an
         estimate for the exact location of the center of the object bounding
         box vis-a-vis the center of the cell.  That cell must also lead to an
         estimate for the height and the width of the bounding-box for the
         object instance.
 
 
PUBLIC METHODS:
 
    Many of these method are related to using this module for experimenting
    with the traditional graph-based algorithms for constructing region
    proposals in images:
 
    (1)  selective_search_for_region_proposals()
 
         This method implements elements of the Selective Search (SS)
         algorithm proposed by Uijlings, van de Sande, Gevers, and Smeulders
         for creating region proposals for object detection.  As mentioned
         elsewhere here, this algorithm sits on top of the graph based image
         segmentation algorithm that was proposed by Felzenszwalb and
         Huttenlocher.
 
    (2)  graph_based_segmentation()
 
         This is an implementation of the Felzenszwalb and Huttenlocher (FH)
         algorithm for graph-based segmentation of images.  At the moment, it
         is limited to working on grayscale images.
 
    (3)  display_tensor_as_image()
 
         This method converts the argument tensor into a photo image that you
         can display in your terminal screen. It can convert tensors of three
         different shapes into images: (3,H,W), (1,H,W), and (H,W), where H,
         for height, stands for the number of pixel in the vertical direction
         and W, for width, the same along the horizontal direction. When the
         first element of the shape is 3, that means that the tensor
         represents a color image in which each pixel in the (H,W) plane has
         three values for the three color channels.  On the other hand, when
         the first element is 1, that stands for a tensor that will be shown
         as a grayscale image.  And when the shape is just (H,W), that is
         automatically taken to be for a grayscale image.
 
    (4)  graying_resizing_binarizing()
 
         This is a demonstration of some of the more basic and commonly used
         image transformations from the torchvision.transformations module.
         The large comment blocks are meant to serve as tutorial introduction
         to the syntax used for invoking these transformations.  The
         transformations shown can be used for converting a color image into a
         grayscale image, for resizing an image, for converting a PIL.Image
         into a tensor and a tensor back into an PIL.Image object, and so on.
 
    (5)  accessing_one_color_plane()
 
         This method shows how can access the n-th color plane of the argument
         color image.
 
    (6)  working_with_hsv_color_space()
 
         Illustrates converting an RGB color image into its HSV
         representation.
 
    (7)  histogramming_the_image()
 
         PyTorch based experiments with histogramming the grayscale and the
         color values in an image
 
    (8)  histogramming_and_thresholding():
 
         This method illustrates using the PyTorch functionality for
         histogramming and thresholding individual images.
 
    (9)  convolutions_with_pytorch()
 
         This method calls on torch.nn.functional.conv2d() for demonstrating a
         single image convolution with a specified kernel.
 
    (10) gaussian_smooth()
 
         This method smooths an image with a Gaussian of specified sigma.  You
         can do the same much faster by using the functionality programmed
         into torch.nn.functional.
 
    (11) visualize_segmentation_in_pseudocolor()
 
         After an image has been segmented, this method can be used to assign
         a random color to each blob in the segmented output for a better
         visualization of the segmentation.
 
    (12) visualize_segmentation_with_mean_gray()
 
         If the visualization produced by the previous method appears too
         chaotic, you can use this method to assign the mean color to each
         each blob in the output of an image segmentation algorithm.  The mean
         color is derived from the pixel values in the blob.
 
    (13) extract_image_region_interactively_by_dragging_mouse()
 
         You can use this method to apply the graph-based segmentation and the
         selective search algorithms to just a portion of your image.  This
         method extract the portion you want.  You click at the upper left
         corner of the rectangular portion of the image you are interested in
         and you then drag the mouse pointer to the lower right corner.  Make
         sure that you click on "save" and "exit" after you have delineated
         the area.
 
    (14) extract_image_region_interactively_through_mouse_clicks()
 
         This method allows a user to use a sequence of mouse clicks in order
         to specify a region of the input image that should be subject to
         further processing.  The mouse clicks taken together define a
         polygon. The method encloses the polygonal region by a minimum
         bounding rectangle, which then becomes the new input image for the
         rest of processing.
 
    (15) displaying_and_histogramming_images_in_batch1(image_dir, batch_size)
 
         This method is the first of three such methods in this module for
         illustrating the functionality of matplotlib for simultaneously
         displaying multiple images and the results obtained from them in a
         gridded arrangement.  The core idea in this method is to call
         "plt.subplots(2,batch_size)" to create 'batch_size' number of subplot
         objects, called "axes", in the form of a '2xbatch_size' array. We use
         the first row of this grid to display each image in its own subplot
         object.  And we use the second row of the grid to display the
         histograms of the corresponding images in the first row.
 
    (16) displaying_and_histogramming_images_in_batch2(image_dir, batch_size)
 
         I now show a second approach to displaying multiple images and their
         corresponding histograms in a gridded display.  In this method we
         call on "torchvision.utils.make_grid()" to construct a grid for us.
         The grid is created by giving an argument like "nrow=4" to it.  The
         grid object returned by the call to make_grid() is a tensor unto
         itself. Such a tensor object is converted into a numpy array so that
         it can be displayed by matplotlib's "imshow()" function.
 
    (17) displaying_and_histogramming_images_in_batch3(image_dir, batch_size)
 
         This method illustrates two things: (1) The syntax used for the
         'singular' version of the subplot function "plt.subplot()" ---
         although I'll be doing so by actually calling "fig.add_subplot()".
         And (2) How you can put together multiple multi-image plots by
         creating multiple Figure objects.  'Figure' is the top-level
         container of plots in matplotlib.  This method creates two separate
         Figure objects, one as a container for all the images in a batch and
         the other as a container for all the histograms for the images.  The
         two Figure containers are displayed in two separate windows on your
         computer screen.
 
THE DATASETS INCLUDED:
 
    Download the archive
 
        datasets_for_RPG.tar.gz
 
    through the link "Download the image datasets for RPG" at the main
    webpage for this module and store the archive in the Examples directory
    of your install of the module. Subsequently, execute the following
    command in the Examples directory:
 
        tar zxvf datasets_for_RPG.tar.gz
 
    This command will create a 'data' subdirectory in the Examples directory
    and deposit the following datasets in it:
 
        Purdue_Dr_Eval_Dataset-clutter-10-noise-20-size-10000-train.gz
        Purdue_Dr_Eval_Dataset-clutter-10-noise-20-size-1000-test.gz
 
        Purdue_Dr_Eval_Multi_Dataset-clutter-10-noise-20-size-10000-train.gz
        Purdue_Dr_Eval_Multi_Dataset-clutter-10-noise-20-size-1000-test.gz
 
    In the naming convention used for the archives, the string 'clutter-10'
    means that each image will have a maximum of 10 clutter objects in it,
    and the string 'noise-20' means that I have added 20% Gaussian noise to
    each image. The string 'size-10000' means that the dataset consists of
    '10,000' images.
 
    The two "Multi" datasets named above are for experimenting with the YOLO
    implementation in the module for multi-instance object detection in
    images.  The datasets without the word "Multi" in their names are for
    experimenting with single-instance detection --- but under circumstances
    that are more difficult than discussed in my Week 7 Deep Learning Lecture
    at Purdue.
 
    You will also find two smaller datasets in the overall archive that 
    contain just 30 images each.  Those are for debugging your code.
 
 
 
THE ExamplesObjectDetection DIRECTORY:
 
    This directory contains the following two scripts related to object
    detection in images:
 
        single_instance_object_detection.py
 
        multi_instance_object_detection.py
 
    The first script carries out single-instance detections in the images in
    the PurdueDrEvalDataset dataset and the second script carries out
    multi-instance detections in the PurdueDrEvalMultiDataset.  In the former
    dataset, each 128x128 image has only one instance of a meaningful object
    along with structured artifacts and 20% random noise.  And, in the latter
    dataset, each image has up to five instances of meaningful objects along
    with the structured artifacts and 20% random noise.
 
 
 
THE ExamplesRegionProposals DIRECTORY:
 
    This directory contains the following scripts for showcasing graph-based
    algorithms for constructing region proposals:
 
        selective_search.py
 
        interactive_graph_based_segmentation.py
 
        torchvision_some_basic_transformations.py    
 
        torchvision_based_image_processing.py
 
        multi_image_histogramming_and_display.py  
 
    The Examples directory also illustrates the sort of region proposal
    results you can obtain with the graph-based algorithms in this module.
    The specific illustrations are in the following subdirectories of the
    Examples directory:
 
        Examples/color_blobs/
 
        Examples/mondrian/
 
        Examples/wallpic2/
 
    Each subdirectory contains at least the following two files:
 
        selective_search.py
 
        the image file specific for that subdirectory.
 
    All you have to do is to execute selective_search.py in that directory to
    see the results on the image in that directory.
 
 
 
BUGS:
 
    Please notify the author if you encounter any bugs.  When sending
    email, please place the string 'RegionProposalGenerator' in the
    subject line to get past the author's spam filter.
 
 
ABOUT THE AUTHOR:
 
    The author, Avinash Kak, is a professor of Electrical and Computer
    Engineering at Purdue University.  For all issues related to this
    module, contact the author at kak@purdue.edu If you send email,
    please place the string "RegionProposalGenerator" in your subject
    line to get past the author's spam filter.
 
COPYRIGHT:
 
    Python Software Foundation License
 
    Copyright 2022 Avinash Kak
 
@endofdocs

 
Imported Modules
       
torchvision.transforms.functional
PIL.Image
PIL.ImageDraw
PIL.ImageFont
PIL.ImageTk
tkinter
copy
functools
glob
gzip
logging
math
torch.nn
numpy
torch.optim
os
pickle
matplotlib.pyplot
random
re
signal
sys
time
torch
torchvision
torchvision.utils
torchvision.transforms

 
Classes
       
builtins.object
RegionProposalGenerator

 
class RegionProposalGenerator(builtins.object)
    RegionProposalGenerator(*args, **kwargs)
 

 
  Methods defined here:
__init__(self, *args, **kwargs)
Initialize self.  See help(type(self)) for accurate signature.
accessing_one_color_plane(self, image_file, n)
This method shows how can access the n-th color plane of the argument color image.
convolutions_with_pytorch(self, image_file, kernel)
Using torch.nn.functional.conv2d() for demonstrating a single image convolution with
a specified kernel
displayImage(self, argimage, title='')
Displays the argument image.  The display stays on for the number of seconds
that is the first argument in the call to tk.after() divided by 1000.
displayImage2(self, argimage, title='')
Displays the argument image.  The display stays on until the user closes the
window.  If you want a display that automatically shuts off after a certain
number of seconds, use the previous method displayImage().
displayImage3(self, argimage, title='')
Displays the argument image (which must be of type Image) in its actual size.  The 
display stays on until the user closes the window.  If you want a display that 
automatically shuts off after a certain number of seconds, use the method 
displayImage().
displayImage4(self, argimage, title='')
Displays the argument image (which must be of type Image) in its actual size without 
imposing the constraint that the larger dimension of the image be at most half the 
corresponding screen dimension.
displayImage5(self, argimage, title='')
This does the same thing as displayImage4() except that it also provides for
"save" and "exit" buttons.  This method displays the argument image with more 
liberal sizing constraints than the previous methods.  This method is 
recommended for showing a composite of all the segmented objects, with each
object displayed separately.  Note that 'argimage' must be of type Image.
displayImage6(self, argimage, title='')
For the argimge which must be of type PIL.Image, this does the same thing as
displayImage3() except that it also provides for "save" and "exit" buttons.
display_tensor_as_image(self, tensor, title='')
This method converts the argument tensor into a photo image that you can display
in your terminal screen. It can convert tensors of three different shapes
into images: (3,H,W), (1,H,W), and (H,W), where H, for height, stands for the
number of pixels in the vertical direction and W, for width, for the same
along the horizontal direction.  When the first element of the shape is 3,
that means that the tensor represents a color image in which each pixel in
the (H,W) plane has three values for the three color channels.  On the other
hand, when the first element is 1, that stands for a tensor that will be
shown as a grayscale image.  And when the shape is just (H,W), that is
automatically taken to be for a grayscale image.
display_tensor_as_image2(self, tensor, title='')
This method converts the argument tensor into a photo image that you can display
in your terminal screen. It can convert tensors of three different shapes
into images: (3,H,W), (1,H,W), and (H,W), where H, for height, stands for the
number of pixels in the vertical direction and W, for width, for the same
along the horizontal direction.  When the first element of the shape is 3,
that means that the tensor represents a color image in which each pixel in
the (H,W) plane has three values for the three color channels.  On the other
hand, when the first element is 1, that stands for a tensor that will be
shown as a grayscale image.  And when the shape is just (H,W), that is
automatically taken to be for a grayscale image.
displaying_and_histogramming_images_in_batch1(self, dir_name, batch_size)
This method is the first of three such methods in this module for illustrating the
functionality of matplotlib for simultaneously displaying multiple images and
the results obtained on them in gridded arrangements.  In the implementation
shown below, the core idea in this method is to call
"plt.subplots(2,batch_size)" to create 'batch_size' number of subplot
objects, called "axes", in the form of a '2xbatch_size' array. We use the first
row of this grid to display each image in its own subplot object.  And we use
the second row the grid to display the histogram of the corresponding image
in the first row.
displaying_and_histogramming_images_in_batch2(self, dir_name, batch_size)
I now show a second approach to display multiple images and their corresponding
histograms in a gridded display.  Unlike in the previous implementation of
this method, now we do not call on "plt.subplots()" to create a grid
structure for displaying the images.  On the other hand, we now call on
"torchvision.utils.make_grid()" to construct a grid for us.  The grid is
created by giving an argument like "nrow=4" to it.  When using this method,
an important thing to keep in mind is that the first argument to make_grip()
must be a tensor of shape "(B, C, H, W)" where B stands for batch_size, C for
channels (3 for color, 1 for gray), and (H,W) for the height and width of the
image. What that means in our example is that we need to synthesize a tensor
of shape "(8,1,64,64)" in order to be able to call the "make_grid()"
function. Note that the object returned by the call to make_grid() is a
tensor unto itself.  For the example shown, if we had called
"print(grid.shape)" on the "grid" returned by "make_grid()", the answer would
be "torch.Size([3, 158, 306])" which, after it is converted into a numpy
array, can be construed by a plotting function as a color image of size
158x306.
displaying_and_histogramming_images_in_batch3(self, dir_name, batch_size)
The core idea here is to illustrate two things: (1) The syntax used for the
'singular' version of the subplot function "plt.subplot()" --- although I'll
be doing so by actually calling "fig.add_subplot()".  And (2) How you can put
together multiple multi-image plots by creating multiple Figure objects.
Figure is the top-level container of plots in matplotlib.  In the 
implementation shown below, the key statements are: 
 
    fig1 = plt.figure(1)    
    axis = fig1.add_subplot(241)              
                                                                                                              
Calling "add_subplot()" on a Figure object returns an "axis" object.  The
word "axis" is a misnomer for what should really be called a "subplot".
Subsequently, you can call display functions lime "imshow()", "bar()", etc.,
on the axis object to display an individual plot in a gridded arrangement.
 
The argument "241" in the first call to "add_subplot()" means that your
larger goal is to create a 2x4 display of plots and that you are supplying
the 1st plot for that grid.  Similarly, the argument "242" in the next call
to "add_subplot()" means that for your goal of creating a 2x4 gridded
arrangement of plots, you are now supplying the second plot.  Along the same
lines, the argument "248" toward the end of the code block that you are now
supplying the 8th plot for the 2x4 arrangement of plots.
 
Note how we create a second Figure object in the second major code block.  We
use it to display the histograms for each of the images shown in the first
Figure object.  The two Figure containers will be shown in two separate
windows on your laptop screen.
extract_data_pixels_in_bb(self, image_file, bounding_box)
Mainly used for testing
extract_image_region_interactively_by_dragging_mouse(self, image_name)
This is one method you can use to apply selective search algorithm to just a
portion of your image.  This method extract the portion you want.  You click
at the upper left corner of the rectangular portion of the image you are
interested in and you then drag the mouse pointer to the lower right corner.
Make sure that you click on "save" and "exit" after you have delineated the
area.
extract_image_region_interactively_through_mouse_clicks(self, image_file)
This method allows a user to use a sequence of mouse clicks in order to specify a
region of the input image that should be subject to further processing.  The
mouse clicks taken together define a polygon. The method encloses the
polygonal region by a minimum bounding rectangle, which then becomes the new
input image for the rest of processing.
extract_rectangular_masked_segment_of_image(self, horiz_start, horiz_end, vert_start, vert_end)
Keep in mind the following convention used in the PIL's Image class: the first
coordinate in the args supplied to the getpixel() and putpixel() methods is for
the horizontal axis (the x-axis, if you will) and the second coordinate for the
vertical axis (the y-axis).  On the other hand, in the args supplied to the
array and matrix processing functions, the first coordinate is for the row
index (meaning the vertical) and the second coordinate for the column index
(meaning the horizontal).  In what follows, I use the index 'i' with its
positive direction going down for the vertical coordinate and the index 'j'
with its positive direction going to the right as the horizontal coordinate. 
The origin is at the upper left corner of the image.
gaussian_smooth(self, pil_grayscale_image)
This method smooths an image with a Gaussian of specified sigma.
graph_based_segmentation(self, image_name, num_blobs_wanted=None)
This is an implementation of the Felzenszwalb and Huttenlocher algorithm for
graph-based segmentation of images.  At the moment, it is limited to working
on grayscale images.
graph_based_segmentation_for_arrays(self, which_one)
This method is provided to enable the user to play with small arrays when
experimenting with graph-based logic for image segmentation.  At the moment, it
provides three small arrays, one under the "which_one==1" option, one under the
"which_one==2" option, and the last under the "which_one==3" option.
graying_resizing_binarizing(self, image_file, polarity=1, area_threshold=0, min_brightness_level=100)
This is a demonstration of some of the more basic and commonly used image
transformations from the torchvision.transformations module.  The large comments
blocks are meant to serve as tutorial introduction to the syntax used for invoking
these transformations.  The transformations shown can be used for converting a
color image into a grayscale image, for resizing an image, for converting a
PIL.Image into a tensor and a tensor back into an PIL.Image object, and so on.
histogramming_and_thresholding(self, image_file)
PyTorch based experiments with histogramming and thresholding
histogramming_the_image(self, image_file)
PyTorch based experiments with histogramming the grayscale and the color values in an
image
repair_blobs(self, merged_blobs, color_map, all_pairwise_similarities)
The goal here to do a final clean-up of the blob by merging tiny pixel blobs with
an immediate neighbor, etc.  Such a cleanup requires adjacency info regarding the
blobs in order to figure out which larger blob to merge a small blob with.
selective_search_for_region_proposals(self, graph, image_name)
This method implements the Selective Search (SS) algorithm proposed by Uijlings,
van de Sande, Gevers, and Smeulders for creating region proposals for object
detection.  As mentioned elsewhere here, that algorithm sits on top of the graph
based image segmentation algorithm that was proposed by Felzenszwalb and
Huttenlocher.  The parameter 'pixel_blobs' required by the method presented here
is supposed to be the pixel blobs produced by the Felzenszwalb and Huttenlocher
algorithm.
visualize_segmentation_in_pseudocolor(self, pixel_blobs, color_map, label='')
Assigns a random color to each blob in the output of an image segmentation algorithm
visualize_segmentation_with_mean_gray(self, pixel_blobs, label='')
Assigns the mean color to each each blob in the output of an image segmentation algorithm
working_with_hsv_color_space(self, image_file, test=False)
Shows color image conversion to HSV

Data descriptors defined here:
__dict__
dictionary for instance variables (if defined)
__weakref__
list of weak references to the object (if defined)

Data and other attributes defined here:
PurdueDrEvalDataset = <class 'RegionProposalGenerator.RegionProposalGenerator.PurdueDrEvalDataset'>
This is the dataset to use if you are experimenting with single-instance object
detection.  The dataset contains three kinds of objects in its images:
Dr. Eval, and two "objects" in his neighborhood: a house and a watertower.
Each 128x128 image in the dataset contains one of these objects after it is
randomly scaled and colored and substantial structured noise in addition to
20% Gaussian noise.  Examples of these images are shown in Week 8 lecture
material in Purdue's Deep Learning class.
 
In order to understand the implementation of the dataloader for the Dr Eval
dataset for single-instance-based object detection, note that the top-level
directory for the dataset is organized as follows:
 
                                  dataroot
                                     |
                                     |
       ______________________________________________________________________
      |           |           |              |               |               | 
      |           |           |              |               |               |
  Dr_Eval       house      watertower    mask_Dr_Eval    mask_house     mask_watertower
      |           |           |              |               |               |
      |           |           |              |               |               |
    images      images      images      binary images    binary images   binary images
 
 
As you can see, the three main image directories are Dr_Eval, house, and
watertower. For each image in each of these directories, the mask for the
object of interest is supplied in the corresponding directory whose name
carries the prefix 'mask'.
 
For example, if you have an image named 29.jpg in the Dr_Eval directory, you
will have an image of the same name in the mask_Dr_Eval directory that will
just be the mask for the Dr_Eval object in the former image
 
As you can see, the dataset does not directly provide the bounding boxes for
object localization.  So the implementation of the __getitem__() function in
the dataloader must include code that calculates the bounding boxes from the
masks.  This you can see in the definition of the dataloader shown below.
 
Since this is a ``non-standard'' organization of the of data, the dataloader
must also provide for the indexing of the images so that they can be subject
to a fresh randomization that is carried out by PyTorch's
torch.utils.data.DataLoader class for each epoch of training.  The
index_dataset() function is provided for that purpose.
 
After the dataset is downloaded for the first time, the index_dataset()
function stores away the information as a PyTorch ``.pt'' file so that it can
be downloaded almost instantaneously at subsequent attempts.
 
One final note about the dataset: Under the hood, the dataset consists of the
pathnames to the image files --- and NOT the images themselves.  It is the
job of the multi-threaded ``workers'' provided by torch.utils.data.DataLoader
to actually download the images from those pathnames.
PurdueDrEvalMultiDataset = <class 'RegionProposalGenerator.RegionProposalGenerator.PurdueDrEvalMultiDataset'>
This is the dataset to use if you are experimenting with multi-instance object
detection.  As with the previous dataset, it contains three kinds of objects
in its images: Dr. Eval, and two "objects" in his neighborhood: a house and a
watertower.  Each 128x128 image in the dataset contains up to 5 instances of
these objects. The instances are randomly scaled and colored and exact number
of instances in each image is also chosen randomly. Subsequently, background
clutter is added to the images --- these are again randomly chosen
shapes. The number of clutter objects is also chosen randomly but cannot
exceed 10.  In addition to the structured clutter, I add 20% Gaussian noise
to each image.  Examples of these images are shown in Week 8 lecture material
in Purdue's Deep Learning class.
 
On account of the much richer structure of the image annotations, this
dataset is organized very differently from the previous one:
 
 
                                  dataroot
                                     |
                                     |
                         ___________________________
                        |                           |
                        |                           |
                   annotations.p                  images
 
 
Since each image is allowed to contain instances of the three different types
of "meaningful" objects, it is not possible to organize the images on the
basis of what they contain.
 
As for the annotations, the annotation for each 128x128 image is a dictionary
that contains information related to all the object instances in the image. Here
is an example of the annotation for an image that has three instances in it:
 
    annotation:  {'filename': None, 
                  'num_objects': 3, 
                  'bboxes': {0: (67, 72, 83, 118), 
                             1: (65, 2, 93, 26), 
                             2: (16, 68, 53, 122), 
                             3: None, 
                             4: None}, 
                  'bbox_labels': {0: 'Dr_Eval', 
                                  1: 'house', 
                                  2: 'watertower', 
                                  3: None, 
                                  4: None}, 
                  'seg_masks': {0: <PIL.Image.Image image mode=1 size=128x128 at 0x7F5A06C838E0>, 
                                1: <PIL.Image.Image image mode=1 size=128x128 at 0x7F5A06C837F0>, 
                                2: <PIL.Image.Image image mode=1 size=128x128 at 0x7F5A06C838B0>, 
                                3: None, 
                                4: None}
                 }
 
The annotations for the individual images are stored in a global Python
dictionary called 'all_annotations' whose keys consist of the pathnames to
the individual image files and the values the annotations dict for the
corresponding images.  The filename shown above in the keystroke diagram,
'annotations.p' is what you get by calling 'pickle.dump()' on the
'all_annotations' dictionary.
RPN = <class 'RegionProposalGenerator.RegionProposalGenerator.RPN'>
I have not yet mentioned this class in the documentation page for this module
because its implementation is not finished.
SingleInstanceDetector = <class 'RegionProposalGenerator.RegionProposalGenerator.SingleInstanceDetector'>
This class demonstrates single-instance object detection on the images in the
PurdueDrEvalDataset dataset.  Although these image are complex, in the sense
that each image contains multiple clutter objects in addition to random
noise, nonetheless we know that each image contains only a single meaningful
object instance.  The LOADnet network used for detection is adaptation of the
the LOADnet2 network from DLStudio to the case of 128x128 sized input images.
The LOADnet network uses the SkipBlock as a building-block element for
dealing the problems caused by vanishing gradients.
YoloLikeDetector = <class 'RegionProposalGenerator.RegionProposalGenerator.YoloLikeDetector'>
The primary purpose of this class is to demonstrate multi-instance object detection with YOLO-like 
logic.  A key parameter of the logic for YOLO-like detection is the variable 'yolo_interval'.  
The image gridding that is required is based on the value assigned to this variable.  The grid is 
represented by an SxS array of cells where S is the image width divided by yolo_interval. So for
images of size 128x128 and 'yolo_interval=20', you will get a 6x6 grid of cells over the image. Since
my goal is merely to explain the principles of the YOLO logic, I have not bothered with the bottom
8 rows and the right-most 8 columns of the image that get left out of the area covered by such a grid.
 
An important element of the YOLO logic is defining a set of Anchor Boxes for each cell in the SxS 
grid.  The anchor boxes are characterized by their aspect ratios.  By aspect ratio I mean the
'height/width' characterization of the boxes.  My implementation provides for 5 anchor boxes for 
each cell with the following aspect ratios: 1/5, 1/3, 1/1, 3/1, 5/1.  
 
At training time, each instance in the image is assigned to that cell whose central pixel is 
closest to the center of the bounding box for the instance. After the cell assignment, the 
instance is assigned to that anchor box whose aspect ratio comes closest to matching the aspect 
ratio of the instance.
 
The assigning of an object instance to a <cell, anchor_box> pair is encoded in the form of a 
'5+C' element long YOLO vector where C is the number of classes for the object instances.  
In our cases, C is 3 for the three classes 'Dr_Eval', 'house' and 'watertower', therefore we 
end up with an 8-element vector encoding when we assign an instance to a <cell, anchor_box> 
pair.  The last C elements of the encoding vector can be thought as a one-hot representation 
of the class label for the instance.
 
The first five elements of the vector encoding for each anchor box in a cell are set as follows: 
The first element is set to 1 if an object instance was actually assigned to that anchor box. 
The next two elements are the (x,y) displacements of the center of the actual bounding box 
for the object instance vis-a-vis the center of the cell. These two displacements are expressed 
as a fraction of the width and the height of the cell.  The next two elements of the YOLO vector
are the actual height and the actual width of the true bounding box for the instance in question 
as a multiple of the cell dimension.
 
The 8-element YOLO vectors are packed into a YOLO tensor of shape (num_cells, num_anch_boxes, 8)
where num_cell is 36 for a 6x6 gridding of an image, num_anch_boxes is 5.
 
Classpath:  RegionProposalGenerator  ->  YoloLikeDetector
canvas = None
drawEnable = 0
region_mark_coords = {}
startX = 0
startY = 0

 
Functions
       
ctrl_c_handler(signum, frame)

 
p
        __author__ = 'Avinash Kak (kak@purdue.edu)'
__copyright__ = '(C) 2022 Avinash Kak. Python Software Foundation.'
__date__ = '2022-April-16'
__url__ = 'https://engineering.purdue.edu/kak/distRPG/RegionProposalGenerator-2.0.8.html'
__version__ = '2.0.8'
 
Author
        Avinash Kak (kak@purdue.edu)