I am trying to read an RGB image and convert it to 1D array. When printing the values, I understand that the logic I have written for conversion is not correct. Can someone help me with this please? Also, when I am trying to define image_array, it is giving me an error.
Expression must have constant value.
I have posted the code below.
//reading the images
Mat img_rev = imread("C:/Users/20181217/Desktop/images/imgs/output_rev.png");
cvtColor(img_rev, img_rev, COLOR_BGR2RGB);
//get the image height,width and number of channles to convert into an array
int const img_height = img_rev.rows;
int const img_width = img_rev.cols;
int const img_channels = img_rev.channels();
//printing out the dimenesions
cout << img_height << ", " << img_width << ", " << img_channels; //256,512,3
//starting the conversion to array
uchar image_array[256 * 512 * 3];
//uchar image_array[img_rev.rows * img_rev.cols * img_rev.channels()]; error:expression must have a constant value
//uchar image_array[img_height * img_width * img_channels]; error:expression must have a constant value
for (int i = 0; i < img_rev.rows; i++)
{
for (int j = 0; j < img_rev.cols; j++)
{
if(i==200 && j==200)
cout << endl << "value at (200,200) of green in MAT is :" << (int)img_rev.at<Vec3b>(i, j)[1] << endl; //printing the value at (200,200) of green channel
}
}
//conversion from image to 1d array
for (int i = 0; i < img_rev.rows; i++)
{
for (int j = 0; j < img_rev.cols; j++)
{
image_array[(i*img_rev.rows) + (j * 3)] = img_rev.at<Vec3b>(i, j)[0]; //R
image_array[(i*img_rev.rows) + (j * 3) + 1] = img_rev.at<Vec3b>(i, j)[1]; //G
image_array[(i*img_rev.rows) + (j * 3) + 2] = img_rev.at<Vec3b>(i, j)[2]; //B
}
}
cout << endl << "value at (200,200) of green in array is :" << (int)image_array[(200*img_rev.cols) + 200 +1];
cout << endl << "done";
waitKey(100000);
I would be really grateful if someone can provide me some assistance with this.
Thanks in advance.
If the array does not have a fixed-size, like in your example, you should use dynamically allocated array. Declaring uchar* image_array = new uchar[img_rev.total() * img_rev.channels()] can be a solution but you need to delete it manually to free memory when it is used no more.
In order not to deal with keeping track of deletion, I suggest using std::vector.
std::vector<uchar> image_array(img_rev.total() * img_rev.channels());
And you can use your method once the array is dynamically allocated.
There is an easier method for converting cv::Mat to std::vector. In you case, since img_rev is created by imread, we are sure that the data is continuous, so we can do the following:
std::vector<uchar> image_array = img_rev.data;
Related
I'm trying to create a convolution function but I'm having trouble during the access to the kernel data (cv::Mat).
I create the 3x3 kernel:
cv::Mat krn(3, 3, CV_32FC1);
krn.setTo(1);
krn = krn/9;
And I try to loop over it. Next the image Mat will be the image to which I want to apply the convolution operator and output will be the result of convolution:
for (int r = 0; r < image.rows - krn.rows; ++r) {
for (int c = 0; c < image.cols - krn.cols; ++c) {
int sum = 0;
for (int rs = 0; rs < krn.rows; ++rs) {
for (int cs = 0; cs < krn.cols; ++cs) {
sum += krn.data[rs * krn.cols + cs] * image.data[(r + rs) * image.cols + c + cs];
}
}
output.data[(r+1)*src.cols + c + 1]=sum; // assuming 3x3 kernel
}
}
However the output is not as desired (only randomic black and white pixel).
However, if I change my code this way:
for (int r = 0; r < image.rows - krn.rows; ++r) {
for (int c = 0; c < image.cols - krn.cols; ++c) {
int sum = 0;
for (int rs = 0; rs < krn.rows; ++rs) {
for (int cs = 0; cs < krn.cols; ++cs) {
sum += 0.11 * image.data[(r + rs) * image.cols + c + cs]; // CHANGE HERE
}
}
output.data[(r+1)*src.cols + c + 1]=sum; // assuming 3x3 kernel
}
}
Using 0.11 instead of the kernel values seems to give the correct output.
For this reason I think I'm doing something wrong accessing the kernel's data.
P.S: I cannot use krn.at<float>(rs,cs).
Thanks!
Instead of needlessly using memcpy, you can just cast the pointer. I'll use a C-style cast because why not.
cv::Mat krn = 1 / (cv::Mat_<float>(3,3) <<
1, 2, 3,
4, 5, 6,
7, 8, 9);
for (int i = 0; i < krn.rows; i += 1)
{
for (int j = 0; j < krn.cols; j += 1)
{
// to see clearly what's happening
uint8_t *byteptr = krn.data + krn.step[0] * i + krn.step[1] * j;
float *floatptr = (float*) byteptr;
// or in one step:
float *floatptr = (float*) (krn.data + krn.step[0] * i + krn.step[1] * j);
cout << "krn.at<float>(" << i << "," << j << ") = " << (*floatptr) << endl;
endl;
}
}
krn.at<float>(0,0) = 1
krn.at<float>(0,1) = 0.5
krn.at<float>(0,2) = 0.333333
krn.at<float>(1,0) = 0.25
krn.at<float>(1,1) = 0.2
krn.at<float>(1,2) = 0.166667
krn.at<float>(2,0) = 0.142857
krn.at<float>(2,1) = 0.125
krn.at<float>(2,2) = 0.111111
Note that pointer arithmetic may not be obvious. if you have a uint8_t*, adding 1 moves it by one uint8_t, and if you have a float*, adding 1 moves it by one float which is four bytes. The step[] contains offsets expressed in bytes.
Consult the documentation for details, which include information on the step[] array that contains the strides/steps to calculate the offset given a tuple of indices into the matrix.
cv::Mat::data is pointer of type uchar.
By data[y * cols + x] you access some byte of stored float values in krn. To get full float values use at method template:
krn.at<float>(rs,cs)
Consider changing type of sum variable to be real. Without this, you may lose partial results when calculating convolution .
So, if you cannot use at, just read 4 bytes from data pointer:
float v = 0.0;
memcpy(&v, krn.data + (rs * krn.step + cs * sizeof(float)), 4);
step - means total bytes occupied by one line in mat.
essentially, I'm making a class that takes a BMP file for the constructor. From THIS POST I get all of the header data out of the way and then read RGB data into a vector. I then calculate the intensities as 0.25R + 0.5G + 0.25B. I put these numbers into a space-separated file, line by line. With the original above my result below and Using GNUPlot to open and plot the image gives me this result.
original
distortion
As you can see, the right side of the image is consistently being wrapped around further as the image is written to file (or somewhere before this process). I've pasted the code below, any help?
std::vector<char> MImage::readBMP(std::string const file){
static constexpr size_t HEADER_SIZE = 54;
std::ifstream bmp(file, std::ios::binary);
std::array<char, HEADER_SIZE> header;
bmp.read(header.data(), header.size());
auto fileSize = *reinterpret_cast<uint32_t*>(&header[2]);
auto dataOffset = *reinterpret_cast<uint32_t*>(&header[10]);
auto width = *reinterpret_cast<uint32_t*>(&header[18]);
auto height = *reinterpret_cast<uint32_t*>(&header[22]);
auto depth = *reinterpret_cast<uint16_t*>(&header[28]);
/*
std::cout << "fileSize: " << fileSize << std::endl;
std::cout << "dataOffset: " << dataOffset << std::endl;
std::cout << "width: " << width << std::endl;
std::cout << "height: " << height << std::endl;
std::cout << "depth: " << depth << "-bit" << std::endl;
*/
std::vector<char> img(dataOffset - HEADER_SIZE);
//bmp.read(img.data(), img.size());
auto dataSize = ((width * 3 + 3) & (~3)) * height;
img.resize(dataSize);
bmp.read(img.data(), img.size());
char temp = 0;
for (int i = dataSize - 4; i >= 0; i -= 3)
{
temp = img[i];
img[i] = img[i + 2];
img[i + 2] = temp;
}
// Convert to intensity
int k = 0;
int size = (int)img.size();
for (int j = 0; k+2 < size; j++)
{
//0.25B + 0.5G + 0.25R
img[j] = ((abs(img[k]) >> 2) + (abs(img[k + 1]) >> 1) + (abs(img[k + 2]) >> 2));
//OutputDebugStringA((to_string(img[j]) + "\n").c_str());
k += 3;
}
img.resize(dataSize / 3);
//OutputDebugStringA((to_string(img.size()) + "\n").c_str());
int a, b, c = 0;
//Testing #img data
ofstream TestPic;
TestPic.open("testpic.txt");
for (a = 0; a < HEIGHT; a++) {
for (b = 0; b < WIDTH; b++) {
TestPic << (int)img[c];
if (b < WIDTH-1) {
TestPic << " ";
}
c++;
}
TestPic << "\n";
}
TestPic.close();
return img; }
GNUPlot command: plot [0:630] [0:354] 'testpic.txt' matrix with image pixels
The problem you are seeing is caused by improper data alignment. Each scanline of .bmp file must have a byte-size divisible by 4. You are calculating the input data size correctly with this line:
auto dataSize = ((width * 3 + 3) & (~3)) * height;
However, while converting the img array you do not compensate/throw away the padding at the end of each scanline.
The best advice is either to use a standard bmp loader/converter like the one in STB libraries (the stb_image.h file) or, if you want to do it yourself, allocate the other array (img2 or something like that) and iterate img array scanline by scanline, writing greyscale values to img2.
By the way, this answer, which you have mentioned, contains a solution for your exact problem (it is called the "padding fix").
I am looking for the fastest way of converting 3 channels RGB frame to 1 channel picture in openCV. But I need to concatenate all three colors (R, G, B) of the pixel into one 32-bit value.
Every pixel should consist for an example:
pixel[0:31]= 01001001 11110000 11111111 00000000
The first 8 bits are RED color from the frame (at the same position in frame), second 8 bits are from green color and third 8 bits are from blue, last 8 bits are not important.
I tried this:
for (y = 100; y < 500; y++){
for (x = 100; x < 500; x++) {
int pixel =((edges.at<Vec3b>(y, x)[0])<<16)|
((edges.at<Vec3b>(y, x)[1])<<8)|
(edges.at<Vec3b>(y, x)[2]);
}}
But this is to slow, because I need to go through every pixel in the frame.
Thanks
Tried some variants:
Method 0a : as in the question, #Sveva
Method 0b : as in the question, with for loops inverted, #Miki
Method 1 : querying value from mat only once using indices, #Miki
Method 2 : querying value from mat using pointers, #Miki
Method 3 : converting to BGRA and memcpy, #RyanP NOTE: works only if mat isContinuos().
Results (time in milliseconds)
isContinuos? 1
Method 0a: 113.704 0x1020300
Method 0b: 20.0975 0x1020300
Method 1: 20.1939 0x1020300
Method 2: 15.7434 0x1020300
Method 3: 22.5592 0xff030201
Considerations
Inverting the for loops has a major speedup, because OpenCV Mat are row-major ordered.
The fastest method is Method 2, using pointers. Method 1 is slightly slower, but probably more readable.
Method 3 is quite fast, but a single memcpy works only if the matrix isContinuos(). If not the case, you need to loop on each row, and memcpy each row, and this is going to be (only a little) slower.
NOTE
OpenCV stores BGR values (not RGB). Methods 0a, 0b, 1 and 2 output the values as: B G R 0. You just need to swap the index 0 and 2 to get R G B 0. For Method 3, you need to use cvtColor with parameter COLOR_BGR2RGBA.
Code
#include <opencv2\opencv.hpp>
#include <vector>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
// Test Image
Mat3b img(2000, 3000, Vec3b(1, 2, 3));
cout << "isContinuos? " << img.isContinuous() << endl;
// Method 0a: method from question. Credits to #Sveto
double tic0a = double(getTickCount());
vector<int> v0a(img.rows * img.cols, 0);
for (int c = 0; c < img.cols; ++c)
{
for (int r = 0; r < img.rows; ++r)
{
v0a[r*img.cols + c] = ((img.at<Vec3b>(r, c)[0]) << 24) |
((img.at<Vec3b>(r, c)[1]) << 16) |
(img.at<Vec3b>(r, c)[2]) << 8;
}
}
double toc0a = (double(getTickCount()) - tic0a) * 1000. / getTickFrequency();
cout << "Method 0a: " << toc0a << "\t\t";;
cout << "0x" << hex << v0a[0] << endl;
// Method 0b: method from question, loops inverted
double tic0b = double(getTickCount());
vector<int> v0b(img.rows * img.cols, 0);
for (int r = 0; r < img.rows; ++r)
{
for (int c = 0; c < img.cols; ++c)
{
v0b[r*img.cols + c] = ((img.at<Vec3b>(r, c)[0]) << 24) |
((img.at<Vec3b>(r, c)[1]) << 16) |
(img.at<Vec3b>(r, c)[2]) << 8;
}
}
double toc0b = (double(getTickCount()) - tic0b) * 1000. / getTickFrequency();
cout << "Method 0b: " << toc0b << "\t\t";
cout << "0x" << hex << v0b[0] << endl;
// Method 1: custom loop with indices
double tic1 = double(getTickCount());
vector<int> v1(img.rows * img.cols, 0);
for (int r = 0; r < img.rows; ++r)
{
for (int c = 0; c < img.cols; ++c)
{
const Vec3b& b = img(r, c);
v1[r*img.cols + c] = (b[0] << 24) | (b[1] << 16) | (b[2] << 8);
}
}
double toc1 = (double(getTickCount()) - tic1) * 1000. / getTickFrequency();
cout << "Method 1: " << toc1 << "\t\t";
cout << "0x" << hex << v1[0] << endl;
// Method 2: custom loop with pointers
double tic2 = double(getTickCount());
vector<int> v2(img.rows * img.cols, 0);
for (int r = 0; r < img.rows; ++r)
{
uchar* p = img.ptr<uchar>(r);
for (int c = 0; c < img.cols; ++c)
{
int val = ((*p) << 24); ++p;
val |= ((*p) << 16); ++p;
val |= ((*p) << 8); ++p;
v2[r*img.cols + c] = val;
}
}
double toc2 = (double(getTickCount()) - tic2) * 1000. / getTickFrequency();
cout << "Method 2: " << toc2 << "\t\t";
cout << "0x" << hex << v2[0] << endl;
// Method 3: using BGRA conversion. Credits #RyanP
// NOTE: works only if img.isContinuos()
double tic3 = double(getTickCount());
Mat4b rgba3;
cvtColor(img, rgba3, COLOR_BGR2BGRA);
vector<int> v3(img.rows * img.cols, 0);
memcpy(v3.data(), rgba3.data, img.rows * img.cols * sizeof(int));
double toc3 = (double(getTickCount()) - tic3) * 1000. / getTickFrequency();
cout << "Method 3: " << toc3 << "\t\t";
cout << "0x" << hex << v3[0] << endl;
int dummy;
cin >> dummy;
return 0;
}
Use the split and merge channels functions.
The look complicated but are a lot easier than doing it a pixel at a time.
See stackoverflow.com/questions/14582082/merging-channels-in-opencv for sample
Here is the code I got from a reputable source but its not working:
Mat img = imread("/home/w/d1",CV_LOAD_IMAGE_COLOR);
unsigned char *input = (unsigned char*)(img.data);
int i,j,r,g,b;
for(int i = 0;i < img.rows ;i++){
for(int j = 0;j < img.cols ;j++){
b = input[img.step * j + i ] ;
g = input[img.step * j + i + 1];
r = input[img.step * j + i + 2];
cout << b << g <<r;
}
}
When I run it the output isn't the same as when I do a cout << img;
I think it might be something to do with the new C++ interface. If that is so and I'm supposed to use the step1 method, can some one show me how to update my code to access BGR values with the step1 method. I couldn't find online doc. on how to use step1. Thanks in advance for any help.
your code looks basically right, but as far as I see, your ordering is wrong.
You must access
value = data[img.step*ROW + COL] but you have row and col switched.
edit: in addition you need to multiply the COL with the number of channels:
value = data[img.step*ROW + #channels*COL + currentChannel]
try:
Mat img = imread("/home/w/d1",CV_LOAD_IMAGE_COLOR);
unsigned char *input = (unsigned char*)(img.data);
int i,j,r,g,b;
for(int i = 0;i < img.rows ;i++){
for(int j = 0;j < img.cols ;j++){
b = input[img.step * i + 3*j ] ; // 3 == img.channels()
g = input[img.step * i + 3*j + 1];
r = input[img.step * i + 3*j + 2];
cout << b << g <<r;
}
}
maybe you want to avoid the 'raw data' approach:
for(int i=0; i<img.rows; i++) {
for(int j=0; j<img.cols; j++) {
Vec3b pix = img.at<Vec3b>(i,j);
cout << int(pix[0]) << int(pix[1]) << int(pix[2]) << endl;
}
}
Been stuck on this a few days now, I'm going out my mind. Essentially I have converted a 2D array of values (an image, I know there are easier ways to achieve this but I have explicit requirements) into a 1D array. I can rotate the 2D array with ease. I'm having trouble with the rotating of the 1D version of the array, and I think it's down to a single line of algorithm being incorrect.
The code I'm using for rotating the array is:
cout << "\nTransfer from 2D to dynamic 1D array and print details\n\n";
myImage * p_myImage = new myImage[total];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int offset = width * y + x;
p_myImage[offset].pixel = array2D[y][x];
cout << p_myImage[offset].pixel << " ";
}
cout << "\n";
}
//new pointer to copy to
myImage * p_myImage2 = new myImage[total];
cout << "\nRotate Matrix through 1D array\n\n";
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
int offset = height * x + y;
//int offset = width * y + x ;
p_myImage2[offset].pixel = p_myImage[height-1+x].pixel;
cout << p_myImage2[offset].pixel << " ";
}
cout << "\n";
}
This should rotate it clockwise:
p_myImage2[offset].pixel = p_myImage[width * (height - 1 - y) + x].pixel;