2

I know there is an option to find the average of a Mat in OpenCV:

cv::mean(mat);

I want to know if in OpenCV there is also an option to find the average without the extreme values (e.g. just the values between 10% to 90%).

Miki
  • 40,887
  • 13
  • 123
  • 202
Bschs
  • 61
  • 10

3 Answers3

1

I dont know OpenCV, but I doubt that it has a ready to use function for this. However, a naive implementation could look like this:

double m = cv::mean(mat);
Mat temp = mat;
... set all elements in temp to 0, where abs(temp[i][j] - m) > tolerance
... and count those elements in count
int N = mat.total(); // total number of elements
m = cv::sum(temp) / (N-count)

EDIT: Actually this is not exactly what the question was asking for. However, if one can assume a gaussian distribution of the values, one could estimate the value of tolerance based on the standard deviation (has to be computed) to exclude the upper/lower 10% of the data.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • cv::mean considers also in the zero values. so the mean will be so low. – Bschs Feb 11 '16 at 16:09
  • @Bschs thats why the last line corrects for the normalization – 463035818_is_not_an_ai Feb 11 '16 at 16:38
  • @tobi303 this won't work for a general matrix, where values can be for example less than 0. However, this approach would work for images, given that you have the `tolerance`, which can't be fixed, but re-computed for every image. So this doesn't answer the question, since you miss how to compute the thresholds. The rest is ok, and Bschs is wrong :D – Miki Feb 11 '16 at 16:46
  • @Miki I agree with your point on the tolerance. Actually I misunderstood the answer and thought outliers that are a certain fraction off the mean should be skipped (actually still not what my code is doing, but this could be fixed easily). On the other hand I dont understand, why negative entries would be a problem – 463035818_is_not_an_ai Feb 11 '16 at 16:54
  • @tobi303 my bad, I overlooked it. Just the _tolerance_ thing :D – Miki Feb 11 '16 at 16:56
  • Pay attention, however, that the interval may not be symmetric wrt the mean. – Miki Feb 11 '16 at 16:58
  • @Miki thanks for the feedback, I think you are right and the answer doesnt fit the question too well. Nevertheless, I think it is not bad enough to delete it ;). I edited to point out its shortcomings – 463035818_is_not_an_ai Feb 11 '16 at 16:59
  • Assuming stuff (as normal distribution) is not a great idea in general. However, I agree this is not worth deleting, since this approach may be valuable when some criteria are met – Miki Feb 11 '16 at 17:00
  • btw, the name you're looking for is: `int N = mat.total();` (valid for single channel matrix) – Miki Feb 11 '16 at 17:02
  • regarding to the last line - Is it not easier to write: `m = cv::sum(temp) / (N-count)`? – Bschs Feb 14 '16 at 08:48
  • @Bschs yes, of course, I just didnt know the name of `cv::sum` – 463035818_is_not_an_ai Feb 14 '16 at 09:44
0

No, there is no OpenCV function to do this. You can, however, implement your own.


The trickiest part is to compute the values that correspond to your percentages. This can be easily achieved computing the cumulative histogram of the image.

However, to make the approach general, you can't know which values are in the matrix, so you need rely on maps.

Note that if you are working only on CV_8U images, you can optimize knowing that you'll have at most 256 different values. To implement this, you can have an hint here.

So this is a possible implementation, that works on Mat with at most 4 channels (as cv::mean), and without knowing a priori the possible number of different values. You can check commenting / decommenting parts in the example matrix intialization that it performs correctly:

#include <opencv2/opencv.hpp>
#include <vector>
#include <numeric>
#include <map>
#include <iostream>
using namespace cv;
using namespace std;

double robustMeanC1(const Mat1d& src, Vec2d bounds)
{
    // Compute histogram (with no predefined range)
    map<double, int> hist;
    for (int r = 0; r < src.rows; ++r)
    {
        for (int c = 0; c < src.cols; ++c)
        {
            double key = src(r,c);
            if (hist.count(key) == 0) {
                hist.insert(make_pair(key, 1));
            }
            else {
                hist[key]++;
            }
        }
    }

    // Get vectors from map
    vector<double> vals;
    vector<int> sums;
    vals.reserve(hist.size());
    sums.reserve(hist.size());
    for (auto kv : hist)
    {
        vals.push_back(kv.first);
        sums.push_back(kv.second);
    }

    // Compute cumulative histogram
    vector<int> cumhist(sums);
    for (int i=1; i<sums.size(); ++i)
    {
        cumhist[i] = cumhist[i - 1] + sums[i];
    }

    // Compute bounds
    int total = src.rows * src.cols;
    double low_bound = (total * bounds[0]) / 100.0;
    double upp_bound = (total * bounds[1]) / 100.0;
    int low_index = distance(cumhist.begin(), upper_bound(cumhist.begin(), cumhist.end(), low_bound));
    int upp_index = distance(cumhist.begin(), upper_bound(cumhist.begin(), cumhist.end(), upp_bound));

    if (low_index == upp_index) {upp_index++;}

    // Compute mean
    double mean = 0.0;
    int count = 0;
    for (int i = low_index; i < upp_index; ++i)
    {
        mean += vals[i] * sums[i];
        count += sums[i];
    }
    mean /= count;

    return mean;
}

Scalar robustMean(const Mat& src, Vec2d bounds) 
{
    Mat m;
    src.convertTo(m, CV_64F);

    Scalar res(0.0, 0.0, 0.0, 0.0);

    if (m.channels() == 1)
    {
        res[0] = robustMeanC1(m, bounds);
    } 
    else
    {
        vector<Mat1d> planes;
        split(m, planes);

        if (planes.size() > 4)
        {
            // Error, at most 4 channels
            return Scalar(0,0,0,0);
        }

        for (int i = 0; i < planes.size(); ++i)
        {
            res[i] = robustMeanC1(planes[i], bounds);
        }
    }
    return res;
}



int main()
{
    Mat1d m(10,10, 5.f);
    m(Range(0,1), Range::all()) = 2.0;
    //m(Range(1, 2), Range::all()) = 80.0;
    //randu(m, Scalar(0), Scalar(1));

    //Mat3b m = imread("path_to_image");

    Scalar rs = robustMean(m, Vec2d(10, 90));
    Scalar s = mean(m);

    cout << "Robust Mean: " << rs << endl;
    cout << "       Mean: " << s << endl;

    return 0;
}
Community
  • 1
  • 1
Miki
  • 40,887
  • 13
  • 123
  • 202
0

I would simply sort the Mat elements and take the mean of the truncated vector

#include <algorithm>
#include <vector>
// c in [0,1] the portion of middvalues added to the mean
template<class _T> _T avg( std::vector< _T > & vec, double c )
{
    if ( c < 0.0 )
        c = 0.0;
    else if ( c > 1.0 )
        c = 1.0;
    const size_t len = (size_t)( c * (double)vec.size() );
    if ( len == 0 )
        return 0.0;
    std::vector< _T >::iterator beg = vec.begin();
    std::vector< _T >::iterator end = vec.end();
    if ( len < vec.size() )
    {    
        beg += ( vec.size() - len )/2;
        end = beg + len;
        std::nth_element( vec.begin(), beg, vec.end() );
        std::nth_element( beg, end, vec.end() );
    }
    double sum = 0.0, d = 0.0;
    for ( std::vector<_T>::iterator it = beg; it!=end; ++it, d+=1.0 )
        sum += *it;
    return sum/d;
}
// fill the vector and compute for each channel separately.

Here tail and head the same portion for simplicity.

mainactual
  • 1,625
  • 14
  • 17