Detecting areas of differing colors using OpenCV? - c++

Let's say I have the following image:
I want to be able to detect each one of the white squares amongst the black background.
I would also like to be able to acquire the center & size of each one.
I'm attempting to use the opencv library in C++ in order to achieve this. I'm doing this on a Raspberry Pi, with it's standard OS. I installed the latest opencv library by running the sudo apt-get install libopencv-dev command on the terminal. I don't have any prior experience with opencv or any of its libraries.
I understand that one can load an image, in the form of it's pixel data, in a variable of type cv::Mat. I understand how to access each pixel inside the cv::Mat variable, and I understand how to get the byte value for each pixel.
What I'm looking for now is a function or functions that would allow me to detect each one of the white squares, and give me their center positions on the image and their sizes.
Is what I'm trying to achieve even possible with opencv in C++? Is there a function that does what I'm looking for?

This is a basic operation of image processing. It is described in the OpenCV documentation.
Here's a short bit of code. Please excuse the use of Python. This looks pretty much identical in C++. All the numpy arrays are just cv::Mat objects then.
im = cv.imread("ySqre.png", cv.IMREAD_GRAYSCALE)
(nlabels, labelmap, stats, centroids) = cv.connectedComponentsWithStats(im)
And the results:
# NOTE: background is counted as a label too, with label index 0
# nlabels: 6 (5 + background)
# centroids:
# array([[320.17971, 160.72618], # this is background, ignore it
# [ 84.5 , 56.5 ],
# [288.5 , 88.5 ],
# [437. , 140. ],
# [185. , 222.5 ],
# [527.5 , 237.5 ]])
# for the sizes, inspect the `stats` array.
# Use cv.CC_STAT_WIDTH and CC_STAT_HEIGHT for indices into the array.
# There is also the top left corner and the area in number of pixels.
# stats:
# array([[ 0, 0, 640, 320, 190359], # background, ignore
# [ 48, 39, 74, 36, 2664],
# [ 274, 62, 30, 54, 1620],
# [ 412, 81, 51, 119, 6069],
# [ 159, 195, 53, 56, 2968],
# [ 514, 218, 28, 40, 1120]], dtype=int32)

If you can assume the detection target area is white and its shape is (non-rotated) rectangle, simply doing labeling will achieve your detection (because of the assumption : all found white region should be rectangle).
You say you can access pixel data, so, you can implement simple labeling process for this purpose.
Very simple sample code:
#include <iostream>
#include <vector>
struct HorizontalLineSeg
{
int left, right, y;
HorizontalLineSeg( int left=0, int right=0, int y=0 )
: left(left), right(right), y(y)
{}
};
struct RectReg
{
HorizontalLineSeg Top, Bottom;
RectReg( const HorizontalLineSeg &Top ) : Top(Top),Bottom(Top) {}
bool ConnectToBottom( const HorizontalLineSeg &Seg )
{
if( Bottom.y+1 != Seg.y )return false;
if( Seg.right < Bottom.left || Bottom.right < Seg.left )return false;
Bottom = Seg;
return true;
}
};
void Update( std::vector<RectReg> &Regs, const HorizontalLineSeg &FoundSeg )
{
for( auto &Reg : Regs )
{
if( Reg.ConnectToBottom( FoundSeg ) )return;
}
Regs.emplace_back( FoundSeg);
}
int main()
{
//- Load Image as GrayScale
cv::Mat Img = cv::imread( "WhiteRects.png", cv::IMREAD_GRAYSCALE );
if( Img.empty() ){ std::cout << "imread() failed" << std::endl; return 0; }
//- Find white regions
std::vector<RectReg> Regs;
{
const unsigned char Thresh = 128;
for( int y=0; y<Img.rows; ++y )
{
const unsigned char *p = Img.ptr<unsigned char>( y );
int FoundLeft = -1;
for( int x=0; x<Img.cols; ++x, ++p )
{
if( *p >= Thresh )
{
if( FoundLeft<0 )FoundLeft = x;
}
else if( FoundLeft >= 0 )
{
Update( Regs, HorizontalLineSeg( FoundLeft, x-1, y ) );
FoundLeft = -1;
}
}
}
}
//- Visualize result
cv::Mat ShowImg = Img * 0.35;
if( !Regs.empty() )
{
std::vector< std::vector<cv::Point> > Pts;
Pts.reserve( Regs.size() );
for( const auto &Reg : Regs )
{
Pts.push_back(
{
{ Reg.Top.left, Reg.Top.y },
{ Reg.Top.right, Reg.Top.y },
{ Reg.Bottom.right, Reg.Bottom.y },
{ Reg.Bottom.left, Reg.Bottom.y }
}
);
}
cv::polylines( ShowImg, Pts, true, cv::Scalar(255) );
}
std::cout << Regs.size() << " regs found" << std::endl;
cv::imshow( "Result", ShowImg );
cv::waitKey();
return 0;
}
Added code that uses OpenCV functions because it seems to be criticized for not using OpenCV functions.
int main()
{
//- Load Image as GrayScale
cv::Mat Img = cv::imread( "WhiteRects.png", cv::IMREAD_GRAYSCALE );
if( Img.empty() ){ std::cout << "imread() failed" << std::endl; return 0; }
//- Main process with cv::connectedComponentsWithStats()
cv::Mat Stats;
int N = 0;
{
//- Pre-Binalize with OpenCV's function.
// Purpose of this is just to set the background value to 0.
// So, if you know the backgroud value is already 0, you can omit this step.
cv::Mat BinImg;
cv::threshold( Img, BinImg, 128, 255, cv::THRESH_BINARY );
//- Use connectedComponentsWithStats()
cv::Mat Labels; //Only needed to use the function.
cv::Mat Centroids; //Only needed to use the function. Or you can use this if you want.
N = cv::connectedComponentsWithStats( BinImg, Labels, Stats, Centroids );
}
//- Visualize result
cv::Mat ShowImg = Img * 0.35;
for( int i=1; i<N; ++i ) //be careful not to include 0
{
const int *pS = Stats.ptr<int>( i );
cv::rectangle(
ShowImg,
cv::Rect(
cv::Point{ pS[ cv::CC_STAT_LEFT ], pS[ cv::CC_STAT_TOP ] },
cv::Size{ pS[ cv::CC_STAT_WIDTH ], pS[ cv::CC_STAT_HEIGHT ] }
),
cv::Scalar(255)
);
}
std::cout << N-1 << " regs found" << std::endl; //not N
cv::imshow( "Result", ShowImg );
cv::waitKey();
return 0;
}

Related

Resizing an image using opencv c++ maintaining aspect ratio [duplicate]

Is there a way of resizing images of any shape or size to say [500x500] but have the image's aspect ratio be maintained, levaing the empty space be filled with white/black filler?
So say the image is [2000x1000], after getting resized to [500x500] making the actual image itself would be [500x250], with 125 either side being white/black filler.
Something like this:
Input
Output
EDIT
I don't wish to simply display the image in a square window, rather have the image changed to that state and then saved to file creating a collection of same size images with as little image distortion as possible.
The only thing I came across asking a similar question was this post, but its in php.
Not fully optimized, but you can try this:
EDIT handle target size that is not 500x500 pixels and wrapping it up as a function.
cv::Mat GetSquareImage( const cv::Mat& img, int target_width = 500 )
{
int width = img.cols,
height = img.rows;
cv::Mat square = cv::Mat::zeros( target_width, target_width, img.type() );
int max_dim = ( width >= height ) ? width : height;
float scale = ( ( float ) target_width ) / max_dim;
cv::Rect roi;
if ( width >= height )
{
roi.width = target_width;
roi.x = 0;
roi.height = height * scale;
roi.y = ( target_width - roi.height ) / 2;
}
else
{
roi.y = 0;
roi.height = target_width;
roi.width = width * scale;
roi.x = ( target_width - roi.width ) / 2;
}
cv::resize( img, square( roi ), roi.size() );
return square;
}
A general approach:
cv::Mat utilites::resizeKeepAspectRatio(const cv::Mat &input, const cv::Size &dstSize, const cv::Scalar &bgcolor)
{
cv::Mat output;
double h1 = dstSize.width * (input.rows/(double)input.cols);
double w2 = dstSize.height * (input.cols/(double)input.rows);
if( h1 <= dstSize.height) {
cv::resize( input, output, cv::Size(dstSize.width, h1));
} else {
cv::resize( input, output, cv::Size(w2, dstSize.height));
}
int top = (dstSize.height-output.rows) / 2;
int down = (dstSize.height-output.rows+1) / 2;
int left = (dstSize.width - output.cols) / 2;
int right = (dstSize.width - output.cols+1) / 2;
cv::copyMakeBorder(output, output, top, down, left, right, cv::BORDER_CONSTANT, bgcolor );
return output;
}
Alireza's answer is good, however I modified the code slightly so that I don't add the vertical borders when the image fits vertically and I don't add horizontal borders when the image fits horizontally (this is closer to the original request):
cv::Mat utilites::resizeKeepAspectRatio(const cv::Mat &input, const cv::Size &dstSize, const cv::Scalar &bgcolor)
{
cv::Mat output;
// initially no borders
int top = 0;
int down = 0;
int left = 0;
int right = 0;
if( h1 <= dstSize.height)
{
// only vertical borders
top = (dstSize.height - h1) / 2;
down = top;
cv::resize( input, output, cv::Size(dstSize.width, h1));
}
else
{
// only horizontal borders
left = (dstSize.width - w2) / 2;
right = left;
cv::resize( input, output, cv::Size(w2, dstSize.height));
}
return output;
}
You can create another image of the square size you wish, then put your image in the middle of the square image. Something like this:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "opencv2/imgproc/imgproc.hpp"
int main(int argc, char *argv[])
{
// read an image
cv::Mat image1= cv::imread("/home/hdang/Desktop/colorCode.png");
//resize it
cv::Size newSize = cv::Size(image1.cols/2,image1.rows/2);
cv::resize(image1, image1, newSize, 0, 0, cv::INTER_LINEAR);
//create the square container
int dstWidth = 500;
int dstHeight = 500;
cv::Mat dst = cv::Mat(dstHeight, dstWidth, CV_8UC3, cv::Scalar(0,0,0));
//Put the image into the container, roi is the new position
cv::Rect roi(cv::Rect(0,dst.rows*0.25,image1.cols,image1.rows));
cv::Mat targetROI = dst(roi);
image1.copyTo(targetROI);
//View the result
cv::namedWindow("OpenCV Window");
cv::imshow("OpenCV Window", dst);
// wait key for 5000 ms
cv::waitKey(5000);
return 0;
}
I extended alireza answer to allow a zero allocation answer.
Allow user to give a preallocated, or a cv::Mat as input
cv::resize input image immediatly to output mat
Color top and bottom box with cv::rectangle
#include <opencv2/imgproc.hpp>
void resizeKeepAspectRatio(const cv::Mat& src, cv::Mat& dst, const cv::Size& dstSize, const cv::Scalar& backgroundColor = {})
{
// Don't handle anything in this corner case
if(dstSize.width <= 0 || dstSize.height <= 0)
return;
// Not job is needed here, let's avoid any copy
if(src.cols == dstSize.width && src.rows == dstSize.height)
{
dst = src;
return;
}
// Try not to reallocate memory if possible
cv::Mat output = [&]()
{
if(dst.data != src.data && dst.cols == dstSize.width && dst.rows == dstSize.height && dst.type() == src.type())
return dst;
return cv::Mat(dstSize.height, dstSize.width, src.type());
}();
// 'src' inside 'dst'
const auto imageBox = [&]()
{
const auto h1 = int(dstSize.width * (src.rows / (double)src.cols));
const auto w2 = int(dstSize.height * (src.cols / (double)src.rows));
const bool horizontal = h1 <= dstSize.height;
const auto width = horizontal ? dstSize.width : w2;
const auto height = horizontal ? h1 : dstSize.height;
const auto x = horizontal ? 0 : int(double(dstSize.width - width) / 2.);
const auto y = horizontal ? int(double(dstSize.height - height) / 2.) : 0;
return cv::Rect(x, y, width, height);
}();
cv::Rect firstBox;
cv::Rect secondBox;
if(imageBox.width > imageBox.height)
{
// ┌──────────────► x
// │ ┌────────────┐
// │ │┼┼┼┼┼┼┼┼┼┼┼┼│ firstBox
// │ x────────────►
// │ │ │
// │ ▼────────────┤
// │ │┼┼┼┼┼┼┼┼┼┼┼┼│ secondBox
// │ └────────────┘
// ▼
// y
firstBox.x = 0;
firstBox.width = dstSize.width;
firstBox.y = 0;
firstBox.height = imageBox.y;
secondBox.x = 0;
secondBox.width = dstSize.width;
secondBox.y = imageBox.y + imageBox.height;
secondBox.height = dstSize.height - secondBox.y;
}
else
{
// ┌──────────────► x
// │ ┌──x──────►──┐
// │ │┼┼│ │┼┼│
// │ │┼┼│ │┼┼│
// │ │┼┼│ │┼┼│
// │ └──▼──────┴──┘
// ▼ firstBox secondBox
// y
firstBox.y = 0;
firstBox.height = dstSize.height;
firstBox.x = 0;
firstBox.width = imageBox.x;
secondBox.y = 0;
secondBox.height = dstSize.height;
secondBox.x = imageBox.x + imageBox.width;
secondBox.width = dstSize.width - secondBox.x;
}
// Resizing to final image avoid useless memory allocation
cv::Mat outputImage = output(imageBox);
assert(outputImage.cols == imageBox.width);
assert(outputImage.rows == imageBox.height);
const auto* dataBeforeResize = outputImage.data;
cv::resize(src, outputImage, cv::Size(outputImage.cols, outputImage.rows));
assert(dataBeforeResize == outputImage.data);
const auto drawBox = [&](const cv::Rect& box)
{
if(box.width > 0 && box.height > 0)
{
cv::rectangle(output, cv::Point(box.x, box.y), cv::Point(box.x + box.width, box.y + box.height), backgroundColor, -1);
}
};
drawBox(firstBox);
drawBox(secondBox);
// Finally copy output to dst, like that user can use src & dst to the same cv::Mat
dst = output;
}
With this function, dst mat can be reused without any reallocation.
cv::Mat src(200, 100, CV_8UC3, cv::Scalar(1,100,200));
cv::Size dstSize(300, 400)
cv::Mat dst;
resizeKeepAspectRatio(src, dst, dstSize); // dst get allocated
resizeKeepAspectRatio(src, dst, dstSize); // dst get reused

Unknown pooling method when testing caffe with cuda but not cudnn

I built the caffe deep learning library in windows as shown in this link:
https://initialneil.wordpress.com/2015/07/15/caffe-vs2013-opencv-in-windows-tutorial-i/
I deactivated the cuDNN because my nvidia card didnot support this and changed the targert architecture to fermi architecture.
I built caffe as static library to use it in the test project shown below:
int main(int argc, char** argv)
{
// get a testing image and display
Mat img = imread(CAFFE_ROOT + "/examples/images/mnist_5.png");
cvtColor(img, img, CV_BGR2GRAY);
imshow("img", img);
waitKey(1);
// Set up Caffe
Caffe::set_mode(Caffe::GPU);
int device_id = 0;
Caffe::SetDevice(device_id);
LOG(INFO) << "Using GPU";
// Load net
Net<float> net(CAFFE_ROOT + "/examples/mnist/lenet_test-memory-1.prototxt");
string model_file = CAFFE_ROOT + "/examples/mnist/lenet_iter_10000.caffemodel";
net.CopyTrainedLayersFrom(model_file);
// set the patch for testing
vector<Mat> patches;
patches.push_back(img);
// push vector<Mat> to data layer
float loss = 0.0;
boost::shared_ptr<MemoryDataLayer<float> > memory_data_layer;
memory_data_layer = boost::static_pointer_cast<MemoryDataLayer<float>>(net.layer_by_name("data"));
vector<int> labels(patches.size());
memory_data_layer->AddMatVector(patches, labels);
// Net forward
//ERROR IN THE LINE BELOW
const vector<Blob<float>*> & results = net.ForwardPrefilled(&loss);// HERE THE ERROR
float *output = results[1]->mutable_cpu_data();
// Display the output
for (int i = 0; i < 10; i++) {
printf("Probability to be Number %d is %.3f\n", i, output[i]);
}
waitKey(0);
}
But I get an error when accessing the file: pooling_layer.cu in the function described below:
template <typename Dtype>
void PoolingLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
vector<Blob<Dtype>*>* top) {
const Dtype* bottom_data = bottom[0]->gpu_data();
Dtype* top_data = (*top)[0]->mutable_gpu_data();
int count = (*top)[0]->count();
// We'll output the mask to top[1] if it's of size >1.
const bool use_top_mask = top->size() > 1;
int* mask = NULL;
Dtype* top_mask = NULL;
switch (this->layer_param_.pooling_param().pool()) {
case PoolingParameter_PoolMethod_MAX:
if (use_top_mask) {
top_mask = (*top)[1]->mutable_gpu_data();
} else {
mask = max_idx_.mutable_gpu_data();
}
// NOLINT_NEXT_LINE(whitespace/operators)
MaxPoolForward<Dtype><<<CAFFE_GET_BLOCKS(count), CAFFE_CUDA_NUM_THREADS>>> (
count, bottom_data, bottom[0]->num(), channels_,
height_, width_, pooled_height_, pooled_width_, kernel_h_,
kernel_w_, stride_h_, stride_w_, pad_h_, pad_w_, top_data,
mask, top_mask);
break;
case PoolingParameter_PoolMethod_AVE:
// NOLINT_NEXT_LINE(whitespace/operators)
AvePoolForward<Dtype><<<CAFFE_GET_BLOCKS(count), CAFFE_CUDA_NUM_THREADS>>>(
count, bottom_data, bottom[0]->num(), channels_,
height_, width_, pooled_height_, pooled_width_, kernel_h_,
kernel_w_, stride_h_, stride_w_, pad_h_, pad_w_, top_data);
break;
case PoolingParameter_PoolMethod_STOCHASTIC:
if (Caffe::phase() == Caffe::TRAIN) {
// We need to create the random index as well.
caffe_gpu_rng_uniform(count, Dtype(0), Dtype(1),
rand_idx_.mutable_gpu_data());
// NOLINT_NEXT_LINE(whitespace/operators)
StoPoolForwardTrain<Dtype><<<CAFFE_GET_BLOCKS(count),
CAFFE_CUDA_NUM_THREADS>>>(
count, bottom_data, bottom[0]->num(), channels_,
height_, width_, pooled_height_, pooled_width_, kernel_h_,
kernel_w_, stride_h_, stride_w_,
rand_idx_.mutable_gpu_data(), top_data);
} else {
// NOLINT_NEXT_LINE(whitespace/operators)
StoPoolForwardTest<Dtype><<<CAFFE_GET_BLOCKS(count),
CAFFE_CUDA_NUM_THREADS>>>(
count, bottom_data, bottom[0]->num(), channels_,
height_, width_, pooled_height_, pooled_width_, kernel_h_,
kernel_w_, stride_h_, stride_w_, top_data);
}
break;
default:
LOG(FATAL) << "Unknown pooling method.";
}
CUDA_POST_KERNEL_CHECK;
}
And get the message "Unknown pooling method." as shown in the window below:
The normal execution of my project is described in the image below:
Could someone give me an idea about the possible solution?
The pooling layer which by default should be max pooling was translated into some other layers. You might add a breakpoint at pooling_layer.cu (line 163) or add cout << this->layer_param_.pooling_param().pool() << endl; before that line to see what pooling layer it was using. I guess it doesn't equal to PoolingParameter_PoolMethod_MAX here.
I'm not sure why it happened, maybe there some error in the prototxt file or the protobuf. A brutal trick would be overlapping line 206 with line 165-176 in order to force using max pooling.

Open cv chessBoard and 2d pieces recognition

I'm doing a project in open cv.
The goal is to recognize 2d chess pieces on a chessboard,and their location.
the algorithm goes like this:
run canny on the picture.
run houghLines on the image.
3.Merge close lines
4.sort to vertical lines and horizontal lines.
5.sort the lines and choose all the boarder lines +1 (so we get the actual play board lines and not the board lines)
find intersection of boarder lines. (4 corners)
do Homography with found corners.
split new image to 8X8 cells (length/8,width/8).
detect pieces on board and location using template matching.
Right now I'm stuck on doing the Homography.
1. Sometimes I will detect the lines of the outer board , Sometimes I wont , and sometimes I will detect 2 lines on the outer board.
2.the merge line function I found online seems not to be doing a good job. there can be 2 vertical lines on each other which it does not merge.
void processChessboardImage(Mat & image)
{
Vec2f temp(0,0);
vector<Vec2f> lines;
vector<Vec2f> boarderLines;
vector<Vec2f> verticalLines;
vector<Vec2f> horizontalLines;
Mat canny_output,hough_output;
canny_output = CannyOnImage(image);
//cvtColor(canny_output, image, CV_GRAY2BGR);
HoughLines(canny_output, lines, 1, CV_PI/180, 120, 0, 0 );
//at this point we have lines - all straight lines in image
MergeRelatedLines(&lines,canny_output);
for(int i=0; i< lines.size(); i++)
{
if(lines[i][1] != -100)
{
if((lines[i][1] >= 0 && lines[i][1] < 1 )|| lines[i][1] >= 3)//Vertical
{
verticalLines.push_back(lines[i]);
}
else // horizontal;
{
horizontalLines.push_back(lines[i]);
}
}
}
sort(verticalLines.begin(),verticalLines.end(),[](const Vec2f& elem1, const Vec2f& elem2){ return elem1[0]*cos(elem1[1]) < elem2[0]*cos(elem2[1]); });
sort(horizontalLines.begin(),horizontalLines.end(),[](const Vec2f& elem1, const Vec2f& elem2){ return elem1[0] < elem2[0]; });
int numVerticalLines = verticalLines.size();
int numHorizontalLines = horizontalLines.size();
boarderLines.push_back(verticalLines[0]);
boarderLines.push_back(verticalLines[verticalLines.size()-1]);
boarderLines.push_back(horizontalLines[0]);
boarderLines.push_back(horizontalLines[horizontalLines.size() -1 ]);
void MergeRelatedLines(vector<Vec2f> *lines, Mat &img)
{
vector<Vec2f>::iterator current;
vector<Vec4i> points(lines->size());
for(current=lines->begin();current!=lines->end();current++)
{
if((*current)[0]==0 && (*current)[1]==-100)
continue;
float p1 = (*current)[0];
float theta1 = (*current)[1];
Point pt1current, pt2current;
if(theta1>CV_PI*45/180 && theta1<CV_PI*135/180)
{
pt1current.x=0;
pt1current.y = p1/sin(theta1);
pt2current.x=img.size().width;
pt2current.y=-pt2current.x/tan(theta1) + p1/sin(theta1);
}
else
{
pt1current.y=0;
pt1current.x=p1/cos(theta1);
pt2current.y=img.size().height;
pt2current.x=-pt2current.y/tan(theta1) + p1/cos(theta1);
}
vector<Vec2f>::iterator pos;
for(pos=lines->begin();pos!=lines->end();pos++)
{
if(*current==*pos)
continue;
if(fabs((*pos)[0]-(*current)[0])<20 && fabs((*pos)[1]-(*current)[1])<CV_PI*10/180)
{
float p = (*pos)[0];
float theta = (*pos)[1];
Point pt1, pt2;
if((*pos)[1]>CV_PI*45/180 && (*pos)[1]<CV_PI*135/180)
{
pt1.x=0;
pt1.y = p/sin(theta);
pt2.x=img.size().width;
pt2.y=-pt2.x/tan(theta) + p/sin(theta);
}
else
{
pt1.y=0;
pt1.x=p/cos(theta);
pt2.y=img.size().height;
pt2.x=-pt2.y/tan(theta) + p/cos(theta);
}
if(((double)(pt1.x-pt1current.x)*(pt1.x-pt1current.x)
+ (pt1.y-pt1current.y)*(pt1.y-pt1current.y)<64*64)
&& ((double)(pt2.x-pt2current.x)*(pt2.x-pt2current.x) + (pt2.y-pt2current.y)*(pt2.y-pt2current.y)<64*64))
{
printf("Merging\n");
// Merge the two
(*current)[0] = ((*current)[0]+(*pos)[0])/2;
(*current)[1] = ((*current)[1]+(*pos)[1])/2;
(*pos)[0]=0;
(*pos)[1]=-100;
}
}
}
}
}
example images:
good image
bad image
for the bad image-this code works , but of course i need something more generic...
boarderLines.push_back(verticalLines[1]);
boarderLines.push_back(verticalLines[verticalLines.size()-3]);
boarderLines.push_back(horizontalLines[1]);
boarderLines.push_back(horizontalLines[horizontalLines.size() -2 ]);
thanks in advance for your help!

Finding HSV Thresholds Via Histograms with OpenCV

I'm trying to write a method that will find the proper threshold values in HSV space for an object placed at the center of the screen. These values are used for an object tracking algorithm. I've tested that piece of code with hand coded threshold values and it works well. The idea behind the method is that it should calculate the histograms for each of the channels and then return the 5th and 95th percentile for each to be used as the threshold values. (credit: How to find RGB/HSV color parameters for color tracking?) The image being passed is a picture of the object to be tracked (which is set by the user before the whole process begins. Here is the code
std::vector<cv::Scalar> HSV_Threshold_Determiner::Get_Threshold_Values(const cv::Mat& image)
{
cv::Mat inputImage;
cv::cvtColor(image, inputImage, CV_BGR2HSV);
std::vector<cv::Mat> bgrPlanes;
cv::split(inputImage, bgrPlanes);
cv::Mat hHist, sHist, vHist;
int hMax = 180, svMax = 256;
float hRanges[] = { 0, (float)hMax };
const float* hRange = { hRanges };
float svRanges[] = { 0, (float)svMax };
const float* svRange = { svRanges };
//float sRanges[] = { 0, 256 };
cv::calcHist(&bgrPlanes[0], 1, 0, cv::Mat(), hHist, 1, &hMax, &hRange);
cv::calcHist(&bgrPlanes[1], 1, 0, cv::Mat(), sHist, 1, &svMax, &svRange);
cv::calcHist(&bgrPlanes[2], 1, 0, cv::Mat(), vHist, 1, &svMax, &svRange);
int totalEntries = image.cols * image.rows;
int fiveCutoff = (int)(totalEntries * .05);
int ninetyFiveCutoff = (int)(totalEntries * .95);
float hTotal = 0, sTotal = 0, vTotal = 0;
bool hMinFound = false, hMaxFound = false, sMinFound = false, sMaxFound = false,
vMinFound = false, vMaxFound = false;
cv::Scalar hThresholds;
cv::Scalar sThresholds;
cv::Scalar vThresholds;
for(int i = 0; i < vHist.rows; ++i)
{
if(i < hHist.rows)
{
hTotal += hHist.at<float>(i, 0);
if(hTotal >= fiveCutoff && !hMinFound)
{
hThresholds.val[0] = i;
hMinFound = true;
}
else if(hTotal>= ninetyFiveCutoff && !hMaxFound)
{
hThresholds.val[1] = i;
hMaxFound = true;
}
}
sTotal += sHist.at<float>(i, 0);
vTotal += vHist.at<float>(i, 0);
if(sTotal >= fiveCutoff && !sMinFound)
{
sThresholds.val[0] = i;
sMinFound = true;
}
else if(sTotal >= ninetyFiveCutoff && !sMaxFound)
{
sThresholds.val[1] = i;
sMaxFound = true;
}
if(vTotal >= fiveCutoff && !vMinFound)
{
vThresholds.val[0] = i;
vMinFound = true;
}
else if(vTotal >= ninetyFiveCutoff && !vMaxFound)
{
vThresholds.val[1] = i;
vMaxFound = true;
}
if(vMaxFound && sMaxFound && hMaxFound)
{
break;
}
}
std::vector<cv::Scalar> returnVect;
returnVect.push_back(hThresholds);
returnVect.push_back(sThresholds);
returnVect.push_back(vThresholds);
return returnVect;
}
What I am trying to do is sum up the number of entries in each bucket until I get to a number that is greater than or equal to five percent and ninety-five percent of the total. Unfortunately the numbers I get are never close to the ones I get if I do the thresholding by hand.
Mat img = ... // from camera or some other source
// STEP 1: learning phase
Mat hsv, imgThreshed, processed, denoised;
cv::GaussianBlur(img, denoised, cv::Size(5,5), 2, 2); // remove noise
cv::cvtColor(denoised, hsv, CV_BGR2HSV);
// lets say we picked manually a region of 100x100 px with the interested color/object using mouse
cv::Mat roi = hsv (cv::Range(mousex-50, mousey+50), cv::Range(mousex-50, mousey+50));
// must split all channels to get Hue only
std::vector<cv::Mat> hsvPlanes;
cv::split(roi, hsvPlanes);
// compute statistics for Hue value
cv::Scalar mean, stddev;
cv::meanStdDev(hsvPlanes[0], mean, stddev);
// ensure we get 95% of all valid Hue samples (statistics 3*sigma rule)
float minHue = mean[0] - stddev[0]*3;
float maxHue = mean[0] + stddev[0]*3;
// STEP 2: detection phase
cv::inRange(hsvPlanes[0], cv::Scalar(minHue), cv::Scalar(maxHue), imgThreshed);
imshow("thresholded", imgThreshed);
cv_erode(imgThreshed, processed, 5); // minimizes noise
cv_dilate(processed, processed, 20); // maximize left regions
imshow("final", processed);
//STEP 3: do some blob/contour detection on processed image & find maximum blob/region, etc ...
A much simpler solution - just calculate mean & std. deviation for a region of interest, i.e. containing the Hue value.
Since Hue is the most stable component in the image, the other components saturation & value should be discarded as they vary too much. However you can still compute mean for them if needed.

Open yml file in opencv 1.0

I have a yml file and i want to open the file for reading using the existing opencv 1.0 functions. The file contains something like this:
%YAML:1.0
Image file: "00961010.jpg"
Contours count: 8
Contours:
-
Name: FO
Count: 41
Closed: 0
Points:
-
x: 740.7766113281250000
y: 853.0124511718750000
-
x: 745.1353149414062500
y: 875.5324096679687500
Can you please provide some example of how to iterate over this data? I need only the x, y points and store then in an array. I have searched but i did not found a similar example of data and please help me. Thanks in advance!
You're going to want to look at the cvFileStorage data-structures, and functions.
Here is an example from OpenCV to get you started:
#include "cxcore.h"
int main( int argc, char** argv )
{
CvFileStorage* fs = cvOpenFileStorage( "points.yml", 0, CV_STORAGE_READ );
CvStringHashNode* x_key = cvGetHashedNode( fs, "x", -1, 1 );
CvStringHashNode* y_key = cvGetHashedNode( fs, "y", -1, 1 );
CvFileNode* points = cvGetFileNodeByName( fs, 0, "points" );
if( CV_NODE_IS_SEQ(points->tag) )
{
CvSeq* seq = points->data.seq;
int i, total = seq->total;
CvSeqReader reader;
cvStartReadSeq( seq, &reader, 0 );
for( i = 0; i < total; i++ )
{
CvFileNode* pt = (CvFileNode*)reader.ptr;
int x = cvReadIntByName( fs, pt, "x", 0 /* default value */ );
int y = cvReadIntByName( fs, pt, "y", 0 /* default value */ );
CV_NEXT_SEQ_ELEM( seq->elem_size, reader );
printf("point%d is (x = %d, y = %d)\n", i, x, y);
}
}
cvReleaseFileStorage( &fs );
return 0;
}