Commit 3e38dc0c authored by Pierre Lassalle's avatar Pierre Lassalle

New version of the GRM library

parent 3f4c3298
...@@ -15,36 +15,5 @@ ...@@ -15,36 +15,5 @@
# PURPOSE. See the above copyright notices for more information. # PURPOSE. See the above copyright notices for more information.
#========================================================================= #=========================================================================
project(GRM) add_executable(RegionMergingSegmentation RegionMergingSegmentation.cxx)
target_link_libraries(RegionMergingSegmentation OTBGRM)
cmake_minimum_required(VERSION 2.8)
find_package(OTB)
IF(OTB_FOUND)
include(${OTB_USE_FILE})
ELSE(OTB_FOUND)
message(FATAL_ERROR
"Cannot build OTB project without OTB. Please set OTB_DIR.")
ENDIF(OTB_FOUND)
#Check compiler version
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
# require at least gcc 4.8
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8)
message(FATAL_ERROR "GCC version must be at least 4.8!")
endif()
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
# require at least clang 3.2
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.2)
message(FATAL_ERROR "Clang version must be at least 3.2!")
endif()
else()
message(WARNING "You are using an unsupported compiler! Compilation has only been tested with Clang and GCC.")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/Library)
add_subdirectory(Library)
add_subdirectory(Applications)
#include <iostream>
#include <otbImage.h>
#include <otbImageFileReader.h>
#include <otbVectorImage.h>
#include "lsrmBaatzSegmenter.h"
int main(int argc, char *argv[])
{
if(argc != 7)
{
std::cerr << "Usage: ./" << argv[0] << "\n"
<< "\t[input image path] : (.jpg, .png, .tif)\n"
<< "\t[output clustered image] : (.jpg, .png, .tif)\n"
<< "\t[output label image] : (.tif)\n"
<< "\t[spectral weight] : range from 0 to 1\n"
<< "\t[shape weight] : range from 0 to 1\n"
<< "\t[scale threshold] : unlimited positive value"
<< std::endl;
return 1;
}
lsrm::BaatzParam params;
const char * input_image = argv[1];
const char * clustered_image = argv[2];
const char * label_image = argv[3];
params.m_SpectralWeight = atof(argv[4]);
params.m_ShapeWeight = atof(argv[5]);
float sqrt_scale = atof(argv[6]);
const float scale = sqrt_scale * sqrt_scale;
typedef float PixelType;
typedef otb::VectorImage<PixelType, 2> ImageType;
typedef lsrm::BaatzSegmenter<ImageType> SegmenterType;
SegmenterType segmenter;
segmenter.SetParam(params);
segmenter.SetThreshold(scale);
segmenter.SetInputFileName(input_image);
segmenter.SetClusteredImageFileName(clustered_image);
segmenter.SetLabelImageFileName(label_image);
segmenter.RunSegmentation();
return 0;
}
...@@ -15,14 +15,19 @@ ...@@ -15,14 +15,19 @@
# PURPOSE. See the above copyright notices for more information. # PURPOSE. See the above copyright notices for more information.
#========================================================================= #=========================================================================
add_executable(EuclideanDistanceSegmentation EuclideanDistanceSegmentation.cxx) project(GRM)
target_link_libraries(EuclideanDistanceSegmentation
GRM)
add_executable(FLSASegmentation FLSASegmentation.cxx) cmake_minimum_required(VERSION 2.8)
target_link_libraries(FLSASegmentation
GRM)
add_executable(BaatzSegmentation BaatzSegmentation.cxx) find_package(OTB)
target_link_libraries(BaatzSegmentation IF(OTB_FOUND)
GRM) include(${OTB_USE_FILE})
ELSE(OTB_FOUND)
message(FATAL_ERROR
"Cannot build OTB project without OTB. Please set OTB_DIR.")
ENDIF(OTB_FOUND)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/Code)
add_subdirectory(Code)
add_subdirectory(Applications)
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
# PURPOSE. See the above copyright notices for more information. # PURPOSE. See the above copyright notices for more information.
#========================================================================= #=========================================================================
file(GLOB SRC "*.cxx") add_library(OTBGRM
add_library(GRM ${SRC}) lsrmContourOperations.cpp
target_link_libraries(GRM OTBCommon OTBIO) lsrmNeighborhood.cpp)
target_link_libraries(OTBGRM OTBCommon OTBIO)
#ifndef __LSRM_BAATZ_SEGMENTER_H
#define __LSRM_BAATZ_SEGMENTER_H
#include "lsrmSegmenter.h"
#include "lsrmGraphToOtbImage.h"
/*
* Tutorial : Implementation of the Baatz & Schape criterion
*
* Details about the criterion can be found in the following publication:
*
* Martin Baatz and Arno Schape. Multiresolution segmentation: an optimization approach for high quality multi-scale image segmentation.
* Angewandte Geographische Informationsverarbeitung XII, pages 12–23, 2000.
*
* The steps are ordered has to be followed in a chronological way for a full understanding.
* This tutorial does not aim at explaining all the details of the large scale region merging
* but gives all the elements to use it properly.
*/
namespace lsrm
{
/*
* Step 1 :
* We define the specific attributes required for the Baatz & Schape criterion.
* Regions are represented by nodes in a graph and the lsrm library has an internal
* representation of the nodes with the class lsrm::Node.
* --> A node contains a unique number to be indentified, it corresponds to the vectorized
* coordinates of its first pixel and its name is m_Id.
* To retrieve the 2D coordinates from the value of m_Id, it is necessary to know the dimension
* of the image (the number of rows and columns), then :
* x = m_Id % columns and y = m_Id / columns.
*
* --> A node contains the perimeter (m_Perimeter) of its corresponding region, it is the length of the external
* boundary of the region. For example a region of one pixel has a perimeter of 4.
*
* --> A node contains the area (m_Area) of its corresponding region, it is the number of pixels within the
* region.
*
* --> A node contains the minimum rectangular bounding box (parrallel to the image axis) of the region (m_Bbox).
* the boundix box is determined by the structure lsrm::BoundingBox with the coordinates of the upper left
* corner pixel and the width and the height (m_UX, m_UY, m_W, m_H).
*
* After Reading the paper about the Baatz & Schape criterion, we can conclude that we have to define additional
* spectral attributes for a node: its mean vector, the square mean vector, the sum of all the pixels
* and the standard deviation vector.
*
* We define a new class BaatzNode which inherits from the template structure Node. Notice that the template
* is the derived node type, then the template type is BaatzNode.
*/
struct BaatzNode : Node<BaatzNode>
{
std::vector<float> m_Means;
std::vector<float> m_SquareMeans;
std::vector<float> m_SpectralSum;
std::vector<float> m_Std;
};
/*
* Step 2
*
* The Baatz & Schape criterion has user-defined parameters, the spectral weight
* which is the relative importance given to the spectral components and the shape
* weight for the shape components.
*
* We define then a structure wrapping these two parameters we called BaatzParam.
*/
struct BaatzParam
{
float m_SpectralWeight;
float m_ShapeWeight;
};
/*
* Step 3 :
*
*/
template<class TImage>
class BaatzSegmenter : public Segmenter< TImage, BaatzNode, BaatzParam>
{
public:
/* Some convenient typedefs */
typedef Segmenter<TImage, BaatzNode, BaatzParam> Superclass;
typedef TImage ImageType;
typedef typename Superclass::GraphType GraphType;
typedef typename Superclass::NodePointerType NodePointerType;
typedef typename Superclass::GraphOperatorType GraphOperatorType;
typedef GraphToOtbImage<GraphType> IOType;
BaatzSegmenter();
void RunSegmentation();
float ComputeMergingCost(NodePointerType n1, NodePointerType n2);
void UpdateSpecificAttributes(NodePointerType n1, NodePointerType n2);
void InitFromImage();
};
} // end of namespace lsrm
#include "lsrmBaatzSegmenter.txx"
#endif
#ifndef __LSRM_BAATZ_SEGMENTER_TXX
#define __LSRM_BAATZ_SEGMENTER_TXX
#include <otbImageFileReader.h>
#include <itkImageRegionIterator.h>
namespace lsrm
{
template<class TImage>
BaatzSegmenter<TImage>::BaatzSegmenter() : Superclass()
{
this->m_DoBFSegmentation = true;
this->m_NumberOfIterations = 75;
}
template<class TImage>
void
BaatzSegmenter<TImage>::InitFromImage()
{
typedef otb::ImageFileReader<TImage> ImageReaderType;
typedef itk::ImageRegionIterator<TImage> ImageIterator;
typename ImageReaderType::Pointer reader = ImageReaderType::New();
reader->SetFileName(this->m_InputFileName);
reader->Update();
this->m_ImageWidth = reader->GetOutput()->GetLargestPossibleRegion().GetSize()[0];
this->m_ImageHeight = reader->GetOutput()->GetLargestPossibleRegion().GetSize()[1];
this->m_NumberOfComponentsPerPixel = reader->GetOutput()->GetNumberOfComponentsPerPixel();
std::size_t idx = 0;
ImageIterator it(reader->GetOutput(), reader->GetOutput()->GetLargestPossibleRegion());
for(it.GoToBegin(); !it.IsAtEnd(); ++it)
{
this->m_Graph.m_Nodes[idx]->m_Means.reserve(this->m_NumberOfComponentsPerPixel);
this->m_Graph.m_Nodes[idx]->m_SquareMeans.reserve(this->m_NumberOfComponentsPerPixel);
this->m_Graph.m_Nodes[idx]->m_SpectralSum.reserve(this->m_NumberOfComponentsPerPixel);
this->m_Graph.m_Nodes[idx]->m_Std.assign(this->m_NumberOfComponentsPerPixel, 0.0f);
for(std::size_t b = 0; b < this->m_NumberOfComponentsPerPixel; ++b)
{
this->m_Graph.m_Nodes[idx]->m_Means.push_back(it.Get()[b]);
this->m_Graph.m_Nodes[idx]->m_SquareMeans.push_back((it.Get()[b])*(it.Get()[b]));
this->m_Graph.m_Nodes[idx]->m_SpectralSum.push_back(it.Get()[b]);
}
++idx;
}
}
template<class TImage>
float
BaatzSegmenter<TImage>::ComputeMergingCost(NodePointerType n1, NodePointerType n2)
{
const std::size_t bands = n1->m_Means.size();
const unsigned int a1 = n1->m_Area, a2 = n2->m_Area, a_sum = a1 + a2;
float spect_cost = 0.0f;
float mean, square_mean, sum, std;
for (unsigned int b = 0; b < this->m_NumberOfComponentsPerPixel; b++)
{
mean = ((a1 * n1->m_Means[b]) + (a2 * n2->m_Means[b])) / a_sum;
square_mean = n1->m_SquareMeans[b] + n2->m_SquareMeans[b];
sum = n1->m_SpectralSum[b] + n2->m_SpectralSum[b];
std = std::sqrt((square_mean - 2*mean*sum + a_sum * mean* mean) / a_sum);
spect_cost += (a_sum * std - a1 * n1->m_Std[b] - a2 * n2->m_Std[b]);
}
spect_cost *= this->m_Param.m_ShapeWeight;
if(spect_cost < this->m_Threshold)
{
float shape_cost, smooth_f, compact_f;
// Compute the shape merging cost
const float p1 = static_cast<float>(n1->m_Perimeter);
const float p2 = static_cast<float>(n2->m_Perimeter);
const unsigned int boundary = (GraphOperatorType::FindEdge(n1, n2))->m_Boundary;
const float p3 = p1 + p2 - 2 * static_cast<float>(boundary);
const BoundingBox merged_bbox = ContourOperations::MergeBoundingBoxes(n1->m_Bbox, n2->m_Bbox);
const float bb1_perimeter = static_cast<float>(2*n1->m_Bbox.m_W + 2*n1->m_Bbox.m_H);
const float bb2_perimeter = static_cast<float>(2*n2->m_Bbox.m_W + 2*n2->m_Bbox.m_H);
const float mbb_perimeter = static_cast<float>(2 * merged_bbox.m_W + 2 * merged_bbox.m_H);
smooth_f = a_sum*p3/mbb_perimeter - a1*p1/bb1_perimeter - a2*p2/bb2_perimeter;
compact_f = a_sum*p3/std::sqrt(a_sum) - a1*p1/std::sqrt(a1) - a2*p2/std::sqrt(a2);
shape_cost = this->m_Param.m_ShapeWeight * compact_f + (1-this->m_Param.m_ShapeWeight) * smooth_f;
return (spect_cost + (1-this->m_Param.m_ShapeWeight)*shape_cost);
}
else
return spect_cost;
}
template<class TImage>
void
BaatzSegmenter<TImage>::UpdateSpecificAttributes(NodePointerType n1, NodePointerType n2)
{
const float a1 = static_cast<float>(n1->m_Area);
const float a2 = static_cast<float>(n2->m_Area);
const float a_sum = a1 + a2;
for(unsigned int b = 0; b < this->m_NumberOfComponentsPerPixel; ++b)
{
n1->m_Means[b] = (a1 * n1->m_Means[b] + a2 * n2->m_Means[b]) / a_sum;
n1->m_SquareMeans[b] += n2->m_SquareMeans[b];
n1->m_SpectralSum[b] += n2->m_SpectralSum[b];
n1->m_Std[b] = std::sqrt((n1->m_SquareMeans[b] - 2 * n1->m_Means[b] * n1->m_SpectralSum[b] +
a_sum * n1->m_Means[b] * n1->m_Means[b]) / a_sum);
}
}
template<class TImage>
void
BaatzSegmenter<TImage>::RunSegmentation()
{
GraphOperatorType::InitNodes(this->m_Graph, *this, this->m_InputFileName, FOUR);
bool prev_merged =
GraphOperatorType::PerfomAllIterationsWithLMBFAndConstThreshold(this->m_Graph, *this,
this->m_Threshold, this->m_NumberOfIterations,
this->m_ImageWidth, this->m_ImageHeight);
if(prev_merged && this->m_DoBFSegmentation)
{
prev_merged =
GraphOperatorType::PerfomAllIterationsWithBFAndConstThreshold(this->m_Graph, *this,
this->m_Threshold, this->m_NumberOfIterations,
this->m_ImageWidth, this->m_ImageHeight);
}
if(!this->m_ClusteredImageFileName.empty())
IOType::WriteOutputRGBImage(this->m_Graph,
this->m_ImageWidth,
this->m_ImageHeight,
this->m_ClusteredImageFileName);
if(!this->m_LabelImageFileName.empty())
IOType::WriteLabelImage(this->m_Graph,
this->m_ImageWidth,
this->m_ImageHeight,
this->m_LabelImageFileName);
if(!this->m_ContourImageFileName.empty())
IOType::WriteContourImage(this->m_Graph,
this->m_InputFileName,
this->m_LabelImageFileName);
}
} // end of namespace lsrm
#endif
This diff is collapsed.
#ifndef __LSRM_DATA_STRUCTURES_H
#define __LSRM_DATA_STRUCTURES_H
#include <bitset>
#include <vector>
namespace lsrm
{
typedef std::bitset<2> ContourElem;
typedef std::vector<ContourElem> Contour;
typedef typename Contour::iterator ContourIterator;
typedef typename Contour::const_iterator ContourConstIterator;
struct BoundingBox
{
/* X coordinate of the upper left. */
long unsigned int m_UX;
/* Y coordinate of the upper left. */
long unsigned int m_UY;
/* Width */
unsigned int m_W;
/* Height */
unsigned int m_H;
};
} // end of namespace lsrm
#endif
#ifndef __LSRM_GRAPH_H
#define __LSRM_GRAPH_H
#include <boost/shared_ptr.hpp>
#include "lsrmDataStructures.h"
namespace lsrm
{
struct BaseNode
{
BaseNode(){}
virtual ~BaseNode() {}
/* Node already merged. */
bool m_Valid;
/* Node has to be removed from the graph. */
bool m_Expired;
/* Perimeter of the region */
unsigned int m_Perimeter;
/* Area (number of inner pixels) of the region */
unsigned int m_Area;
/*
Node is identified by the location
of the first pixel of the region.
*/
long unsigned int m_Id;
/*
Bounding box of the region
in the image.
*/
BoundingBox m_Bbox;
/*
Contour of the region
*/
Contour m_Contour;
};
template<class DerivedNode>
struct Node : BaseNode
{
struct Edge
{
/* Boundary length between two adjacent regions.*/
unsigned int m_Boundary;
/* Fusion cost (similarity measure) with the target node. */
float m_Cost;
/* Pointer to a neighboring node. */
boost::shared_ptr<DerivedNode> m_Target;
};
Node(){};
virtual ~Node() {}
std::vector<Edge> m_Edges;
};
template<class TNode>
struct Graph
{
typedef TNode NodeType;
typedef typename NodeType::Edge EdgeType;
typedef boost::shared_ptr<NodeType> NodePointerType;
typedef std::vector<NodePointerType> NodeListType;
typedef typename NodeListType::iterator NodeIteratorType;
typedef typename NodeListType::const_iterator NodeConstIteratorType;
typedef std::vector<EdgeType> EdgeListType;
typedef typename EdgeListType::iterator EdgeIteratorType;
typedef typename EdgeListType::const_iterator EdgeConstIteratorType;
std::vector< boost::shared_ptr<TNode> > m_Nodes;
};
} // end of namespace lsrm
#endif
This diff is collapsed.
This diff is collapsed.
#ifndef __LSRM_GRAPH_TO_OTBIMAGE_H
#define __LSRM_GRAPH_TO_OTBIMAGE_H
#include <itkRGBPixel.h>
#include <otbImage.h>
#include <otbVectorImage.h>
#include <otbImageFileReader.h>
#include <otbImageFileWriter.h>
#include "lsrmGraph.h"
#include "lsrmContourOperations.h"
#include <string>
#include <stdlib.h>
#include <time.h>
namespace lsrm
{
template<class TGraph>
class GraphToOtbImage
{
public:
/* Some convenient typedefs */
typedef TGraph GraphType;
typedef typename GraphType::NodeType NodeType;
typedef std::vector< boost::shared_ptr<NodeType> > NodeList;
typedef typename NodeList::const_iterator NodeConstIterator;
/*
* Given a graph of nodes and the size of the image, it returns
* a raster image where to every pixel of a region is assigned
* the same unique label value.
*
* @params
* const GraphType& graph : graph of nodes
* const unsigned int width : width of the output labeled image.
* const unsigned int height : height of the output labeled image.
* const std::string& outputFileName : filename of the output image.
*/
static void WriteLabelImage(const GraphType& graph,
const unsigned int width,
const unsigned int height,
const std::string& outputFileName);
/*
* Given a graph of nodes and the size of the image, it returns
* a raster image where to every pixel of a region is assigned
* the same rgb color.
*
* @params
* const GraphType& graph : graph of nodes
* const unsigned int width : width of the output rgb image.
* const unsigned int height : height of the output rgb image.
* const std::string& outputFileName : filename of the output image.
*/
static void WriteOutputRGBImage(const GraphType& graph,
const unsigned int width,
const unsigned int height,
const std::string& outputFileName);
static void WriteContourImage(const GraphType& graph,
const std::string& inputFileName,
const std::string& outputFileName);
};
} // end of namespace lsrm
#include "lsrmGraphToOtbImage.txx"
#endif
#ifndef __LSRM_GRAPH_TO_OTBIMAGE_TXX
#define __LSRM_GRAPH_TO_OTBIMAGE_TXX
#include "lsrmGraphToOtbImage.h"
namespace lsrm
{
template<class TGraph>
void GraphToOtbImage<TGraph>::WriteLabelImage(const GraphType& graph,
const unsigned int width,
const unsigned int height,
const std::string& outputFileName)
{
typedef long unsigned int LabelPixelType;
typedef otb::Image<LabelPixelType, 2> LabelImageType;
LabelImageType::IndexType index;
LabelImageType::SizeType size;
LabelImageType::RegionType region;
index[0] = 0; index[1] = 0;
size[0] = width; size[1] = height;
region.SetIndex(index);
region.SetSize(size);
LabelImageType::Pointer label_img = LabelImageType::New();
label_img->SetRegions(region);
label_img->Allocate();
// Start at 1 (value 0 can be used for invalid pixels)
long unsigned int label = 1;
for(NodeConstIterator nit = graph.m_Nodes.begin();
nit != graph.m_Nodes.end(); ++nit)
{
ContourOperations::PixelList pixels = ContourOperations::GeneratePixels((*nit)->m_Id,
(*nit)->m_Contour,
(*nit)->m_Bbox,
width);
for (ContourOperations::PixelConstIterator pit = pixels.begin();
pit != pixels.end(); ++pit)
{
index[0] = (*pit) % width;
index[1] = (*pit) / width;
label_img->SetPixel(index, label);
}
++label;
}
typedef otb::ImageFileWriter<LabelImageType> LabelWriterType;
LabelWriterType::Pointer label_writer = LabelWriterType::New();
label_writer->SetInput(label_img);
label_writer->SetFileName(outputFileName);
label_writer->Update();
}
template<class TGraph>
void GraphToOtbImage<TGraph>::WriteOutputRGBImage(const GraphType& graph,
const unsigned int width,
const unsigned int height,
const std::string& outputFileName)
{
typedef unsigned char RGBPixelType;
typedef otb::VectorImage<RGBPixelType, 2> RGBImageType;
typename RGBImageType::PixelType pixelValue;