module demonstrating the usage of k-means algorithm on a 2d image
More...
module demonstrating the usage of k-means algorithm on a 2d image
Overview
This application classifies the pixels of a given 2d image using K-means algorithm. It also involves, among others, median2dImg, standardizeImg and lawTexture2dImg algorithms
Usage
This is a standalone script file which takes no input argument.
Here is a snapshot of input image used for the following example :
Problem statement
Having a quick look at the input image, the human eye clearly distinguishes 4 distinct types of regions (or classes):
- class #1: the background, dark and textured
- class #2: two bright and textured regions
- class #3: 2 dark and homogeneous regions
- class #4: 1 bright and homogeneous region
However, distinguishing these 4 classes using the k-means algorithm is more complicated than expected: if we simply provide as input of the k-means algorithm the original image, we obtain an image of classes that if far from the expected result (see image below).
image of classes resulting from k-means application using only the image of intensities as input
There are 2 problems with the current input image that prevent us from relying only on the information of intensity:
- means of intensities are equal between class #1 and class #3, and between class #2 and class #4
- standard deviations of intensities in classes #1 and #2 (resp. in classes #3 and #4) are so great that some pixels in class #1 (resp. class #3] have the same intensity that some pixels and class #2 (resp. class #4].
In fact, to do the classification, the human eye implicitly tries to agglomerate neighbouring pixels that share the same properties. In this case, common properties are:
- intensities
- local texture
Using k-means, we can at least rely on the the cunjunction of these 2 properties, provided we feed the algorithm with the appropriate images:
first, extract local texture information using lawTexture2dImg. In our case, the
image seems to give the bests results (see image below, we clearly distinguish the 2 types of textures - classes #1 and #2 on one hand, classes #3 and #4 on the other hand)
R5R5 Law's texture energy map associated to input image
- then, unnoise the input image using the median2dImg filter, to slightly decrease the standard deviation of intensities of each class
- concatenate the unnoised image of intensities with the image resulting from the lawTexture2dImg filter application in a temporal sequence image
- standardize the sequence image, so that the intensity information and the local texture information have the same weight
- pass the standardize sequence image as input of k-means algorithm.
The result is closer from what we can expect, even if it's still not perfect (pixels on the boundary of regiton associated to class #4 are not correctly classified), but some filters applied as post-process (morphologic opening, for instance) can improve the result.
image of classes resulting from sample application execution
Source code documentation
Start by including all the necessary header files:
#include <IPSDKCore/Config/LibraryInitializer.h>
#include <IPSDKIPL/IPSDKIPLClassification/Processor/KMeansImg/KMeansImg.h>
#include <IPSDKBaseProcessing/Logger/IPSDKBaseProcessingException.h>
#include <IPSDKImage/Geometry/BaseImageGeometry.h>
#include <IPSDKImage/Image/Memory/MemoryImage.h>
#include <IPSDKImageFile/Logger/IPSDKImageFileException.h>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/convenience.hpp>
#include <log4cplus/consoleappender.h>
#include <iostream>
In the main function body, we start by asking to display all the log messages generated by IPSDK libraries and by our application itself to the application console:
int
main(
int argc,
char* argv[])
{
log4cplus::SharedAppenderPtr pConsole(new log4cplus::ConsoleAppender);
log4cplus::Logger::getRoot().addAppender(pConsole);
log4cplus::Logger::getRoot().setLogLevel(log4cplus::INFO_LOG_LEVEL);
Next, we initialize the IPSDK environment:
case ipsdk::core::eLibInitStatus::eLIS_Warn:
break;
case ipsdk::core::eLibInitStatus::eLIS_Failed:
return -1;
break;
default:
break;
}
Then we declare objects 'inImgFilePath' and 'outImgFilePath':
boost::filesystem::path inImgFilePath =
"Unequalized_Hawkes_Bay_NZ.tif";
boost::filesystem::path outImgFilePath =
And we load our input image from the associated TIFF file, by calling the function ipsdk::image::file::loadTiffImageFile.
try {
pInImg =
} catch(const image::file::IPSDKImageFileException& e) {
% inImgFilePath.string() % e.getMsg());
return -1;
}
We then call the median 2d filter, to unnoise the input image.
try {
} catch(const processor::IPSDKBaseProcessingException& e) {
% e.getMsg());
return -1;
}
To extract local texture information, we apply Law's texture 2D filter to the input image
pKernelTypes->setValue<attr::LawTextureKernel2dTypes::FlagL5E5_E5L5>(false);
pKernelTypes->setValue<attr::LawTextureKernel2dTypes::FlagL5S5_S5L5>(false);
pKernelTypes->setValue<attr::LawTextureKernel2dTypes::FlagL5R5_R5L5>(false);
pKernelTypes->setValue<attr::LawTextureKernel2dTypes::FlagE5E5>(false);
pKernelTypes->setValue<attr::LawTextureKernel2dTypes::FlagE5S5_S5E5>(false);
pKernelTypes->setValue<attr::LawTextureKernel2dTypes::FlagE5R5_R5E5>(false);
pKernelTypes->setValue<attr::LawTextureKernel2dTypes::FlagS5S5>(false);
pKernelTypes->setValue<attr::LawTextureKernel2dTypes::FlagS5R5_R5S5>(false);
try {
}
catch (const image::file::IPSDKImageFileException& e) {
% e.getMsg());
return -1;
}
Next, we concatenate intensities and local texture images into a temporal sequence image
try {
}
catch (const image::file::IPSDKImageFileException& e) {
% e.getMsg());
return -1;
}
The sequence is then standardized, to give the same weight to intensities and to local texture information
try {
}
catch (const image::file::IPSDKImageFileException& e) {
% e.getMsg());
return -1;
}
We are now able to launch k-means classifier on the standardized sequence, to classify our image into 4 classes
boost::shared_ptr<MemoryImage> pOutImg = boost::make_shared<MemoryImage>();
pOutImg->init(pInImg->getGeometry());
try {
}
catch (const image::file::IPSDKImageFileException& e) {
% e.getMsg());
return -1;
}
The output image is then saved to the TIFF file specified in object "outImgFilePath":
try {
} catch(const image::file::IPSDKImageFileException& e) {
% outImgFilePath % e.getMsg());
return -1;
}
Finally, we clean IPSDK environment and exit:
See the full source listing