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
/*=========================================================================
#include "lsrmContourOperations.h"
Program: Shape Encoder librarie of OBIA (Object Based Image Analysis)
Language: C++
author: Lassalle Pierre
Copyright (c) Centre National d'Etudes Spatiales. All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=========================================================================*/
#include "contour-encoder.h"
namespace shpenc
namespace lsrm
{
typename ShapeEncoder::Contour
ShapeEncoder::EncodeContour(long unsigned int curr_id,
Contour& curr_contour,
long unsigned int adj_id,
Contour& adj_contour,
BoundingBox& merged_bbox)
BoundingBox
ContourOperations::MergeBoundingBoxes(const BoundingBox& bb1,
const BoundingBox& bb2)
{
// To determine the future contour pixels
std::vector<short> matrix_bbox(merged_bbox[2]*merged_bbox[3], 0);
long unsigned int min_ux, min_uy, max_xw, max_yh;
BoundingBox bb;
// Generate contour pixels of the merged segments
auto curr_v = GeneratePixels(curr_id, curr_contour);
auto adj_v = GeneratePixels(adj_id, adj_contour);
curr_v.insert(curr_v.end(), adj_v.begin(), adj_v.end());
min_ux = std::min(bb1.m_UX, bb2.m_UX);
min_uy = std::min(bb1.m_UY, bb2.m_UY);
max_xw = std::max(bb1.m_UX + bb1.m_W, bb2.m_UX + bb2.m_W);
max_yh = std::max(bb1.m_UY + bb1.m_H, bb2.m_UY + bb2.m_H);
// A case in the matrix_bbox filled by a pixel is marked true
for(auto& pix : curr_v)
matrix_bbox[ToBBoxId(pix, merged_bbox)] = 1;
bb.m_UX = min_ux;
bb.m_UY = min_uy;
bb.m_W = max_xw - min_ux;
bb.m_H = max_yh - min_uy;
// Remove internal pixels
RemoveInternalPixels(matrix_bbox, merged_bbox[3], merged_bbox[2]);
return bb;
}
// Encode the new contour
return EncodeNewContour(curr_id, merged_bbox, matrix_bbox);
long unsigned int
ContourOperations::ToBoundingBoxCoordinates(const long unsigned int id,
const BoundingBox& bb,
const unsigned int width)
{
long unsigned int img_x = id % width;
long unsigned int img_y = id / width;
long unsigned int bb_x = img_x - bb.m_UX;
long unsigned int bb_y = img_y - bb.m_UY;
return (bb_y * bb.m_W + bb_x);
}
std::vector<long unsigned int> ShapeEncoder::GeneratePixels(long unsigned int id, Contour& contour)
typename ContourOperations::PixelList
ContourOperations::GenerateBorderPixels(long unsigned int id,
const Contour& contour,
const unsigned int width)
{
std::vector<long unsigned int> pixels;
PixelList pixels;
pixels.push_back(id);
short curr_move, prev_move;
if(contour.size() > 4)
{
for(auto m = contour.begin()+1; m != contour.end(); m++)
for(ContourConstIterator m = contour.begin()+1; m != contour.end(); m++)
{
curr_move = (*m)[0] + 2*(*m)[1];
prev_move = (*(m-1))[0] + 2*(*(m-1))[1];
......@@ -62,14 +55,14 @@ namespace shpenc
{
if(prev_move == 0)
{
id-=m_Cols;
id-=width;
pixels.push_back(id);
}
else if(prev_move == 1) // 1
{
id++;
pixels.push_back(id);
id-=m_Cols;
id-=width;
pixels.push_back(id);
}
}
......@@ -82,7 +75,7 @@ namespace shpenc
}
else if(prev_move == 2)
{
id+=m_Cols;
id+=width;
pixels.push_back(id);
id++;
pixels.push_back(id);
......@@ -94,12 +87,12 @@ namespace shpenc
{
id--;
pixels.push_back(id);
id+=m_Cols;
id+=width;
pixels.push_back(id);
}
else if(prev_move == 2)
{
id+=m_Cols;
id+=width;
pixels.push_back(id);
}
}
......@@ -107,7 +100,7 @@ namespace shpenc
{
if(prev_move==0)
{
id-=m_Cols;
id-=width;
pixels.push_back(id);
id--;
pixels.push_back(id);
......@@ -121,187 +114,211 @@ namespace shpenc
}
// Remove duplicated pixels
std::sort(pixels.begin(), pixels.end());
auto it = std::unique(pixels.begin(), pixels.end());
PixelIterator it = std::unique(pixels.begin(), pixels.end());
pixels.resize(std::distance(pixels.begin(), it));
}
return pixels;
}
unsigned int ShapeEncoder::ToBBoxId(long unsigned int id, BoundingBox& bounding_box)
bool
ContourOperations::GetCollisionAtNorth(const long unsigned int idx,
const short * grid,
const unsigned int grid_width)
{
unsigned int img_row = id / m_Cols;
unsigned int img_col = id % m_Cols;
unsigned int bbox_row = img_row - bounding_box[1];
unsigned int bbox_col = img_col - bounding_box[0];
return bbox_row * bounding_box[2] + bbox_col;
}
long int tmp_idx = idx - grid_width;
long unsigned int ShapeEncoder::ToImgId(unsigned int id, BoundingBox& bounding_box)
{
unsigned int bbox_row = id / bounding_box[2];
unsigned int bbox_col = id % bounding_box[2];
unsigned int img_row = bbox_row + bounding_box[1];
unsigned int img_col = bbox_col + bounding_box[0];
if(grid[tmp_idx] > 0)
return true;
return img_row * m_Cols + img_col;
while(tmp_idx > 0)
{
if(grid[tmp_idx] > 0)
return true;
tmp_idx -= grid_width;
}
return false;
}
void ShapeEncoder::RemoveInternalPixels(std::vector<short>& matrix_bbox,
unsigned int bbox_rows,
unsigned int bbox_cols)
bool
ContourOperations::GetCollisionAtNorthEast(const long unsigned int idx,
const short * grid,
const unsigned int grid_width)
{
int mat_idx;
bool is_internal;
for(int r = 1; r<bbox_rows-1; r++)
long int tmp_idx = idx - grid_width + 1;
if(grid[tmp_idx] > 0)
return true;
while(tmp_idx > 0 && (tmp_idx % grid_width) != 0)
{
for(int c = 1; c<bbox_cols-1; c++)
{
mat_idx = r*bbox_cols+c;
is_internal = true;
if(matrix_bbox[mat_idx] == 1)
{
auto neighbors = mask::SquareNeighborhood(mat_idx, bbox_cols, bbox_rows);
for(short j=0; j<8; j++)
{
if(matrix_bbox[neighbors[j]] == 0)
{
is_internal = false;
break;
}
}
if(is_internal)
matrix_bbox[mat_idx] = 2;
}
}
if(grid[tmp_idx] > 0)
return true;
tmp_idx = tmp_idx - grid_width + 1;
}
for(int i = 0; i<bbox_rows*bbox_cols; i++)
if(matrix_bbox[i] == 2) matrix_bbox[i] = 0;
return false;
}
typename ShapeEncoder::Contour
ShapeEncoder::EncodeNewContour(long unsigned int curr_id,
BoundingBox& merged_bbox,
std::vector<short>& matrix_bbox)
bool
ContourOperations::GetCollisionAtEast(const long unsigned int idx,
const short * grid,