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));
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.
-> 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;
}
}
-> 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.
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.
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 ...
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 .
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With