Transpose a 1D byte-wise array to a 2D array - c++

I am reading a 64x128 grayscale image in an array, one line at a time. Each pixel is 8-bit wide. The read operation is done in a byte addressable manner. Now I want to transpose each line and store it in a 2D array. This architecture is designed towards memory optimization on a specific device. Once the 2D array is filled, I need to read it byte-by-byte such that each of the 8 bits lie in a different row of the array. Can anyone give a sample code?
Thanks!

sample
#include <stdio.h>
#include <string.h>
int main(void){
unsigned char a[64*128];
int i,r,c;
for(i=0;i<64*128;i++){
a[i]=i;
}
//not fill
unsigned char (*b)[64][128] = (unsigned char (*)[64][128])a;
for(r=0;r<64;++r){
for(c=0;c<128;++c){
printf("%i,", (*b)[r][c]);
}
printf("\n");
}
//FILL
unsigned char b2[64][128];
memcpy(b2, a, sizeof(b2));
printf("\nFill ver\n");
for(r=0;r<2;++r){
for(c=0;c<16;++c){
printf("%i,", b2[r][c]);
}
printf("\n");
}
return 0;
}

just construct a Mat from your array:
uchar array[w*h];
// read bytes:
Mat m( h, w, CV_8U, array );
or start with a Mat in the 1st place, and read into it's data member:
Mat m(h, w, CV_8U);
// read into m.data
if you have a 1d Mat, you can just reshape it:
Mat m( 1, w*h, CV_8U );
// make it 2d HxW now:
m = m.reshape(1, w);

Related

Read a 3D Dicom image with DCMTK and convert it to OpenCV Mat

I have a dicom 3D image which is [512,512,5] (rows, cols, slices). I want to read it with DCMTK toolkit and convert it to a OpenCV Mat object. The image is 16 bits unsigned int.
My question is: Does anyone know the correct way to convert this dicom image into a Mat object? How to properly read all the slices with the method getOutputData?
Based on the comments of #Alan Birtles, there is the possibility to specify the frame you want to read on the getOutputData method. After reading each frame, you simply merge the Mat objects into a single Mat.
I wrote this code to get the whole volume:
DicomImage *image = new DicomImage(file);
// Get the information
unsigned int nRows = image->getHeight();
unsigned int nCols = image->getWidth();
unsigned int nImgs = image->getFrameCount();
vector <Mat> slices(nImgs);
// Loop for each slice
for(int k = 0; k<nImgs; k++){
(Uint16 *) pixelData = (Uint16 *)(image->getOutputData(16 /* bits */,k /* slice */));
slices[k] = Mat(nRows, nCols, CV_16U, pixelData).clone();
}
Mat img;
// Merge the slices in a single img
merge(slices,img);
cout << img.size() << endl;
cout << img.channels() << endl;
// Output:
// [512 x 512]
// 5

How to copy cv::Mat & Img to unsigned char* img?opencv

How to copy "Mat& imgSrc" to "unsigned char* imgSrc"?
void BGR2NV21( unsigned char* bgrData, int w, int h, unsigned char* nv21Data)
{.............}
int enhanceRGB( cv::Mat& fieldFaceImgSrc, int iWidth,int iHeight, cv::Mat& faceImgDst)
{
cv::imwrite("enhanceInput.jpg", fieldFaceImgSrc); //it's ok
cv::Mat faceBackupData;
faceBackupData = fieldFaceImgSrc.clone(); //it's ok
cv::imwrite("enhanceput.jpg", faceBackupData); //it's ok
unsigned char*pcfieldFaceDataNV21 = (unsigned char*)faceBackupData .data;
BGR2NV21(pcfieldFaceDataNV21, //it's bad,pcfieldFaceDataNV21 is bad data;
iWidth, iHeight, pcfieldFaceDataNV21);
}
Thanks for your help.
Is there an error message or is the image just "wrong"?
Two obvious things to check, have you passed image width and height the right way around? It's a constant source of pain in image between camera APIs (x,y) and image processing matrix APIs (row,col)
And opencv pads rows of an image to 32bit (4byte boundaries) see Stride on image using Opencv C++
One of the reason why it may failed is about the management of the channels.
OpenCV's Mat object store the data continuously if they are not a submatrix.
This storage follow the raw line ordering that mean:
The matrix :
|1 2 3|
|4 5 6|
|7 8 9|
will be stored a a linear pointer in that order : 1,2,3,4,5,6,7,8,9.
That is also true for the channels i.e. let supose two pixel with the value [255,64,128] and [64,12,34] corresponding the the coordinate row = 0, col =0 and row =0 col = 1 they will stored in memory continuously (the six first values of the data will be 255,64,128,64,12,34)
Now the easiest way to copy the data pointer of Mat object to another data pointer can:
std::memcpy(dst,src,rows*cols*elemsize); or if you pass the Mat object:
if(obj.isContiguous())
std::memcpy(dst,src.ptr(),src.rows*src.step); // if you data are of type unsigned char src.step == src.cols otherwise src.step = src.cols*src.elemSize()
else
for(int r=0;r<src.rows;r++,dst+=src.step)
std::memcpy(dst,src.ptr(r),src.step);
so sorry,it always some questions,code is :
int enhanceRGB( cv::Mat fieldFaceImgSrc, int iWidth,int iHeight, cv::Mat& faceImgDst)
{
cv::imwrite("enhanceInput.jpg", fieldFaceImgSrc); //it's ok
cv::Mat faceBackupData;
faceBackupData = fieldFaceImgSrc.clone();
cv::imwrite("enhanceput.jpg", faceBackupData); //it's ok
unsigned char *testdata = (unsigned char*)malloc(iWidth * iHeight * 3 * sizeof(unsigned char));
memcpy(testdata, faceBackupData.ptr(), iWidth * iHeight * 3);
cv::Mat FaceData(iHeight , iWidth, CV_8UC3, testdata);
cv::imwrite("testput.jpg", FaceData); //it's bad
}

Convert Mat to Array/Vector in OpenCV

I am novice in OpenCV. Recently, I have troubles finding OpenCV functions to convert from Mat to Array. I researched with .ptr and .at methods available in OpenCV APIs, but I could not get proper data. I would like to have direct conversion from Mat to Array(if available, if not to Vector). I need OpenCV functions because the code has to be undergo high level synthesis in Vivado HLS. Please help.
If the memory of the Mat mat is continuous (all its data is continuous), you can directly get its data to a 1D array:
std::vector<uchar> array(mat.rows*mat.cols*mat.channels());
if (mat.isContinuous())
array = mat.data;
Otherwise, you have to get its data row by row, e.g. to a 2D array:
uchar **array = new uchar*[mat.rows];
for (int i=0; i<mat.rows; ++i)
array[i] = new uchar[mat.cols*mat.channels()];
for (int i=0; i<mat.rows; ++i)
array[i] = mat.ptr<uchar>(i);
UPDATE: It will be easier if you're using std::vector, where you can do like this:
std::vector<uchar> array;
if (mat.isContinuous()) {
// array.assign(mat.datastart, mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i)
array.assign(mat.data, mat.data + mat.total()*mat.channels());
} else {
for (int i = 0; i < mat.rows; ++i) {
array.insert(array.end(), mat.ptr<uchar>(i), mat.ptr<uchar>(i)+mat.cols*mat.channels());
}
}
p.s.: For cv::Mats of other types, like CV_32F, you should do like this:
std::vector<float> array;
if (mat.isContinuous()) {
// array.assign((float*)mat.datastart, (float*)mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i)
array.assign((float*)mat.data, (float*)mat.data + mat.total()*mat.channels());
} else {
for (int i = 0; i < mat.rows; ++i) {
array.insert(array.end(), mat.ptr<float>(i), mat.ptr<float>(i)+mat.cols*mat.channels());
}
}
UPDATE2: For OpenCV Mat data continuity, it can be summarized as follows:
Matrices created by imread(), clone(), or a constructor will always be continuous.
The only time a matrix will not be continuous is when it borrows data (except the data borrowed is continuous in the big matrix, e.g. 1. single row; 2. multiple rows with full original width) from an existing matrix (i.e. created out of an ROI of a big mat).
Please check out this code snippet for demonstration.
Can be done in two lines :)
Mat to array
uchar * arr = image.isContinuous()? image.data: image.clone().data;
uint length = image.total()*image.channels();
Mat to vector
cv::Mat flat = image.reshape(1, image.total()*image.channels());
std::vector<uchar> vec = image.isContinuous()? flat : flat.clone();
Both work for any general cv::Mat.
Explanation with a working example
cv::Mat image;
image = cv::imread(argv[1], cv::IMREAD_UNCHANGED); // Read the file
cv::namedWindow("cvmat", cv::WINDOW_AUTOSIZE );// Create a window for display.
cv::imshow("cvmat", image ); // Show our image inside it.
// flatten the mat.
uint totalElements = image.total()*image.channels(); // Note: image.total() == rows*cols.
cv::Mat flat = image.reshape(1, totalElements); // 1xN mat of 1 channel, O(1) operation
if(!image.isContinuous()) {
flat = flat.clone(); // O(N),
}
// flat.data is your array pointer
auto * ptr = flat.data; // usually, its uchar*
// You have your array, its length is flat.total() [rows=1, cols=totalElements]
// Converting to vector
std::vector<uchar> vec(flat.data, flat.data + flat.total());
// Testing by reconstruction of cvMat
cv::Mat restored = cv::Mat(image.rows, image.cols, image.type(), ptr); // OR vec.data() instead of ptr
cv::namedWindow("reconstructed", cv::WINDOW_AUTOSIZE);
cv::imshow("reconstructed", restored);
cv::waitKey(0);
Extended explanation:
Mat is stored as a contiguous block of memory, if created using one of its constructors or when copied to another Mat using clone() or similar methods. To convert to an array or vector we need the address of its first block and array/vector length.
Pointer to internal memory block
Mat::data is a public uchar pointer to its memory.
But this memory may not be contiguous. As explained in other answers, we can check if mat.data is pointing to contiguous memory or not using mat.isContinous(). Unless you need extreme efficiency, you can obtain a continuous version of the mat using mat.clone() in O(N) time. (N = number of elements from all channels). However, when dealing images read by cv::imread() we will rarely ever encounter a non-continous mat.
Length of array/vector
Q: Should be row*cols*channels right?
A: Not always. It can be rows*cols*x*y*channels.
Q: Should be equal to mat.total()?
A: True for single channel mat. But not for multi-channel mat
Length of the array/vector is slightly tricky because of poor documentation of OpenCV. We have Mat::size public member which stores only the dimensions of single Mat without channels. For RGB image, Mat.size = [rows, cols] and not [rows, cols, channels]. Mat.total() returns total elements in a single channel of the mat which is equal to product of values in mat.size. For RGB image, total() = rows*cols. Thus, for any general Mat, length of continuous memory block would be mat.total()*mat.channels().
Reconstructing Mat from array/vector
Apart from array/vector we also need the original Mat's mat.size [array like] and mat.type() [int]. Then using one of the constructors that take data's pointer, we can obtain original Mat. The optional step argument is not required because our data pointer points to continuous memory. I used this method to pass Mat as Uint8Array between nodejs and C++. This avoided writing C++ bindings for cv::Mat with node-addon-api.
References:
Create memory continuous Mat
OpenCV Mat data layout
Mat from array
Here is another possible solution assuming matrix have one column( you can reshape original Mat to one column Mat via reshape):
Mat matrix= Mat::zeros(20, 1, CV_32FC1);
vector<float> vec;
matrix.col(0).copyTo(vec);
None of the provided examples here work for the generic case, which are N dimensional matrices. Anything using "rows" assumes theres columns and rows only, a 4 dimensional matrix might have more.
Here is some example code copying a non-continuous N-dimensional matrix into a continuous memory stream - then converts it back into a Cv::Mat
#include <iostream>
#include <cstdint>
#include <cstring>
#include <opencv2/opencv.hpp>
int main(int argc, char**argv)
{
if ( argc != 2 )
{
std::cerr << "Usage: " << argv[0] << " <Image_Path>\n";
return -1;
}
cv::Mat origSource = cv::imread(argv[1],1);
if (!origSource.data) {
std::cerr << "Can't read image";
return -1;
}
// this will select a subsection of the original source image - WITHOUT copying the data
// (the header will point to a region of interest, adjusting data pointers and row step sizes)
cv::Mat sourceMat = origSource(cv::Range(origSource.size[0]/4,(3*origSource.size[0])/4),cv::Range(origSource.size[1]/4,(3*origSource.size[1])/4));
// correctly copy the contents of an N dimensional cv::Mat
// works just as fast as copying a 2D mat, but has much more difficult to read code :)
// see http://stackoverflow.com/questions/18882242/how-do-i-get-the-size-of-a-multi-dimensional-cvmat-mat-or-matnd
// copy this code in your own cvMat_To_Char_Array() function which really OpenCV should provide somehow...
// keep in mind that even Mat::clone() aligns each row at a 4 byte boundary, so uneven sized images always have stepgaps
size_t totalsize = sourceMat.step[sourceMat.dims-1];
const size_t rowsize = sourceMat.step[sourceMat.dims-1] * sourceMat.size[sourceMat.dims-1];
size_t coordinates[sourceMat.dims-1] = {0};
std::cout << "Image dimensions: ";
for (int t=0;t<sourceMat.dims;t++)
{
// calculate total size of multi dimensional matrix by multiplying dimensions
totalsize*=sourceMat.size[t];
std::cout << (t>0?" X ":"") << sourceMat.size[t];
}
// Allocate destination image buffer
uint8_t * imagebuffer = new uint8_t[totalsize];
size_t srcptr=0,dptr=0;
std::cout << std::endl;
std::cout << "One pixel in image has " << sourceMat.step[sourceMat.dims-1] << " bytes" <<std::endl;
std::cout << "Copying data in blocks of " << rowsize << " bytes" << std::endl ;
std::cout << "Total size is " << totalsize << " bytes" << std::endl;
while (dptr<totalsize) {
// we copy entire rows at once, so lowest iterator is always [dims-2]
// this is legal since OpenCV does not use 1 dimensional matrices internally (a 1D matrix is a 2d matrix with only 1 row)
std::memcpy(&imagebuffer[dptr],&(((uint8_t*)sourceMat.data)[srcptr]),rowsize);
// destination matrix has no gaps so rows follow each other directly
dptr += rowsize;
// src matrix can have gaps so we need to calculate the address of the start of the next row the hard way
// see *brief* text in opencv2/core/mat.hpp for address calculation
coordinates[sourceMat.dims-2]++;
srcptr = 0;
for (int t=sourceMat.dims-2;t>=0;t--) {
if (coordinates[t]>=sourceMat.size[t]) {
if (t==0) break;
coordinates[t]=0;
coordinates[t-1]++;
}
srcptr += sourceMat.step[t]*coordinates[t];
}
}
// this constructor assumes that imagebuffer is gap-less (if not, a complete array of step sizes must be given, too)
cv::Mat destination=cv::Mat(sourceMat.dims, sourceMat.size, sourceMat.type(), (void*)imagebuffer);
// and just to proof that sourceImage points to the same memory as origSource, we strike it through
cv::line(sourceMat,cv::Point(0,0),cv::Point(sourceMat.size[1],sourceMat.size[0]),CV_RGB(255,0,0),3);
cv::imshow("original image",origSource);
cv::imshow("partial image",sourceMat);
cv::imshow("copied image",destination);
while (cv::waitKey(60)!='q');
}
Instead of getting image row by row, you can put it directly to an array. For CV_8U type image, you can use byte array, for other types check here.
Mat img; // Should be CV_8U for using byte[]
int size = (int)img.total() * img.channels();
byte[] data = new byte[size];
img.get(0, 0, data); // Gets all pixels
byte * matToBytes(Mat image)
{
int size = image.total() * image.elemSize();
byte * bytes = new byte[size]; //delete[] later
std::memcpy(bytes,image.data,size * sizeof(byte));
}
You can use iterators:
Mat matrix = ...;
std::vector<float> vec(matrix.begin<float>(), matrix.end<float>());
cv::Mat m;
m.create(10, 10, CV_32FC3);
float *array = (float *)malloc( 3*sizeof(float)*10*10 );
cv::MatConstIterator_<cv::Vec3f> it = m.begin<cv::Vec3f>();
for (unsigned i = 0; it != m.end<cv::Vec3f>(); it++ ) {
for ( unsigned j = 0; j < 3; j++ ) {
*(array + i ) = (*it)[j];
i++;
}
}
Now you have a float array. In case of 8 bit, simply change float to uchar, Vec3f to Vec3b and CV_32FC3 to CV_8UC3.
If you know that your img is 3 channel, than you can try this code
Vec3b* dados = new Vec3b[img.rows*img.cols];
for (int i = 0; i < img.rows; i++)
for(int j=0;j<img.cols; j++)
dados[3*i*img.cols+j] =img.at<Vec3b>(i,j);
If you wanna check the (i,j) vec3b you can write
std::cout << (Vec3b)img.at<Vec3b>(i,j) << std::endl;
std::cout << (Vec3b)dados[3*i*img.cols+j] << std::endl;
Since answer above is not very accurate as mentioned in its comments but its "edit queue is full", I have to add correct one-liners.
Mat(uchar, 1 channel) to vector(uchar):
std::vector<uchar> vec = (image.isContinuous() ? image : image.clone()).reshape(1, 1); // data copy here
vector(any type) to Mat(the same type):
Mat m(vec, false); // false(by default) -- do not copy data

Moving my array to Mat and showing image with open CV

I am having problems using opencv to display an image. As my code is currently working, I have function that loads 78 images of size 710X710 of unsigned shorts into a single array. I have verified this works by writing the data to a file and reading it with imageJ. I am now trying to extract a single image frame from the array and load it into a Mat in order to perform some processing on it. Right now I have tried two ways to do this. The code will compile and run if I do not try to read the output, but if I cout<
My question would be, how do I extract the data from my large, 1-D array of 78images of size 710*710 into single Mat images. Or is there an more efficient way where I can load the images into a 3-D mat of dimensions 710X710X78 and operate on each 710X710 slice as needed?
int main(int argc, char *argv[])
{
Mat OriginalMat, TestImage;
long int VImageSize = 710*710;
int NumberofPlanes = 78;
int FrameNum = 150;
unsigned short int *PlaneStack = new unsigned short int[NumberofPlanes*VImageSize];
unsigned short int *testplane = new unsigned short int[VImageSize];
/////Load PlaneStack/////
Load_Vimage(PlaneStack, Path, NumberofPlanes);
//Here I try to extract a single plane image to the mat testplane, I try it two different ways with the same results
memcpy(testplane, &PlaneStack[710*710*40], VImageSize*sizeof(unsigned short int));
//copy(&PlaneStack[VImageSize*40],&PlaneStack[VImageSize*41], testplane);
// move single plane to a mat file
OriginalMat = Mat(710,710,CV_8U, &testplane) ;
//cout<<OriginalMat;
namedWindow("Original");
imshow("Original", OriginalMat);
}
The problem is you are using the constructor Mat::Mat(int rows, int cols, int type, void* data) with a pointer to 16 bit data (unsigned short int) but you are specifying the type CV_8U (8 bit).
Therefore the first byte of your 16 bit pixel becomes the first pixel in OriginalMat, and the second byte of the first pixel becomes the second pixel in OriginalMat, etc.
You need to create a 16 bit Mat, then convert it to 8 bit if you want to display it, e.g.:
int main(int argc, char *argv[])
{
long int VImageSize = 710*710;
int NumberofPlanes = 78;
int FrameNum = 150;
/////Load PlaneStack/////
unsigned short int *PlaneStack = new unsigned short int[NumberofPlanes*VImageSize];
Load_Vimage(PlaneStack, Path, NumberofPlanes);
// Get a pointer to the plane we want to view
unsigned short int *testplane = &PlaneStack[710*710*40];
// "move" single plane to a mat file
// actually nothing gets moved, OriginalMat will just contain a pointer to your data.
Mat OriginalMat(710,710,CV_16UC1, &testplane) ;
double scale_factor = 1.0 / 256.0;
Mat DisplayMat;
OriginalMat.convertTo(DisplayMat, CV_8UC1, scale_factor);
namedWindow("Original");
imshow("Original", DisplayMat);
}

OpenCV: how to fill rgb image having values for each of RGB colors for each pixel?

So having buffer with RGBRGBRGB... size of w*3 how to fill OpenCV Image with such data?
The easiest way is just to loop over the elements of the buffer using the at templated method.
unsigned char buffer[] = {1, 2, 3, ..., 18}; // RGBRGB...
cv::Mat image(2, 3);
for (int i = 0; i < 18; ++i) {
int row = i/9;
int col = (i/3)%3;
int rgb = i%3; // An index
image.at<unsigned char>(row,col+rgb) = buffer[i];
}
Of course, you need to initialize your matrix with the correct type, and set the color format, which I didn't do above. See more about the OpenCV matrix object here.
IplImage has a variable imageData. It is just a buffer. So you can simply copy your array if it have the same format as imageData buffer. If format differs then you can copy, but you will need to fill other variables of your IplImage properly.