6
votes

How can we get the mean of an input RGB image(3 dimensional Mat object) so that we get a gray image? The cvtColor() function of OpenCV converts the image to gray based on a pre-existing formula. I want to get the mean of all three channels and store the resultant image in another matrix. The cv::mean() function in OpenCV returns the scalar mean of all input channels.

Were this Python, with img being a RGB image, img.mean(2) would get me what I want. Successive calls of the addWeighted() function and using gray= blue/3.0 + red/3.0 +green/3.0 [ After splitting channels] yielded different results when compared with Python.

Is there anything analogous to img.mean(2) in C++ or the OpenCV library of C++?

2

2 Answers

7
votes

Is there anything analogous to img.mean(2) in C++ or the OpenCV library of C++?

No, but you can easily compute that. There are a few ways of doing it:

  1. Loop over all the image, and set each value as the mean of the input pixel values. Take care of computing the intermediate values for the mean on a type with more capacity and accuracy than uchar (here I used double) or you may end up with wrong results. You can also optimize the code further, e.g. see this question and its answers. You just need to change the function computed in the inner loop to compute the mean.

  2. Use reduce. You can reshape you 3 channel matrix of size rows x cols to be a matrix of shape ((rows*cols) x 3), and then you can use the reduce operation with parameter REDUCE_AVG to compute the average row-wise. Then reshape the matrix to correct size. reshape operation is very fast, since you just modify the header without affecting the stored data.

  3. Use matrix operations to sum channels. You can use split to get the matrix for each channel, and sum them. Take care to not saturate your values while summing up! (Thanks to beaker for this one.)

You can see that the first approach is faster with small matrices, but as soon as the size increase, the second approach performs much better since you take advantage of OpenCV optimizations. The third approach works surprisingly well (thanks to matrix expressions).

Some numbers, time in ms. Time may vary on you computer depending on OpenCV optimizations enabled. Run in release!

Size  : 10x10   100x100   1000x1000   10000x10000
Loop  : 0.0077  0.3625    34.82       3456.71
Reduce: 1.44    1.42      8.88        716.75
Split : 0.1158  0.0656    2.26304     246.476

Code:

#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main()
{
    Mat3b img(1000, 1000);
    randu(img, Scalar(0, 0, 0), Scalar(10, 10, 10));


    {
        double tic = double(getTickCount());
        Mat1b mean_img(img.rows, img.cols, uchar(0));
        for (int r = 0; r < img.rows; ++r) {
            for (int c = 0; c < img.cols; ++c) {
                const Vec3b& v = img(r, c);
                mean_img(r, c) = static_cast<uchar>(round((double(v[0]) + double(v[1]) + double(v[2])) / 3.0));
            }
        }
        double toc = (double(getTickCount()) - tic) * 1000.0 / getTickFrequency();
        cout << "Loop: " << toc << endl;
    }

    {
        double tic = double(getTickCount());

        Mat1b mean_img2 = img.reshape(1, img.rows*img.cols);
        reduce(mean_img2, mean_img2, 1, REDUCE_AVG);
        mean_img2 = mean_img2.reshape(1, img.rows);

        double toc = (double(getTickCount()) - tic) * 1000.0 / getTickFrequency();
        cout << "Reduce: " << toc << endl;
    }

    {
        double tic = double(getTickCount());

        vector<Mat1b> planes;
        split(img, planes);
        Mat1b mean_img3;
        if (img.channels() == 3) {
            mean_img3 = (planes[0] + planes[1] + planes[2]) / 3.0;
        }

        double toc = (double(getTickCount()) - tic) * 1000.0 / getTickFrequency();
        cout << "Split: " << toc << endl;
    }


    getchar();
    return 0;
}
-1
votes

mean()

Calculates an average (mean) of array elements.

C++: Scalar mean(InputArray src, InputArray mask=noArray())

Python: cv2.mean(src[, mask]) → retval

C: CvScalar cvAvg(const CvArr* arr, const CvArr* mask=NULL )

Python: cv.Avg(arr, mask=None) → scalar

Parameters: src – input array that should have from 1 to 4 channels so that the result can be stored in Scalar_ . mask – optional operation mask.

The function mean calculates the mean value M of array elements, independently for each channel, and return it:

When all the mask elements are 0’s, the functions return Scalar::all(0) .

Also check this answer how to calculate and use cvMat mean value