Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set given channel of a cv::Mat to a given value efficiently without changing other channels?

How to set given channel of a cv::Mat to a given value efficiently without changing other channels? For example, I want to set its fourth channel (alpha channel) value to 120 (i.e. half transparent), something like:

cv::Mat mat; // with type CV_BGRA
...
mat.getChannel(3) = Scalar(120); // <- this is what I want to do

P.S.: My current solution is first split the mat into multiple channels and set the alpha channel, and then merge them back.

P.S.2: I know that I can do this quickly if I also want to change other channels as well by:

mat.setTo(Scalar(54, 154, 65, 120)); 

Update with generalized solution:

Both methods will work for setting all mat values at given channel to given value. And they will work for all matrices whether they are continuous or not.

Method-1 - more efficient

-> based on @Antonio's answer and further improved by @MichaelBurdinov

// set all mat values at given channel to given value
void setChannel(Mat &mat, unsigned int channel, unsigned char value)
{
    // make sure have enough channels
    if (mat.channels() < channel + 1)
        return;

    const int cols = mat.cols;
    const int step = mat.channels();
    const int rows = mat.rows;
    for (int y = 0; y < rows; y++) {
        // get pointer to the first byte to be changed in this row
        unsigned char *p_row = mat.ptr(y) + channel; 
        unsigned char *row_end = p_row + cols*step;
        for (; p_row != row_end; p_row += step)
            *p_row = value;
    }
}

Method-2 - more elegant

-> based on @MichaelBurdinov's answer

// set all mat values at given channel to given value
void setChannel(Mat &mat, unsigned int channel, unsigned char value)
{
    // make sure have enough channels
    if (mat.channels() < channel+1)
        return;

    // check mat is continuous or not
    if (mat.isContinuous())
        mat.reshape(1, mat.rows*mat.cols).col(channel).setTo(Scalar(value));
    else{
        for (int i = 0; i < mat.rows; i++)
            mat.row(i).reshape(1, mat.cols).col(channel).setTo(Scalar(value));
    }
}

P.S.: It's worthy noting that, according to the documentation, matrices created with Mat::create() are always continuous. But if you extract a part of the matrix using Mat::col(), Mat::diag(), and so on, or constructed a matrix header for externally allocated data, such matrices may no longer have this property.

like image 296
herohuyongtao Avatar asked May 07 '14 06:05

herohuyongtao


People also ask

What is a CV :: mat?

In OpenCV the main matrix class is called Mat and is contained in the OpenCV-namespace cv. This matrix is not templated but nevertheless can contain different data types. These are indicated by a certain type-number. Additionally, OpenCV provides a templated class called Mat_, which is derived from Mat.

What does MAT mean in C++?

Mat is basically a class with two data parts : the matrix header (containing information such as the size of the matrix, the method used for storing, at which address is the matrix stored, and so on) and a pointer to the matrix containing the pixel values (taking any dimensionality depending on the method chosen for ...

What is CV_64FC1?

That is, image of type CV_64FC1 is simple grayscale image and has only 1 channel: image[i, j] = 0.5. while image of type CV_64FC3 is colored image with 3 channels: image[i, j] = (0.5, 0.3, 0.7) (in C++ you can check individual pixels as image.at<double>(i, j) ) CV_64F is the same as CV_64FC1 .

What are channels in OpenCV?

There are three channels in an RGB image- red, green and blue. The color space where red, green and blue channels represent images is called RGB color space. In OpenCV, BGR sequence is used instead of RGB. This means the first channel is blue, the second channel is green, and the third channel is red.


1 Answers

If your image is continuous in memory you can use following trick:

mat.reshape(1,mat.rows*mat.cols).col(3).setTo(Scalar(120));

If it is not continuous:

for(int i=0; i<mat.rows; i++)
    mat.row(i).reshape(1,mat.cols).col(3).setTo(Scalar(120));

Edit (thanks to Antonio for the comment):

Note that this code may be the shortest and it is not allocating new memory but it is not efficient at all. It may be even slower than split/merge approach. OpenCV is really inefficient when it should perform operations on non-continuous matrices with 1 pixel in a row. If time performance is important you should use the solution proposed by @Antonio.

Just a minor improvement to his solution:

const int cols = img.cols;
const int step = img.channels();
const int rows = img.rows;
for (int y = 0; y < rows; y++) {
    unsigned char* p_row = img.ptr(y) + SELECTED_CHANNEL_NUMBER; //gets pointer to the first byte to be changed in this row, SELECTED_CHANNEL_NUMBER is 3 for alpha
    unsigned char* row_end = p_row + cols*step;
    for(; p_row != row_end; p_row += step)
         *p_row = value;
    }
}

This saves increment operation for x and one less value in register. On system with limited resources it may give ~5% speedup. Otherwise time performance will be the same.

like image 128
Michael Burdinov Avatar answered Sep 28 '22 04:09

Michael Burdinov