How to set given channel of a cv::Mat to a given v

2019-01-19 11:35发布

问题:

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.

回答1:

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.



回答2:

Mat img;
[...]
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
    for (int x = 0; x < cols; x++) {
         *p_row = value;
         p_row += step; //Goes to the next byte to be changed
    }
}

Note: This works both for continuous and uncontinuous matrices, according to the use of the term for opencv: http://docs.opencv.org/modules/core/doc/basic_structures.html#bool%20Mat::isContinuous%28%29%20const



回答3:

What about direct Mat::data access (I'm quite sure setTo() or oother opencv Mat api use similar solution):

template<int N>
void SetChannel(Mat &img, unsigned char newVal) {   
    for(int x=0;x<img.cols;x++) {
        for(int y=0;y<img.rows;y++) {
            *(img.data + (y * img.cols + x) * img.channels() + N) = newVal;
        }
    }
}


int main() {
    Mat img = Mat::zeros(1000, 1000, CV_8UC4);
    SetChannel<3>(img, 120);
    imwrite("out.jpg", img);

    return 0;
}