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);
Related
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.
I have an algorithm that does some stuff. Among them, there is a conversion that works fine if I'm working on a CV_8UC3 image but goes wrong if the file type is C_16UC3.
This is some code:
//new image is created
Mat3w img(100,100,Vec3w(1000,0,0));
//Image Conversion - ERROR!
cv::Mat inputSource;
//saving the image here will work
img.convertTo(inputSource, CV_64FC3);
//saving the image here will not work -> black image
The problem is that the CV_16UC3 image's processing result is an image of the right dimensions but fully black.
The problem is in the conversion because saving the image right before will give a legit one while saving it right after will give an almost completely white one.
EDIT:
I made some changes: cut off some useless code and added the inputSource declaration.
Now, while I was trying stuff, I arrived at the conclusion that either I haven't understood the CV Types, or something strange is happening.
I always thought that the number in the type was indicating the number of bits per channel. So, in my head, CV_16UC3 is a 3 channel with 16bits per channel. That idea is strengthened by the fact that the image I save during as tests (before the img.convertTo) actually had matching bits per channel number. The strange thing, is that the saved inputSource (type CV_64FC3) is an 8bpc image.
What's am I missing?
You get confused with the way imwrite and imread work in OpenCV. From the OpenCV documentation
imwrite
The function imwrite saves the image to the specified file. The image format is chosen based on the filename extension (see imread() for the list of extensions). Only 8-bit (or 16-bit unsigned (CV_16U) in case of PNG, JPEG 2000, and TIFF) single-channel or 3-channel (with ‘BGR’ channel order) images can be saved using this function. If the format, depth or channel order is different, use Mat::convertTo() , and cvtColor() to convert it before saving. Or, use the universal FileStorage I/O functions to save the image to XML or YAML format.
imread
The function imread loads an image from the specified file and returns it. Possible flags are:
IMREAD_UNCHANGED : If set, return the loaded image as is (with alpha channel, otherwise it gets cropped).
IMREAD_GRAYSCALE : If set, always convert image to the single channel grayscale image.
IMREAD_COLOR : If set, always convert image to the 3 channel BGR color image.
IMREAD_ANYDEPTH : If set, return 16-bit/32-bit image when the input has the corresponding depth, otherwise convert it to 8-bit.
IMREAD_ANYCOLOR : If set, the image is read in any possible color format.
So for your case, CV_16U are saved without conversion, while CV_64F is converted and saved as CV_8U. If you want to store double data, you should use FileStorage.
You should also take care to use imread the image with the appropriate flag.
This example should clarify:
#include <opencv2\opencv.hpp>
using namespace cv;
int main()
{
// Create a 16-bit 3 channel image
Mat3w img16UC3(100, 200, Vec3w(1000, 0, 0));
img16UC3(Rect(0, 0, 20, 50)) = Vec3w(0, 2000, 0);
// Convert to 64-bit (double) 3 channel image
Mat3d img64FC3;
img16UC3.convertTo(img64FC3, CV_64FC3);
// Save to disk
imwrite("16UC3.png", img16UC3); // No conversion
imwrite("64FC3.png", img64FC3); // Converted to CV_8UC3
FileStorage fout("64FC3.yml", FileStorage::WRITE);
fout << "img" << img64FC3; // No conversion
fout.release();
Mat img_maybe16UC3_a = imread("16UC3.png" /*, IMREAD_COLOR*/); // Will be CV_8UC3
Mat img_maybe16UC3_b = imread("16UC3.png", IMREAD_ANYDEPTH); // Will be CV_16UC1
Mat img_maybe16UC3_c = imread("16UC3.png", IMREAD_UNCHANGED); // Will be CV_16UC3
Mat img_maybe64FC3_a = imread("64FC3.png" /*, IMREAD_COLOR*/); // Will be CV_8UC3
Mat img_maybe64FC3_b = imread("64FC3.png", IMREAD_ANYDEPTH); // Will be CV_8UC1
Mat img_maybe64FC3_c = imread("64FC3.png", IMREAD_UNCHANGED); // Will be CV_8UC3
Mat img_mustbe64FC3;
FileStorage fin("64FC3.yml", FileStorage::READ);
fin["img"] >> img_mustbe64FC3; // Will be CV_64FC3
fin.release();
return 0;
}
I am using openCV for the first time. I am using openCV3 and XCode to code it. I want to create a 16 bit grayscale image but I want to the data I have is defined such that 4000 is the pixel value for white and 0 for black. I have the information for these pixels in an array of type int. How can I create a Mat and assign the values in the array to the Mat?
short data[] = { 0,0,4000,4000,0,0,4000, ...};
Mat gray16 = Mat(h, w, CV_16S, data);
again, the types must match. for 16bit, you need CV_16S and a shortarray, for 8bit CV_8U and a uchar* array, for float CV_32S and a float* ....
You can create your Mat with
cv::Mat m(rows, cols, CV_16UC1);
but to my knowledge, there is no way to define a custom value for "white", you'll have to multiply m with std::numeric_limits::max / 4000. However, this is only necessary when displaying the image.
A lookup-table could do the same (potentially slower), see cv::LUT. However, it appearently only supports 8-bit images.
edit: OK, I missed the part about assigning existing array values; see berak's answer. I hope the answer is still useful.
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.
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.