Opencv: convert RGB matrix to 1d array

5.2k Views Asked by At

How can I access an RGB mat as a 1D array? I looked at the documentation but couldn't find how the 3 channel data is laid out in that case.

I'm trying to loop over each pixel with 1 for loop going from n=0 to n = img.rows*img.cols - 1, and access R, G, and B values at each pixel.

Any help would be greatly appreciated.

3

There are 3 best solutions below

0
On BEST ANSWER

I don't really understand why you really need only 1 loop, so I will propose you several options (including 1 or 2 for-loops) that I know by experience to be efficient.

If you really want to iterate over all the values with only one loop in a safe way, you can reshape the matrix and turn a 3-channel 2D image into a 1-channel 1D array using cv::Mat::reshape(...) (doc):

cv::Mat rgbMat = cv::imread(...); // Read original image

// As a 1D-1 channel, we have 1 channel and 3*the number of pixels samples
cv::Mat arrayFromRgb = rgbMat.reshape(1, rgbMat.channels()*rgbMat.size().area());

There are two caveats:

  • reshape() returns a new cv::Mat reference, hence its output needs to be assigned to a variable (it won't operate in-place)
  • you are not allowed to change the number of elements in the matrix.

OpenCV stores the matrix data in row-major order. Thus, an alternative is to iterate over the rows by getting a pointer to each row start. This way, you will not do anything unsafe because of possible padding data at the end of the rows:

cv::Mat rgbMat = cv::imread(...);

for (int y = 0; y < rgbMat.size().height; ++y) {

   // Option 1: get a pointer to a 3-channel element
   cv::Vec3b* pointerToRgbPixel = rgbMat.ptr<cv::Vec3b>(y);

   for (int x = 0; x < rgbMat.size().width; ++x, ++pointerToRgbPixel) {

       uint8_t blue = (*pointerToRgbPixel )[0];
       uint8_t green = (*pointerToRgbPixel )[1];
       uint8_t red = (*pointerToRgbPixel )[2];

       DoSomething(red, green, blue);
   }

   // Option 2: get a pointer to the first sample and iterate
   uint8_t* pointerToSample = rgbMat.ptr<uint8_t>(y);

   for (int x = 0; x < rgbMat.channels()*rgbMat.size().width; ++x) {
       DoSomething(*pointerToSample);
       ++pointerToSample;
   }
}

Why do I like the iteration over the rows ? Because it is easy to make parallel. If you have a multi-core computer, you can use any framework (such as OpenMP or GCD) to handle each line in parallel in a safe way. Using OpenMP, it as easy as adding a #pragma parallel for before the outer loop.

0
On

Yes it is referenced over there in the documentation.

And why don't you see the snippet below:

template<int N>
void SetPixel(Mat &img, int x, int y, unsigned char newVal) {   
    *(img.data + (y * img.cols + x) * img.channels() + N) = newVal;
}


int main() {
    Mat img = Mat::zeros(1000, 1000, CV_8UC4);
    SetPixel<0>(img, 120);
    SetPixel<1>(img, 120);
    SetPixel<2>(img, 120);
    imwrite("out.jpg", img);

    return 0;
}

But it is not the safe way, it assumes mat data lays continuously in the momory (and there is no space in bytes between its rows). So better check Mat::isContinous() before using this snippet.

0
On

//C++ Code Below

//your RGB image

cv::Mat image;

//your 1D array

cv::Mat newimage;

//the function to convert the image into 1D array
image.reshape(0, 1).convertTo(newimage, CV_32F);

//http://docs.opencv.org/modules/core/doc/basic_structures.html#mat-reshape