-1

NOTE: This is a homework problem and the professor explicitly forbids soliciting answers from StackOverflow, so please limit your response to the specific question I have asked and do not attempt to provide a working solution.

I am asked to implement a function that computes the histogram of a single-channel 8-bit image represented as an OpenCV Mat with type CV_U8.

In this case, the histogram uses 256 uniformly-distributed buckets. This is the reference we are intended to replicate (using OpenCV 3.4):

Mat reference;

/// Establish the number of bins
int histSize = 256;

/// Set the ranges ( for B,G,R) )
float range[] = { 0, 256 } ;
const float* histRange = { range };

bool uniform = true;
bool accumulate = false;

cv::calcHist(&bgr_planes[0], 1, 0, Mat(), reference, 1, &histSize, &histRange,
             uniform, accumulate);

// reference now contains the canonical histogram of the input image's
// blue channel

I wrote the following function to calculate the histogram, which produces the correct results 45-69% of the time (p<0.05, n=66). Once when it failed, I examined the results and found no discernable pattern. All trials were conducted on the same test image.

Mat myCalcHist(const Mat& input) {
  assert(input.isContinuous());

  Mat res(256, 1, CV_32F);
  for (const uint8_t* it = input.datastart; it != input.dataend; ++it) {
    ++res.at<float>(*it);
  }

  return res;
}

The following function, on the other hand, more closely matches OpenCV's internal implementation in that it uses the idiomatic accessors and converts the float result from an int work matrix, but in n=66 trials it did not produce the correct result a single time. Again, I found no discernable pattern in the data.

Mat myCalcHist(const Mat& input) {

  Mat ires(256, 1, CV_32S);
  for (int i = 0; i < input.total(); ++i) {
    ++ires.at<int>(input.at<uint8_t>(i));
  }

  Mat res(256, 1, CV_32F);
  ires.convertTo(res, CV_32F);
  return res;
}

Why are the results for my first implementation different than those from my second implementation, and where is nondeterminism introduced to the first implementation?

hxtk
  • 325
  • 1
  • 9
  • 1
    Is `res` initialized? – Cris Luengo Feb 10 '18 at 20:21
  • @Chris I didn't think I needed to initialize it explicitly because (a) `reference` is not initialized explicitly in the reference implementation and (b) `Mat.convertTo` reallocates the result matrix as needed. However, I have since added code to explicitly initialize `res` (see incoming edit) and it had no effect: the second implementation still fails 100% of the time. – hxtk Feb 10 '18 at 20:29
  • 1
    you should initialize your matrix data with either (constructor) cv::Mat::zeros(size, type) or cv::Mat(size,type,cv::Scalar::all(0)) – Micka Feb 10 '18 at 20:38
  • 1
    Use cv::Mat::zeros() or cv::Mat::setTo() to initialize res. If you only call the constructor of the mat, the memory is only allocated and not initialized with zeros. – code monkey Feb 10 '18 at 20:39
  • how did you create Mat input? – Micka Feb 10 '18 at 20:39
  • 1
    in the 2nd code, ires has to be initialized, not res – Micka Feb 10 '18 at 20:40
  • @Micka this is the information I was missing; I had assumed that Mat would initialize its own buffer by default. I will accept this if you repost it as an answer. – hxtk Feb 10 '18 at 20:46
  • The input in all cases is derived from a sample image that was read using `cv::imread` and split into channels using `cv::split` – hxtk Feb 10 '18 at 20:47
  • 1
    @hxtk: There's a good lesson here: if your code is unintentionally "stochastic", you are likely reading uninitiated memory! – Cris Luengo Feb 10 '18 at 21:45

1 Answers1

0

initializing the histogram matrix should work:

Mat myCalcHist(const Mat& input) 
{
    Mat ires = cv::Mat::zeros(256, 1, CV_32S);
    for (int i = 0; i < input.total(); ++i) 
    {
         ++ires.at<int>(input.at<uint8_t>(i));
    }

   Mat res(256, 1, CV_32F);
   ires.convertTo(res, CV_32F);
   return res;
}
Micka
  • 19,585
  • 4
  • 56
  • 74