SEMModelEstimatorExample.cxxΒΆ
Example usage:
./SEMModelEstimatorExample Input/ROI_QB_MUL_4.tif Output/SEMClassif.png 4 40 5
Example source code (SEMModelEstimatorExample.cxx):
/*
* Copyright (C) 2005-2024 Centre National d'Etudes Spatiales (CNES)
* Copyright (C) 2007-2012 Institut Mines Telecom / Telecom Bretagne
*
* This file is part of Orfeo Toolbox
*
* https://www.orfeo-toolbox.org/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// In this example, we present OTB's implementation of SEM, through the class
// \doxygen{otb}{SEMClassifier}. This class performs a stochastic version
// of the EM algorithm, but instead of inheriting from
// \doxygen{itk}{ExpectationMaximizationMixtureModelEstimator}, we chose to
// inherit from \subdoxygen{itk}{Statistics}{ListSample},
// in the same way as \doxygen{otb}{SVMClassifier}.
//
// The program begins with \doxygen{otb}{VectorImage} and outputs
// \doxygen{otb}{Image}. Then appropriate header files have to be included:
#include <iostream>
#include "itkVector.h"
#include "itkVariableLengthVector.h"
#include "itkUnaryFunctorImageFilter.h"
#include "itkRescaleIntensityImageFilter.h"
#include "otbImage.h"
#include "otbVectorImage.h"
#include "otbImageFileReader.h"
#include "otbImageFileWriter.h"
// \doxygen{otb}{SEMClassifier} performs estimation of mixture to fit the
// initial histogram. Actually, mixture of Gaussian pdf can be performed.
// Those generic pdf are treated in
// \subdoxygen{otb}{Statistics}{ModelComponentBase}. The Gaussian model
// is taken in charge with the class
// \subdoxygen{otb}{Statistics}{GaussianModelComponent}.
#include "otbSEMClassifier.h"
int main(int argc, char* argv[])
{
try
{
if (argc != 6)
{
std::cerr << "Unsupervised Image Segmentation with SEM approach\n";
std::cerr << argv[0] << " imageIn imgClassif num_of_class ";
std::cerr << "nbIteration size_of_the_neighborhood\n";
return EXIT_FAILURE;
}
// Input/Output images type are define in a classical way.
// In fact, a \doxygen{itk}{VariableLengthVector} is to be
// considered for the templated \code{MeasurementVectorType}, which
// will be used in the \code{ListSample} interface.
using PixelType = double;
using ImageType = otb::VectorImage<PixelType, 2>;
using ReaderType = otb::ImageFileReader<ImageType>;
using OutputImageType = otb::Image<unsigned char, 2>;
using WriterType = otb::ImageFileWriter<OutputImageType>;
char* fileNameIn = argv[1];
char* fileNameImgInit = nullptr;
char* fileNameOut = argv[2];
int numberOfClasses = atoi(argv[3]);
int numberOfIteration = atoi(argv[4]);
int neighborhood = atoi(argv[5]);
double terminationThreshold = 1e-5;
ReaderType::Pointer reader = ReaderType::New();
reader->SetFileName(fileNameIn);
reader->Update();
// Once the input image is opened, the classifier may be initialised by
// \code{SmartPointer}.
using ClassifType = otb::SEMClassifier<ImageType, OutputImageType>;
ClassifType::Pointer classifier = ClassifType::New();
// Then, it follows, classical initializations of the pipeline.
classifier->SetNumberOfClasses(numberOfClasses);
classifier->SetMaximumIteration(numberOfIteration);
classifier->SetNeighborhood(neighborhood);
classifier->SetTerminationThreshold(terminationThreshold);
classifier->SetSample(reader->GetOutput());
// When an initial segmentation is available, the classifier may use it
// as image (of type \code{OutputImageType}) or as a
// \doxygen{itk}{SampleClassifier} result (of type
// \subdoxygen{itk}{Statistics}{MembershipSample}).
if (fileNameImgInit != nullptr)
{
using ImgInitReaderType = otb::ImageFileReader<OutputImageType>;
ImgInitReaderType::Pointer segReader = ImgInitReaderType::New();
segReader->SetFileName(fileNameImgInit);
segReader->Update();
classifier->SetClassLabels(segReader->GetOutput());
}
// By default, \doxygen{otb}{SEMClassifier} performs initialization of
// \code{ModelComponentBase} by as many instantiation of
// \subdoxygen{otb}{Statistics}{GaussianModelComponent} as the number of
// classes to estimate in the mixture. Nevertheless, the user may add specific
// distribution into the mixture estimation. It is permitted by the use of
// \code{AddComponent} for the given class number and the specific distribution.
std::cerr << "Explicit component initialization\n";
using ClassSampleType = ClassifType::ClassSampleType;
using GaussianType = otb::Statistics::GaussianModelComponent<ClassSampleType>;
for (int i = 0; i < numberOfClasses; ++i)
{
GaussianType::Pointer model = GaussianType::New();
classifier->AddComponent(i, model);
}
// Once the pipeline is instantiated. The segmentation by itself may be
// launched by using the \code{Update} function.
try
{
classifier->Update();
}
catch (itk::ExceptionObject& err)
{
std::cerr << "ExceptionObject caught in " << argv[0] << "!\n";
std::cerr << err << std::endl;
return -1;
}
// The segmentation may outputs a result of type
// \subdoxygen{itk}{Statistics}{MembershipSample} as it is the
// case for the \doxygen{otb}{SVMClassifier}. But when using
// \code{GetOutputImage} the output is directly an Image.
//
// Only for visualization purposes, we choose to rescale the image of
// classes before saving it to a file. We will use the
// \doxygen{itk}{RescaleIntensityImageFilter} for this purpose.
using RescalerType = itk::RescaleIntensityImageFilter<OutputImageType, OutputImageType>;
RescalerType::Pointer rescaler = RescalerType::New();
rescaler->SetOutputMinimum(itk::NumericTraits<unsigned char>::min());
rescaler->SetOutputMaximum(itk::NumericTraits<unsigned char>::max());
rescaler->SetInput(classifier->GetOutputImage());
WriterType::Pointer writer = WriterType::New();
writer->SetFileName(fileNameOut);
writer->SetInput(rescaler->GetOutput());
writer->Update();
// Figure \ref{fig:RESSEMCLASSIF} shows the result of the SEM segmentation
// with 4 different classes and a contextual neighborhood of 3 pixels.
// \begin{figure}
// \center
// \includegraphics[width=0.6\textwidth]{SEMClassif.eps}
// \itkcaption[SEM Classification results]{SEM Classification results.}
// \label{fig:RESSEMCLASSIF}
// \end{figure}
//
// As soon as the segmentation is performed by an iterative stochastic
// process, it is worth verifying the output status: does the segmentation
// ends when it has converged or just at the limit of the iteration numbers.
std::cerr << "Program terminated with a ";
if (classifier->GetTerminationCode() == ClassifType::CONVERGED)
std::cerr << "converged ";
else
std::cerr << "not-converged ";
std::cerr << "code...\n";
// The text output gives for each class the parameters of the pdf (e.g. mean
// of each component of the class and there covariance matrix, in the case of a
// Gaussian mixture model).
classifier->Print(std::cerr);
}
catch (itk::ExceptionObject& err)
{
std::cerr << "Exception itk::ExceptionObject thrown !\n";
std::cerr << err << std::endl;
return EXIT_FAILURE;
}
catch (...)
{
std::cerr << "Unknown exception thrown !\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}