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

New version of the GRM library

parent 3f4c3298
......@@ -15,36 +15,5 @@
# PURPOSE. See the above copyright notices for more information.
#=========================================================================
project(GRM)
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)
add_executable(RegionMergingSegmentation RegionMergingSegmentation.cxx)
target_link_libraries(RegionMergingSegmentation OTBGRM)
#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 @@
# PURPOSE. See the above copyright notices for more information.
#=========================================================================
add_executable(EuclideanDistanceSegmentation EuclideanDistanceSegmentation.cxx)
target_link_libraries(EuclideanDistanceSegmentation
GRM)
project(GRM)
add_executable(FLSASegmentation FLSASegmentation.cxx)
target_link_libraries(FLSASegmentation
GRM)
cmake_minimum_required(VERSION 2.8)
add_executable(BaatzSegmentation BaatzSegmentation.cxx)
target_link_libraries(BaatzSegmentation
GRM)
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)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/Code)
add_subdirectory(Code)
add_subdirectory(Applications)
......@@ -15,6 +15,8 @@
# PURPOSE. See the above copyright notices for more information.
#=========================================================================
file(GLOB SRC "*.cxx")
add_library(GRM ${SRC})
target_link_libraries(GRM OTBCommon OTBIO)
add_library(OTBGRM
lsrmContourOperations.cpp
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
#ifndef __LSRM_CONTOUR_OPERATIONS_H
#define __LSRM_CONTOUR_OPERATIONS_H
#include "lsrmDataStructures.h"
#include "lsrmNeighborhood.h"
#include <cstring>
#include <algorithm>
namespace lsrm
{
class ContourOperations
{
public:
/* Some convenient typedefs */
typedef std::vector<long unsigned int> PixelList;
typedef typename PixelList::iterator PixelIterator;
typedef typename PixelList::const_iterator PixelConstIterator;
/*
* Given two rectangular bounding boxes,
* it returns the bounding box which is
* the union of both bounding boxes.
*
* @params
* const BoundingBox& bb1 : reference to the first bounding box
* const BoundingBox& bb2 : reference to the second bounding box
* @return the union of bb1 and bb2.
*/
static BoundingBox MergeBoundingBoxes(const BoundingBox& bb1,
const BoundingBox& bb2);
/*
* Given the coordinates of the pixel in the current image, the
* bounding box containing it and the width of the image, it returns
* the coordinates of the pixel in the referential of the bounding box.
*
* @params
* const long unsigned int id : coordinates of the pixel in the image
* referential.
* const BoundingBox& bb : bounding box containing the pixel
* const unsigned int width : width of the image
* @return the coordinates of the pixel in the bounding box referential.
*/
static long unsigned int ToBoundingBoxCoordinates(const long unsigned int id,
const BoundingBox& bb,
const unsigned int width);
/*
* Given the contour, the first pixel coordinates
* and the width of the current image,
* it returns the list of the border pixels.
*
* @params
* const long unsigned int id : coordinates of the first pixel.
* const Contour& contour : reference to the contour.
* const unsigned int width : width of the current image.
*/
static PixelList GenerateBorderPixels(long unsigned int id,
const Contour& contour,
const unsigned int width);
/*
* Return true if it exists a pixel in the grid at the north
* of the current pixel.
*
* @params
* const long unsigned int idx : coordinates of the current pixel
* const short * grid : grid
* const unsigned int grid_width : width of the grid
*/
static bool GetCollisionAtNorth(const long unsigned int idx,
const short * grid,
const unsigned int grid_width);
/*
* Return true if it exists a pixel in the grid at the north East
* of the current pixel.
*
* @params
* const long unsigned int idx : coordinates of the current pixel
* const short * grid : grid
* const unsigned int grid_width : width of the grid
*/
static bool GetCollisionAtNorthEast(const long unsigned int idx,
const short * grid,
const unsigned int grid_width);
/*
* Return true if it exists a pixel in the grid at the East
* of the current pixel.
*
* @params
* const long unsigned int idx : coordinates of the current pixel
* const short * grid : grid
* const unsigned int grid_width : width of the grid
*/
static bool GetCollisionAtEast(const long unsigned int idx,
const short * grid,
const unsigned int grid_width);
/*
* Return true if it exists a pixel in the grid at the South East
* of the current pixel.
*
* @params
* const long unsigned int idx : coordinates of the current pixel
* const short * grid : grid
* const unsigned int grid_width : width of the grid
* const unsigned int grid_height : height of the grid
*/
static bool GetCollisionAtSouthEast(const long unsigned int idx,
const short * grid,
const unsigned int grid_width,
const unsigned int grid_height);
/*
* Return true if it exists a pixel in the grid at the South
* of the current pixel.
*
* @params
* const long unsigned int idx : coordinates of the current pixel
* const short * grid : grid
* const unsigned int grid_width : width of the grid
* const unsigned int grid_height : height of the grid
*/
static bool GetCollisionAtSouth(const long unsigned int idx,
const short * grid,
const unsigned int grid_width,
const unsigned int grid_height);
/*
* Return true if it exists a pixel in the grid at the South West
* of the current pixel.
*
* @params
* const long unsigned int idx : coordinates of the current pixel
* const short * grid : grid
* const unsigned int grid_width : width of the grid
* const unsigned int grid_height : height of the grid
*/
static bool GetCollisionAtSouthWest(const long unsigned int idx,
const short * grid,
const unsigned int grid_width,
const unsigned int grid_height);
/*
* Return true if it exists a pixel in the grid at the West
* of the current pixel.
*
* @params
* const long unsigned int idx : coordinates of the current pixel
* const short * grid : grid
* const unsigned int grid_width : width of the grid
*/
static bool GetCollisionAtWest(const long unsigned int idx,
const short * grid,
const unsigned int grid_width);
/*
* Return true if it exists a pixel in the grid at the North West
* of the current pixel.
*
* @params
* const long unsigned int idx : coordinates of the current pixel
* const short * grid : grid
* const unsigned int grid_width : width of the grid
*/
static bool GetCollisionAtNorthWest(const long unsigned int idx,
const short * grid,
const unsigned int grid_width);
/*
* Return true if the pixel located at the coordinates idx
* is an internal pixel, i.e, in each direction there is a pixel.
*
* @params
* const long unsigned int idx : coordinates of the current pixel
* const short * grid : grid
* const unsigned int grid_width : width of the grid
* const unsigned int grid_height : height of the grid
*/
static bool IsInternalPixel(const long unsigned int idx,
const short * grid,
const unsigned int grid_width,
const unsigned int grid_height);
/*
* Given the direction of the front, it returns the field of view
*
* @params
* short * sight : former field of view.
* const short direction : new front direction.
*/
static void UpdateSight(short * sight, const short direction);
/*
* Create a contour element when we move toward the top, right, bottom
* or left wrt to the current pixel.
*
* @params
* long unsigned int& curr_mat_id : pixel coordinates
* short& direction : toward the direction we look
* short& pos : location on the pixel
* Contour& curr_contour : new contour to be created
* const unsigned int grid_width : width of the grid
*/
static void EncodeContourForTopPixel(long unsigned int& curr_mat_id,
short& direction,
short& pos,
Contour& curr_contour,
const unsigned int grid_width);
static void EncodeContourForRightPixel(long unsigned int& curr_mat_id,
short& direction,
short& pos,
Contour& curr_contour);
static void EncodeContourForBottomPixel(long unsigned int& curr_mat_id,
short& direction,
short& pos,
Contour& curr_contour,
const unsigned int grid_width);
static void EncodeContourForLeftPixel(long unsigned int& curr_mat_id,
short& direction,
short& pos,
Contour& curr_contour);
/*
* Given the coordinates of the first pixel, the bounding box, the grid
* with the location of the border pixels and the dimension of the referential
* image, it returns a new contour
*
* @params
* const long unsigned int idx : coordinates of the current pixel.
* const BoundingBox& bbox : reference to the bounding box
* const short * grid : grid
* const unsigned int width : width of the current image.
* cont unsigned int height : height of the current image.
*/
static Contour CreateContourFromBorderPixels(const long unsigned int id,
const BoundingBox& bbox,
const short * grid,