I have what should be a simple exercise in OpenCV, but can't seem to get it working. I'm trying to determine the density of edges in a section of an image. This is the process I follow:
1. pull subimage from image
2. use Canny to find edges in subImage
3. threshold to create binary image
4. create histogram for binary image
5. get number of pixels in binary image that are "on" (255)
6. calculate "edge density" as numPixelsOn/totalPixels
I've checked the results of 1,2,and 3 above, and results seem ok. Steps 4 and 5 seem to be giving me trouble.
Here's my code for calculating the histogram:
int histSize = 256; // bin size
float range[] = { 0, 256} ;
const float* histRange = { range };
bool uniform = true;
bool accumulate = false;
Mat hist;
/// Compute the histograms:
calcHist( &gray, 1, 0, Mat(), hist, 1, &histSize, &histRange, uniform, accumulate );
This doesn't seem to be working. When I check hist after calling calcHist, it has no data (i.e. data == 0)... or maybe I don't understand what I'm looking at.
Now for accessing the "bins" in the histogram, I've tried a number of things. First I tried this:
uchar* p;
p = hist.ptr<uchar>(0);
double edgePixels = p[255];
I also tried to use:
cvQueryHistValue_1D(hist,255); // #include <opencv2/legacy/compat.hpp>
This wouldn't compile. Gave 2 errors: 'cv::Mat' does not have an overloaded member 'operator ->', and 'bins': is not a member of 'cv::Mat'
I guess I need some help on this.
There is an error in your 3rd param - channels, that should be an array so you should call it like this
int histSize = 256; // bin size
float range[] = { 0, 256} ;
const float* histRange = { range };
bool uniform = true;
bool accumulate = false;
Mat hist;
int channels[] = {0};
/// Compute the histograms:
calcHist( &gray, 1, channels, Mat(), hist, 1, &histSize, &histRange, uniform, accumulate );
You should also call:
hist.at<float>(0);
to get your value, OpenCV stores them as floats, this is the reason you're getting 0 when using uchar as uchar is smaller than float and the numbers stores as small enough to not fill the first bites.
Related
I'm trying to convert the result Mat of templateMatch with the following code (which was found at: this question):
void convertmatVec(const cv::Mat& m, std::vector<uchar>& v) {
if (m.isContinuous()) {
v.assign(m.datastart, m.dataend);
}
else {
printf("failed to convert / not continuous");
return;
}
}
and when I check the size of the output it's not the same as the product of result's columns and rows (which is the same when I try to convert another Mat that I created):
result:
another Mat created with:
cv::Mat test(result.size(),false);
test.setTo(cv::Scalar(255));
which is then converted shows that the size is the same as the product:
So my question is how can I get the result's data so I can process it futher because I'm assuming the size of the vector should be the same as the product which it clearly isn't.
EDIT1: Added templateMatching code
void matchTemplatenoRotation(cv::Mat src, cv::Mat templ) {
cv::Mat img_display, result;
src.copyTo(img_display);
int result_cols = src.cols - templ.cols + 1;
int result_rows = src.rows - templ.rows + 1;
result.create(result_rows, result_cols, CV_32FC1);
cv::matchTemplate(src, templ, result, CV_TM_SQDIFF_NORMED);
cv::normalize(result, result, 0, 1, cv::NORM_MINMAX, -1, cv::Mat());
cv::Point minLoc, maxLoc;
double minVal, maxVal;
cv::minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat());
cv::Point matchLoc = minLoc;
//end of templatematching
EDIT2 follow up question:
How come when I create another Mat with following code:
cv::Mat test(cv::Size(result.cols, result.rows),true);
test.setTo(cv::Scalar(255));
//cv::imshow("test3", test);
std::vector<float> testVector;
convertmatVec(test, testVector);
the vector size is as following:
You have 4 times the expected number of elements in your vector because your matrix is of type CV_32FC1. If you look at the type of m.datastart and m.dataend you will see that they are uchar* and not float* as you expect.
To correct this, change v.assign(m.datastart, m.dataend); to v.assign((float*)m.datastart, (float*)m.dataend);. And you will need to pass a vector of float instead of a vector<uchar>.
Of course, your conversion function will only work for float type matrices. you could add some tests to detect the type of the matrix inside the function.
To answer your follow up question, it appears that you have the inverse problem. You are passing a CV_8U type matrix to a function that expects a CV_32F type one. Use your old conversion function for 8-bit matrices and use the fix I suggested for 32-bit floating-values matrices. You can also add a test inside the conversion function to automatically choose the right conversion. I also advise you to read a bit on OpenCV Mat class to understand better what type of data is in your matrices.
It looks like your const cv::Mat& m has four channels.
Another thing to consider: if this Mat is the result of a matchTemplate(), then you should be using a vector<float>, instead of vector<uchar>.
// Fourier transform of Image<Bgr,byte> orig object.
// output is matrix<float> with 2 channels.
private Matrix<float> fourier()
{
Image<Gray, float> image = orig.Convert<Gray, float>();
IntPtr complexImage = CvInvoke.cvCreateImage(image.Size,Emgu.CV.CvEnum.IPL_DEPTH.IPL_DEPTH_32F, 2);
CvInvoke.cvSetZero(complexImage); // Initialize all elements to Zero
CvInvoke.cvSetImageCOI(complexImage, 1);
CvInvoke.cvCopy(image, complexImage, IntPtr.Zero);
CvInvoke.cvSetImageCOI(complexImage, 0);
Matrix<float> dft = new Matrix<float>(image.Rows, image.Cols, 2);
CvInvoke.cvDFT(complexImage, dft, Emgu.CV.CvEnum.CV_DXT.CV_DXT_FORWARD, 0);
//The Real part of the Fourier Transform
Matrix<float> outReal = new Matrix<float>(image.Size);
//The imaginary part of the Fourier Transform
Matrix<float> outIm = new Matrix<float>(image.Size);
CvInvoke.cvSplit(dft, outReal, outIm, IntPtr.Zero, IntPtr.Zero);
return dft;
}
// butterworth filter with Do frequency and order n.
// Filter is returned as matrix<float> with 2 channels.
private Matrix<float> make_butterworth(int Do, int n)
{
Matrix<float> ff = fourier();
Matrix<float> tmp = new Matrix<float>(ff.Rows, ff.Cols, 2);
Point center=new Point(tmp.Rows/2,tmp.Cols/2);
for (int i=0;i<orig.Rows;i++)
for (int j = 0; j < orig.Cols; j++)
{
int Duv= (int) (Math.Sqrt( Math.Pow(i-center.X,2) + Math.Pow(j-center.Y,2)));
tmp[i, j] = (float) (1 / (1 + Math.Pow((Duv / Do), 2 * n)));
}
return tmp;
}
// The click event which will trigger fourier() and
make_butterworth() takes Do and n order input from user
and applies filter on orig image.
private void lowPassToolStripMenuItem2_Click(object sender, EventArgs e)
{
dialog_input d1 = new dialog_input("Enter values of Do and order n seperated by space:\n");
d1.ShowDialog();
string[] s = d1.t.Split(new char[] { ' ', ',' });
int fc = Convert.ToInt32(s[0]);
int order = Convert.ToInt32(s[1]);
Matrix<float> filter= make_butterworth(fc, order); // 2 channels
Matrix<float> m = fourier(); // 2 channels
m._Mul(filter);
// filter * with fourier image.
CvInvoke.cvDFT(m,m,CV_DXT.CV_DXT_INVERSE, 0);
IntPtr cmplx = CvInvoke.cvCreateImage(m.Size, IPL_DEPTH.IPL_DEPTH_32F, 2);
CvInvoke.cvSetZero(cmplx);
CvInvoke.cvSetImageCOI(cmplx, 0);
CvInvoke.cvCopy(m, cmplx, IntPtr.Zero);
Bitmap bm = new Bitmap(m.Width, m.Height);
BitmapData bd = bm.LockBits(new Rectangle
(0, 0, bm.Width, bm.Height),
ImageLockMode.ReadWrite,
PixelFormat.Canonical);
bd.Scan0 = cmplx;
bm.UnlockBits(bd);
pictureBox2.Image = bm;
}
One thing i am taking fourier() as 2 channels instead of only taking real channel. i am not sure if i am wrong in this regard. Also thats why i had to take filter as 2 channels also where 2 channels are used to represent data of Gray and Alpha in both cases.
Problem occurs at bitmapdata object initialization due to pixelFormat.Canonical parameter. The result of multiply of fourier matrix and filter matrix is in matrix float. All i want to do is to take its IDFT and display the filtered image. Not sure about the PixelFormat. Any help would be great.
Read this chapter: opencv DFT tutorial , C code DFT and opencv DFT pythonit explains all you need to know about DFT in opencv.
About the types
1) Image is Real
2) DFT(Image) result in a complex image.
3) butterworth is a one channel matrix with the same size of image.
4) to filter, multiply each channel of DFT resulting image by butterworth filter. each channel must be multplied separated beacouse we have the real and complex parts of each pixel allocated in one channel as result of DFT.how filtering works
5) After filtering you will have a complex image
6) now you can apply the IDFT that have as result a real image. In opencv you maybe get as result a complex image, but the second channel are entirely zeros so you can discart.
Look here to:opencv C++ DFT
I want to calculate histogram of an image hows pixels are of type 32F (32 bit floating point). What should be the parameter values of "calcHist" function for:
- dims
- bins
- range
Well I've done this many times. Something like so:
cv::Mat matSrc; // this is a CV_32FC1 normalised image
int nHistSize = 65536;
float fRange[] = { 0.0f, 1.0f };
const float* fHistRange = { fRange };
cv::Mat matHist;
cv::calcHist(&matSrc, 1, 0, cv::Mat(), matHist, 1, &nHistSize, &fHistRange);
As it says in the documentation describing the source arrays:
Source arrays. They all should have the same depth, CV_8U or CV_32F ,
and the same size. Each of them can have an arbitrary number of
channels.
So CV_32F is supported. In this situation, the range (in my example 0.0 to 1.0) is binned into the number of bins required (in my example 65536).
I created three dimension matrix for computing of histogram as follows:
// Histogram of HSV image
int const hue_bins = 180; //
int const sat_bins = 256; //
int const val_bins = 4; // Only four bins for V channel!
float const hue_range[2] = {0, 180};
float const sat_range[2] = {0, 256};
float const val_range[2] = {0, 256};
int const hsv_sizes[] = {hue_bins, sat_bins, val_bins};
cv::Mat1f m_tone_frequences(3, hsv_sizes, 0.);
Then I'm using
cv::calcHist
( &image, 1, channels, mask, histogram
, num_channels, hsv_sizes, ranges);
...
cv::calcBackProject
( &image_f, 1, channels, histogram
, backproject, hsv_sizes, 1.0);
and seems it works fine (code is simplified).
Since the histograms are sampled from a single image, it is possible to run into sampling problems (object of interest has narrow color distribution). So I want to apply Gaussian smoothing to "Value" histogram planes.
I'm tried get histogram rows, but it gives me anothed 3D Mat:
cv::Mat1f hrow = histogram.row(0);
// hrow.dims ==3 && hrow.rows == -1 && hrow.cols == -1
and I don't have ideas about processing of it.
I am at a loss to solve this issue because this action should be very simple to do.
Any advice is greatly appreciated.
In Matlab, If A is a matrix, sum(A) treats the columns of A as vectors, returning a row vector of the sums of each column.
sum(Image); How could it be done with OpenCV?
Using cvReduce has worked for me. For example, if you need to store the column-wise sum of a matrix as a row matrix you could do this:
CvMat * MyMat = cvCreateMat(height, width, CV_64FC1);
// Fill in MyMat with some data...
CvMat * ColSum = cvCreateMat(1, MyMat->width, CV_64FC1);
cvReduce(MyMat, ColSum, 0, CV_REDUCE_SUM);
More information is available in the OpenCV documentation.
EDIT after 3 years:
The proper function for this is cv::reduce.
Reduces a matrix to a vector.
The function reduce reduces the matrix to a vector by treating the
matrix rows/columns as a set of 1D vectors and performing the
specified operation on the vectors until a single row/column is
obtained. For example, the function can be used to compute horizontal
and vertical projections of a raster image. In case of REDUCE_MAX and
REDUCE_MIN , the output image should have the same type as the source
one. In case of REDUCE_SUM and REDUCE_AVG , the output may have a
larger element bit-depth to preserve accuracy. And multi-channel
arrays are also supported in these two reduction modes.
OLD:
I've used ROI method: move ROI of height of the image and width 1 from left to right and calculate means.
Mat src = imread(filename, 0);
vector<int> graph( src.cols );
for (int c=0; c<src.cols-1; c++)
{
Mat roi = src( Rect( c,0,1,src.rows ) );
graph[c] = int(mean(roi)[0]);
}
Mat mgraph( 260, src.cols+10, CV_8UC3);
for (int c=0; c<src.cols-1; c++)
{
line( mgraph, Point(c+5,0), Point(c+5,graph[c]), Scalar(255,0,0), 1, CV_AA);
}
imshow("mgraph", mgraph);
imshow("source", src);
EDIT:
Just out of curiosity, I've tried resize to height 1 and the result was almost the same:
Mat test;
cv::resize(src,test,Size( src.cols,1 ));
Mat mgraph1( 260, src.cols+10, CV_8UC3);
for(int c=0; c<test.cols; c++)
{
graph[c] = test.at<uchar>(0,c);
}
for (int c=0; c<src.cols-1; c++)
{
line( mgraph1, Point(c+5,0), Point(c+5,graph[c]), Scalar(255,255,0), 1, CV_AA);
}
imshow("mgraph1", mgraph1);
cvSum respects ROI, so if you move a 1 px wide window over the whole image, you can calculate the sum of each column.
My c++ got a little rusty so I won't provide a code example, though the last time I did this I used OpenCVSharp and it worked fine. However, I'm not sure how efficient this method is.
My math skills are getting rusty too, but shouldn't it be possible to sum all elements in columns in a matrix by multiplying it by a vector of 1s?
For an 8 bit greyscale image, the following should work (I think).
It shouldn't be too hard to expand to different image types.
int imgStep = image->widthStep;
uchar* imageData = (uchar*)image->imageData;
uint result[image->width];
memset(result, 0, sizeof(uchar) * image->width);
for (int col = 0; col < image->width; col++) {
for (int row = 0; row < image->height; row++) {
result[col] += imageData[row * imgStep + col];
}
}
// your desired vector is in result