convert image from BGR to ARGB using opencv - c++

I want to convert BGR image to ABGR/ARGB.There are conversion BGR2RGBA in opencv but not BGR2ABGR or BGR2ARGB.
It is possible with opencv or using any other method?

The required operation can be accomplished by swapping the image channels using cv::mixChannels as follows:
cv::Mat bgr, bgra;
//bgr initialization code here...
//.
//.
//.
cv::cvtColor(bgr, bgra, cv::COLOR_BGR2BGRA);
cv::Mat abgr(bgra.size(), bgra.type());
int from_to[] = { 0,3, 1,1, 2,2, 3,0 };
cv::mixChannels(&bgra,1,&abgr,1,from_to,4);
from_to array is the mapping function which specifies which channels from source will be copied to which channels of the destination image. The pairs indicate that channel number 0 of the input will be copied to channel number 3 of the output, 1 to 1, 2 to 2, and channel number 3 will be copied to channel number 0 of the output.
Alternatively, we can split the image channels, swap the required channels and merge again. It can be done as follows:
cv::cvtColor(bgr, bgra, cv::COLOR_BGR2BGRA);
std::vector<cv::Mat> channels_bgra;
cv::split(bgra, channels_bgra);
std::vector<cv::Mat> channels_abgr = { channels_bgra[3], channels_bgra[1], channels_bgra[2], channels_bgra[0] };
cv::merge(channels_abgr, abgr);

OpenCV doesn't support ARGB or ABGR formats, so you will not be able to display it or use some of the functions on it... However, it is possible to create them with split and merge functions of OpenCV. Here is some code to explain what I mean.
cv::Mat src, final_image;
// fill src as you prefer
std::vector<cv::Mat> channels;
cv::split(src, channels); // this will put each channel in a mat in the vector
// swap or add channels in the vector
cv::Mat alpha(src.rows, src.cols, CV_8U, cv::Scalar(255));
channels.push_back(alpha);
std::reverse(channels.begin(), channels.end()); //needs <algorithm>
// merge the channels in one new image
cv::merge(channels, final_image);
This can be done faster (maybe it will be just shorter) with the function mixChannels, but I will say that this one is a little bit more confusing.

Related

Initialize an OpenCV Mat with pixel data in RGBA channel order

Hel lo,
I am writing an effect plug-in for Adobe After Effects in C++, and I need to create a cv::Mat using pixel data that's already in memory. However, when I initialize it using:
cv::Mat in_frame_mat(height, width, CV_8UC4, input->data);
(input->data is a pointer to the first byte), the output is all blue.
I think this is because AE stores pixel data RGBA or ARGB while OpenCV assumes BGRA or ABGR (not sure which).
I know that I could iterate through every pixel and create a new space in memory to store a BGRA representation of the image, then initialize a cv::Mat with that, but this is really performance constrained and I don't want to add unnecessary compute time.
Is there a way to create a cv::Mat using existing pixel data that is stored RGBA?
Thanks!
OpenCV assumes the channel order is BGR (or BGRA).
You can use cv::cvtColor to change your input to BGRA.
The 3rd parameter code should be one of the values from cv::ColorConversionCodes, e.g.: cv::COLOR_RGBA2BGRA (seems to be what you need).
Update:
Following the OP's comment below that his input is actually ARGB (not RGBA):
ARGB to BGRA is not supported by cv::cvtColor.
But you can use cv:mixChannels to do any arbitrary reordering of the channels (the fromTo parameter specifies the reordering).
See below how to use it to convert ARGB to BGRA:
#include <vector>
cv::Mat argb; // initialized from your input data
cv::Mat bgra(argb.size(), argb.type());
std::vector<int> fromTo{ 0,3, 1,2, 2,1, 3,0 }; // ARGB->BGRA: 0->3, 1->2, 2->1, 3->0
cv::mixChannels(argb, bgra, fromTo);
If your OpenCV version does not support this flavor of cv::mixChannels (with std::vector etc.), you can try:
cv::Mat argb; // initialized from your input data
cv::Mat bgra(argb.size(), argb.type());
int fromTo[] = { 0,3, 1,2, 2,1, 3,0 }; // ARGB->BGRA: 0->3, 1->2, 2->1, 3->0
cv::mixChannels(&argb, 1, &bgra, 1, fromTo, 4);
Update 2:
In order to convert ARGB to BGR (i.e. without the alpha channel), you can use cv::mixChannels in the following way:
#include <vector>
cv::Mat argb; // initialized from your input data
cv::Mat bgr(argb.size(), CV_8UC3); // NOTE: the type is CV_8UC3, not argb.type()
std::vector<int> fromTo{ 1,2, 2,1, 3,0 }; // ARGB->BGR: 1->2, 2->1, 3->0
cv::mixChannels(argb, bgr, fromTo);

Attempting to display image using pixel values in OpenCV. Only 1/3rd of Image shows

I'm attempting to read the pixels in an image and convert them into another format by iterating through the pixels.
After my conversion I only seem to be getting 1/3rd of the image and I'm certain it's because of the way I'm accessing the pixels using the .at() function.
I'm reading in the following image:
Mat image = imread("cameraman.jpg");
I then iterate through the images rows and columns:
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
placeGrayValue((double)image.at<uchar>(i, j));
}
}
Note: placedGrayValue() is just a placeholder here so that I can share only the code that is relevant.
The resulting image is only the first third of the image:
You're loading your image with cv::imread, which with default value (cv::IMREAD_COLOR) will load it as a 3 channel image of type CV_8UC3 (aka cv::Mat3b).
If your original image is grayscale, when loading as a 3 channel image you have the same intensity value for each channel.
So when you scan the image you should access pixels with .at<cv::Vec3b>(...).
If you want to copy only the first channel to the placeGrayValue matrix you should do it as:
placeGrayValue((double)image.at<cv::Vec3b>(i, j)[0]);
^^^^^^^^^ ^^^
3 channel first channel
If your input is not a grayscale image, then you shouldn't just copy the first channel, since the grayscale value is a linear combination of the three R,G,B channels.
So it's better to first convert to grayscale, and then copy:
cv::Mat grayscale;
cv::cvtColor(image, grayscale, cv::COLOR_BGR2GRAY);
...
placeGrayValue((double)grayscale.at<uchar>(i, j));
^^^^^
1 channel
Or you can load the image already as a grayscale image:
Mat grayscale = imread("cameraman.jpg", cv::IMREAD_GRAYSCALE);
At the end, you want to have placeGrayValue with the grayscale values as double.
You should not scan the image for this kind of easy operations. You can just:
cv::Mat placeGrayValue;
grayscale.convertTo(placeGrayValue, CV_64F);
^^^^^^
to double type
Summing up:
cv::Mat grayscale = cv::imread("cameraman.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat placeGrayValue;
grayscale.convertTo(placeGrayValue, CV_64F);
I'll post what ended up working as an answer, though it makes little sense to me and I'd still like to understand why.
The image has 3 channels. When iterate through an image using a for loop and extract pixel data with (double)image.at<uchar>(i, j) it goes through each channel as if they were individual pixels.
The solution (at least with this grayscale image) is to iterate and multiply by 3. In other words, (double)image.at<uchar>(i*3, j) ended up giving me the full image.

Splitting a color image into three channels using Open CV and C++?

I am new to Open CV, so please forgive me if my question sounds stupid. So, i was studying about this new concept of splitting a BGR channel to individual channels using the split function. I was reading this article(http://answers.opencv.org/question/37132/i-want-to-split-and-show-r-b-g-pictures-why-does-it-not-work/) and i could not understand the code. So, please can anyone explain me the following line of code as i am really wanted to understand the concept.
I did not understand the create blue channel part at all. Please can anyone explain me a bit?
src = imread("pic.png");
vector<mat> spl(3);
split(src,spl);
Mat empty_image = Mat::zeros(src.rows, src.cols, CV_8UC1);
Mat result_blue(src.rows, src.cols, CV_8UC3); // notice the 3 channels here!
// Create blue channel
Mat in1[] = { spl[0], empty_image, empty_image };
int from_to1[] = { 0,0, 1,1, 2,2 };
mixChannels( in1, 3, &result_blue, 1, from_to1, 3 );
imshow("blue image", result_blue);
What the code does is split the color image into 3 grayscale images, holding the intensities of the red, green and blue.
Then the code takes the blue channel and constructs a color image with a zero red and green, so that when you show it, it will show as bluish, and not just grayscale.
Th OpenCV Split Function:
cv:split(src, spl)
Takes a 3 channel 24bit (8 bit for each channel) of type CV_8UC3 and split it into type CV_8UC1 (GrayScale), that is a single channel image or type R, G and B separately.
If you take as an example a Green channel image from spl1 vector and do cv::imshow, you should notice that the green color from the original RGB image will appear as high intensity value in only Green channel image. Likewise for any other channel.
mixChannels( in1, 3, &result_blue, 1, from_to1, 3 );
mixChannel is basically, a function that copies some channel of source image to the new destination image. When using the function you need to specify the the number of channel you need to mix which in your case is 3.
cv::Mat result_blue(src.rows, src.cols, CV_8UC3);
Note that this variable of type cv::Mat is memory to hold the output image where the channels from src are mixed to the destination image.
1 specifies the number of matrix or image you want in the final output image, that is in cv::Mat result_blue.
int from_to1[] = { 0,0, 1,1, 2,2 };
This array specifies which channel from src needs to be mixed with which channel in the destination. That is 0 will be copied to 0 and so on.
The last param 3 in mixChannel specifies the number of channel pairs in the destination image.
You may also look at this function of merging channels.
The cv::merge() function on the other than takes multiple single channel image and merge it to produce a high level. Note that cv::merge also takes a int, the second param which specifies the number of channels you need to merge.
Lastly, I would suggest that you play with the function to understand them well.

How to Convert to ARGB format

I am trying to convert a RGB image to ARGB format. Is there any way to this in openCV? As far as I know, it is possible to convert the image to RGBA, but am not sure about ARGB.
This is what I am doing currently:
cv::Mat argb(myImage.rows, myImage.cols, CV_8UC4);
cv::Mat alpha(myImage.rows, myImage.cols, CV_8UC1);
cv::Mat in[] = { myImage, alpha };
int from_to[] = { 0,1, 1,2, 2,3, 3,0 };
cv::mixChannels( in, 2, &argb, 1, from_to, 4 );
myImage is in 8uc3 format, which is my input image. But I need to change the format to ARGB for another application.
As far as i know cvtColor function doesn't provide such convertion, so you need to write it on your own - look at the source code of cvtColor function, your convertion will be quite similar to CV_BGR2RGB - you just need to switch first and last channel.

Access to each separate channel in OpenCV

I have an image with 3 channels (img) and another one with a single channel (ch1).
Mat img(5,5,CV_64FC3);
Mat ch1 (5,5,CV_64FC1);
Is there any efficient way (not using for loop) to copy the first channel of img to ch1?
In fact, if you just want to copy one of the channels or split the color image in 3 different channels, CvSplit() is more appropriate (I mean simple to use).
Mat img(5,5,CV_64FC3);
Mat ch1, ch2, ch3;
// "channels" is a vector of 3 Mat arrays:
vector<Mat> channels(3);
// split img:
split(img, channels);
// get the channels (dont forget they follow BGR order in OpenCV)
ch1 = channels[0];
ch2 = channels[1];
ch3 = channels[2];
There is a function called cvMixChannels. You'll need to see implementation in the source code, but I bet it is well optimized.
You can use split function and then put zeros to the channels u want to ignore. This will result dispalying one channels out of three. See below..
For example:
Mat img, chans[3];
img = imread(.....); //make sure its loaded with an image
//split the channels in order to manipulate them
split(img, chans);
//by default opencv put channels in BGR order , so in your situation you want to copy the first channel which is blue. Set green and red channels elements to zero.
chans[1]=Mat::zeros(img.rows, img.cols, CV_8UC1); // green channel is set to 0
chans[2]=Mat::zeros(img.rows, img.cols, CV_8UC1);// red channel is set to 0
//then merge them back
merge(chans, 3, img);
//display
imshow("BLUE CHAN", img);
cvWaitKey();
You can access a specific channel, it works faster than the split operation
Mat img(5,5,CV_64FC3);
Mat ch1;
int channelIdx = 0;
extractChannel(img, ch1, channelIdx); // extract specific channel
// or extract them all
vector<Mat> channels(3);
split(img, channels);
cout << channels[0].size() << endl;
A simpler one if you have a RGB with 3 channels is cvSplit() if i'm not wrong, you have less to configure... (and i think it is also well optimized).
I would use cvMixChannel() for "harder" tasks... :p (i know i am lazy).
here is the documentation for cvSplit()