15

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.

Community
  • 1
  • 1
herohuyongtao
  • 49,413
  • 29
  • 133
  • 174

4 Answers4

12

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.

Michael Burdinov
  • 4,348
  • 1
  • 17
  • 28
  • And if you want it back, what reshape you need? – thedarkside ofthemoon May 07 '14 at 13:30
  • +1 for the more elegant approach, further note that 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. And we can rely on `Mat::isContinuous()` to check that. – herohuyongtao May 07 '14 at 13:59
  • 1
    @thedarkside ofthemoon, reshape is not changing original header of matrix, but create new one and return it. As a result you don't need to do anything to get it back. – Michael Burdinov May 07 '14 at 14:14
  • 2
    +1 I really wonder though what will be the efficiency of this implementation, and more in general why people working in computer vision are so afraid of writing functions going through a matrix and changing bytes values. – Antonio May 08 '14 at 11:14
  • @Antonio, the code is very short and clean but I guess time performance will indeed be quite terrible. OpenCV is really inefficient when it should perform operation on matrices with 1 pixel in a row. I will update my answer to make it clearer. Thanks. – Michael Burdinov May 08 '14 at 13:09
7
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

Antonio
  • 19,451
  • 13
  • 99
  • 197
  • 1
    your img.ptr is lacking the braces, and CV_8UC4 (rgba) was explicitly specified in the post above. and what is step ? (yes, my downvote) – berak May 07 '14 at 09:03
  • type braces is not mandatory, this function stub generalizes on all image types. Thank you for noticing the missing * (and the extra `const`, by the way), but that's no reason to downvote. Step is the step in byte to jump from one alpha byte to the next. http://docs.opencv.org/modules/core/doc/basic_structures.html#mat-ptr – Antonio May 07 '14 at 09:10
  • Not true, but indeed it was wrong as I could not do `*p_row = value;`. However, these are errors easily removed after compiling. – Antonio May 07 '14 at 09:12
  • @berak CV8_UC4 isn't it BGRA? OpenCV stores images as BGR in cv::Mat – thedarkside ofthemoon May 07 '14 at 12:02
  • @thedarkside ofthemoon usually, images come as bgr(CV_8UC3) in opencv, but herohuyongtao's question above is about how to set the 4th channel in a BGRA(CV_8UC4) image – berak May 07 '14 at 12:58
2

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;
}
marol
  • 4,034
  • 3
  • 22
  • 31
  • Thanks. Actually I'm expecting for more elegant way to do this. Doesn't OpenCV provide the API to get specific channel? – herohuyongtao May 07 '14 at 07:42
  • 1
    As far as I it does not. Of course it is not so elegant, but might be faster than splitting and merging the channels separately. – marol May 07 '14 at 08:02
  • 3
    This does not take into account that stride might be different from cols: `img.data + (y * img.cols + x)` should be `img.data + (y * img.step1() + x)` – Antonio May 07 '14 at 08:56
  • Thanks, but you are partially right - I don't take into account the stride, but using step1() does not solve this problem because step1() is just (number_of_bytes_in_row / number_of_bytes_per_pixel, in our 8_UC4 case step1() is 4000) so if the matrix is not continuous, using step1() does not help at all. And second, of you would like to use step1(), the line should look like: *(img.data + y * img.step1() + x * img.channels() + N) = newVal; – marol May 08 '14 at 07:47
  • step1() indeed does not help for SparseMat, but in all other cases (row padding, cv::Mat selected from a bigger cv::Mat). Your code correction is right, thanks. – Antonio May 08 '14 at 11:04
1

Simple algorithm:

void SetChannel(Mat mat, uint channel, uchar value)
{
    const uint channels = mat.channels();
    if (channel > channels - 1)
        return;

    uchar * data = mat.data;
    uint N = mat.rows * mat.step / mat.elemSize1();

    for (uint i = channel; i < N; i += channels)
        data[i] = value;
}
Second Person Shooter
  • 14,188
  • 21
  • 90
  • 165