OpenCV - How to apply Kmeans on a grayscale image? - c++

I am trying to cluster a grayscale image using Kmeans.
First, I have a question:
Is Kmeans the best way to cluster a Mat or are there newer more efficient approaches?
Second, when I try this:
Mat degrees = imread("an image" , IMREAD_GRAYSCALE);
const unsigned int singleLineSize = degrees.rows * degrees.cols;
Mat data = degrees.reshape(1, singleLineSize);
data.convertTo(data, CV_32F);
std::vector<int> labels;
cv::Mat1f colors;
cv::kmeans(data, 3, labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 10, 1.), 2, cv::KMEANS_PP_CENTERS, colors);
for (unsigned int i = 0; i < singleLineSize; i++) {
data.at<float>(i) = colors(labels[i]);
}
Mat outputImage = data.reshape(1, degrees.rows);
outputImage.convertTo(outputImage, CV_8U);
imshow("outputImage", outputImage);
The result (outputImage) is empty.
When I try to multiply colors in the for loop like data.at<float>(i) = 255 * colors(labels[i]);
I get this error:
Unhandled exception : Integer division by zero.
How can I cluster a grayscale image properly?

It looks to me that you are wrongly parsing the labels and colors info to your output matrix.
K-means returns this info:
Labels - This is an int matrix with all the cluster labels. It is a "column" matrix of size TotalImagePixels x 1.
Centers - This what you refer to as "Colors". This is a float matrix that contains the cluster centers. The matrix is of size
NumberOfClusters x featureMean.
In this case, as you are using BGR pixels as "features" consider that Centers has 3 columns: One mean for the B channel, one mean for the G channel and finally, a mean for the R channel.
So, basically you loop through the (plain) label matrix, retrieve the label, use this value as index in the Centers matrix to retrieve the 3 colors.
One way to do this is as follows, using the auto data specifier and looping through the input image instead (that way we can index each input label easier):
//prepare an empty output matrix
cv::Mat outputImage( inputImage.size(), inputImage.type() );
//loop thru the input image rows...
for( int row = 0; row != inputImage.rows; ++row ){
//obtain a pointer to the beginning of the row
//alt: uchar* outputImageBegin = outputImage.ptr<uchar>(row);
auto outputImageBegin = outputImage.ptr<uchar>(row);
//obtain a pointer to the end of the row
auto outputImageEnd = outputImageBegin + outputImage.cols * 3;
//obtain a pointer to the label:
auto labels_ptr = labels.ptr<int>(row * inputImage.cols);
//while the end of the image hasn't been reached...
while( outputImageBegin != outputImageEnd ){
//current label index:
int const cluster_idx = *labels_ptr;
//get the center of that index:
auto centers_ptr = centers.ptr<float>(cluster_idx);
//we got an implicit VEC3B vector, we must map the BGR items to the
//output mat:
clusteredImageBegin[0] = centers_ptr[0];
clusteredImageBegin[1] = centers_ptr[1];
clusteredImageBegin[2] = centers_ptr[2];
//increase the row "iterator" of our matrices:
clusteredImageBegin += 3; ++labels_ptr;
}
}

Related

OpenCV: output image is blue

so i'm making this project where i'm making the reflection of an image on OpenCV (without using the flip function), and the only problem (i think) to finish it, is that the image that is suppose to come out reflected, is coming out as all blue.
The code i have (i took out the usual part, the problem should be around here):
Mat imageReflectionFinal = Mat::zeros(Size(220,220),CV_8UC3);
for(unsigned int r=0; r<221; r++)
for(unsigned int c=0; c<221; c++) {
Vec3b intensity = image.at<Vec3b>(r,c);
imageReflectionFinal.at<Vec3b>(r,c) = (uchar)(c, -r + (220)/2);
}
///displays images
imshow( "Original Image", image );
imshow("Reflected Image", imageReflectionFinal);
waitKey(0);
return 0;
}
There are some problems with your code. As pointed out, your iteration variables go beyond the actual image dimensions. Do not use hardcoded bounds, you can use inputImage.cols and inputImage.rows instead to obtain the image dimensions.
There’s the variable (a BGR Vec3b) that is set but not used - Vec3b intensity = image.at<Vec3b>(r,c);
Most importantly, it is not clear what you are trying to achieve. The line (uchar)(c, -r + (220)/2); does not give out much info. Also, which direction are you flipping the original image around? X or Y axis?
Here’s a possible solution to flip your image in the X direction:
//get input image:
cv::Mat testMat = cv::imread( "lena.png" );
//Get the input image size:
int matCols = testMat.cols;
int matRows = testMat.rows;
//prepare the output image:
cv::Mat imageReflectionFinal = cv::Mat::zeros( testMat.size(), testMat.type() );
//the image will be flipped around the x axis, so the "target"
//row will start at the last row of the input image:
int targetRow = matRows-1;
//loop thru the original image, getting the current pixel value:
for( int r = 0; r < matRows; r++ ){
for( int c = 0; c < matCols; c++ ) {
//get the source pixel:
cv::Vec3b sourcePixel = testMat.at<cv::Vec3b>( r , c );
//source and target columns are the same:
int targetCol = c;
//set the target pixel
imageReflectionFinal.at<cv::Vec3b>( targetRow , targetCol ) = sourcePixel;
}
//for every iterated source row, decrease the number of
//target rows, as we are flipping the pixels in the x dimension:
targetRow--;
}
Result:

image.rows and image.cols are less than real image size (c++)

I am learning open cv, I wrote a code which is supposed to add salt and paper noise to the image(it turns some random pixel's value to 255), my image size is 256256. The problem is I can't access to all pixels and the code just changes half of the image's pixels. For example, my image size as I said is 256256 and when I change pixel value which is located in 256*256 it changes the center image's pixel.
inline cv::Mat salt(cv::Mat, int); //my function
int main()
{
int present, imagepixel;
cv::Mat image = cv::imread("sample.jpg");
imagepixel = image.rows * image.cols;
std::cout << "enter the value of salt and paper :";
std::cin >> percent;
present = (present * imagepixel) / 100; //image pixel is number of image's pixels
if (image.empty())
{
std::cout << "image is not valid" << std::endl;
}
image = salt(image, present);
cv::imshow("salt&paper", image);
cv::waitKey(0);
return 0;
}
cv::Mat salt(cv::Mat image, int present)
{
int row, col, j = 0;
srand(time(NULL));
for (int i = 0; i < present; i++)
{
row = rand() % image.rows;
col = rand() % image.cols;
image.at<unsigned char>(row, col) = 255;
}
return image;
}
Why does the image have more rows and columns than calculated with function like image.rows and image.cols (I give the value to image, in debugging mode and image, had value out of image.rows and image.cols range)?
Why is the output set to unsigned char? Why can't the value of pixels be an integer? (I supposed that inside image.at<unsigned char>, <unsigned char> is output kind (int, char, float))
Why can't this code access all pixels? (screenshot has added look at the output)
Second argument of imread specifies how an image is read. Default value is IMREAD_COLOR what means that:
In the case of color images, the decoded images will have the channels
stored in B G R order. (from
reference)
so your result mat has 256x256 resolution and each pixel is described by 3 values: to store Blue,Green and Red component of space color. These components are stored on uchar type (range is [0,255)).
2D images in OpenCV are stored row-by-row. The formula below can be used to calculate address of M(row,col) element:
addr(M[row,col]) = data + step1 * row + step2 * col
where data is first byte of array where the data of image is stored. Your mat has 3 channels and image's width is 256, so step1 equals to 256 * 3 (it is number of columns in row multiplied by number of channels per pixel) and step2 is 3.
Now, let's open source code of at member function:
template<typename _Tp> inline
_Tp& Mat::at(int i0, int i1) {
return ((_Tp*)(data + step.p[0] * i0))[i1];
}
// i0 means row, i1 means col
in your code you specify _Tp as uchar. Now just rules of pointer arithmetic work which cause you can access only 1/3 of input image. For example when you call <uchar>(0,1) you are accessing Green component of (0,0) pixel.
To resolve your issue you have to pass Vec3b in template argument list:
image.at<cv::Vec3b>(row, col) = cv::Vec3b(255,255,255);
(each component of Vec3b has uchar type).

OpenCV - remap() - getting black pixels

I'm using an STMap to map a .jpg image using remap().
I loaded my STMap, split the channels and converted each channel matrix to CV_32FC1.
I checked them and it worked - each matrix displays correctly and all of its values are between 0.0 and 1.0.
However, when i try to use the remap() function:
Mat dst;
remap(image4, dst,map_x,map_y,INTER_LINEAR,BORDER_CONSTANT,Scalar(0,0,0));
imshow( "Result", dst );
It just displays a black image.
image4 = my .jpg image
map_x = grayscale CV_32FC1 (red channel
of the original STMap)
map_y = grayscale CV_32FC1 (green channel
of the original STMap)
What could be the problem?
Thanks!
Black image when using cv::remap is due to using offsets instead of absolute locations in the passed map(s).
Optical flow algorithms usually export motion vectors, not absolute positions, whereas cv::remap expects the absolute coordinate (subpixel) to sample from.
To convert between the two, starting with a CV_32FC2 flow matrix we can do something like this:
// Convert from offsets to absolute locations.
Mat mapx(flow.size(), CV_32FC1);
Mat mapy(flow.size(), CV_32FC1);
for (int row = 0; row < flow.rows; row++)
{
for (int col = 0; col < flow.cols; col++)
{
Point2f f = flow.at<Point2f>(row, col);
mapx.at<float>(row, col) = col + f.x;
mapy.at<float>(row, col) = row + f.y;
}
}
Then mapx and mapy can be used in remap.

Assign 3x1 mat to 3 channels mat

This question is continuance from my question in this link. After i get mat matrix, the 3x1 matrix is multiplied with 3x3 mat matrix.
for (int i = 0; i < im.rows; i++)
{
for (int j = 0; j < im.cols; j++)
{
for (int k = 0; k < nChannels; k++)
{
zay(k) = im.at<Vec3b>(i, j)[k]; // get pixel value and assigned to Vec4b zay
}
//convert to mat, so i can easily multiplied it
mat.at <double>(0, 0) = zay[0];
mat.at <double>(1, 0) = zay[1];
mat.at <double>(2, 0) = zay[2];
We get 3x1 mat matrix and do multiplication with the filter.
multiply= Filter*mat;
And i get mat matrix 3x1. I want to assign the value into my new 3 channels mat matrix, how to do that? I want to construct an images using this operation. I'm not use convolution function, because i think the result is different. I'm working in c++, and i want to change the coloured images to another color using matrix multiplication. I get the algorithm from this paper. In that paper, we need to multiplied several matrix to get the result.
OpenCV gives you a reshape function to change the number of channels/rows/columns implicitly:
http://docs.opencv.org/modules/core/doc/basic_structures.html#mat-reshape
This is very efficient since no data is copied, only the matrix header is changed.
try:
cv::Mat mat3Channels = mat.reshape(3,1);
Didn't test it, but should work. It should give you a 1x1 matrix with 3 channel element (Vec3d) if you want a Vec3b element instead, you have to convert it:
cv::Mat mat3ChannelsVec3b;
mat3Channels.convertTo(mat3ChannelsVec3b, CV_8UC3);
If you just want to write your mat back, it might be better to create a single Vec3b element instead:
cv::Vec3b element3Channels;
element3Channels[0] = multiply.at<double>(0,0);
element3Channels[1] = multiply.at<double>(1,0);
element3Channels[2] = multiply.at<double>(2,0);
But care in all cases, that Vec3b elements can't save values < 0 and > 255
Edit: After reading your question again, you ask how to assign...
I guess you have another matrix:
cv::Mat outputMatrix = cv::Mat(im.rows, im.cols, CV_8UC3, cv::Scalar(0,0,0));
Now to assign multiply to the element in outputMatrix you ca do:
cv::Vec3b element3Channels;
element3Channels[0] = multiply.at<double>(0,0);
element3Channels[1] = multiply.at<double>(1,0);
element3Channels[2] = multiply.at<double>(2,0);
outputMatrix.at<Vec3b>(i, j) = element3Channels;
If you need alpha channel too, you can adapt that easily.

OpenCV, noise when mapping a a patch of pixels from image to image

I am copying a patch of pixels from one image to another and as a result I am not getting a 1:1 mapping but the new image intensities differ by 1 or 2 itensity-levels from the source image.
Do you know what could be causing this?
This is the code:
void templateCut ( IplImage* ptr2Img, IplImage* tempCut, CvBox2D* boundingBox )
{
/* Upper left corner of target's BB */
int col1 = (int)boundingBox->center.x;
int row1 = (int)boundingBox->center.y;
for(int i=0; i<tempCut->height; i++)
{
/* Pointer to a row */
uchar * ptrImgBB = (uchar*)( ptr2Img->imageData + (row1+i)*ptr2Img->widthStep + col1 );
uchar * ptrTemp = (uchar*)( tempCut->imageData + i*tempCut->widthStep );
for(int i2=0; i2<tempCut->width; i2++)
{
*ptrTemp++ = (*ptrImgBB++);
}
}
}
Is it a single channel image or multiple-channel image (such as RGB)? If it is a multiple-channel image, you have to consider the channel index in your loop.
btw: OpenCV supports region of interest (ROI) which will be convenient for you to implement copying a sub-region of an image. Below is the link you can find information on ROI usage in OpenCV.
http://nashruddin.com/OpenCV_Region_of_Interest_(ROI)