I'm trying to create a PCA model in OpenCV to hold pixel coordinates. As an experiment I have two sets of pixel coordinates that maps out two approximate circles. Each set of coordiantes has 48 x,y pairs. I was experimenting with the following code which reads the coordinates from a file and stores them in a Mat structure. However, I don't think it is right and PCA in openCV seems very poorly covered on the Internet.
Mat m(2, 48, CV_32FC2); // matrix with 2 rows of 48 cols of floats held in two channels
pFile = fopen("data.txt", "r");
for (int i=0; i<48; i++){
int x, y;
fscanf(pFile, "%d%c%c%d%c", &x, &c, &c, &y, &c);
m.at<Vec2f>( 0 , i )[0] = (float)x; // store x in row 0, col i in channel 0
m.at<Vec2f>( 0 , i )[1] = (float)y; // store y in row 0, col i in channel 1
}
for (int i=0; i<48; i++){
int x, y;
fscanf(pFile, "%d%c%c%d%c", &x, &c, &c, &y, &c);
m.at<Vec2f>( 1 , i )[0] = (float)x; // store x in row 1, col i in channel 0
m.at<Vec2f>( 1 , i )[1] = (float)y; // store y in row 1, col i in channel 1
}
PCA pca(m, Mat(), CV_PCA_DATA_AS_ROW, 2); // 2 principle components??? Not sure what to put here e.g. is it 2 for two data sets or 48 for number of elements?
for (int i=0; i<48; i++){
float x = pca.mean.at<Vec2f>(i,0)[0]; //get average x
float y = pca.mean.at<Vec2f>(i,0)[1]; //get average y
printf("\n x=%f, y=%f", x, y);
}
However, this crashes when creating the pca object. I know this is a very basic question but I am a bit lost and was hoping that someone could get me started with pca in open cv.
Perhaps it would be helpful if you described in further detail what you need to use PCA for and what you hope to achieve (output?).
I am fairly sure that the reason your program crashes is because the input Mat is CV_32FC2, when it should be CV_32FC1. You need to reshape your data into 1 dimensional row vectors before using PCA, not knowing what you need I can't say how to reshape your data. (The common application with images is eigenFace which requires an image to be reshaped into a row vector). Additionally you will need to normalize your input data between 0 and 1.
As a further aside, usually you would choose to keep 1 less principal component than the number of input samples because the last principal component is simply orthogonal to the others.
I have worked with opencv PCA before and would like to help further. I would also refer you to this blog: http://www.bytefish.de/blog/pca_in_opencv which helped me get started with PCA in openCV.
Related
Is there a direct way to compute the column-wise standard deviation for a matrix in opencv? Similar to std in Matlab. I've found one for the mean:
cv::Mat col_mean;
reduce(A, col_mean, 1, CV_REDUCE_AVG);
but I cannot find such a function for the standard deviation.
Here's a quick answer to what you're looking for. I added both the standard deviation and mean for each column. The code can easily be modified for rows.
cv::Mat A = ...; // FILL IN THE DATA FOR YOUR INPUT MATRIX
cv::Mat meanValue, stdValue;
cv::Mat colSTD(1, A.cols, CV_64FC1);
cv::Mat colMEAN(1, A.cols, CV_64FC1);
for (int i = 0; i < A.cols; i++){
cv::meanStdDev(A.col(i), meanValue, stdValue);
colSTD.at<double>(i) = stdValue.at<double>(0);
colMEAN.at<double>(i) = meanValue.at<double>(0);
}
The following is not in a single line,but it is another version without loops:
reduce(A, meanOfEachCol, 0, CV_REDUCE_AVG); // produces single row of columnar means
Mat repColMean;
cv::repeat(meanOfEachCol, rows, 1, repColMean); // repeat mean vector 'rows' times
Mat diffMean = A - repColMean; // get difference
Mat diffMean2 = diffMean.mul(diffMean); // per element square
Mat varMeanF;
cv::reduce(diffMean2, varMeanF, 0, CV_REDUCE_AVG); // sum each column's elements to get single row
Mat stdMeanF;
cv::sqrt(varMeanF, stdMeanF); // get standard deviation
I have created a 3D matrix in opencv with the size: 120x100x50 :
int _sz[] = {120,100,50};
Mat src(3,_sz,CV_32FC1,Scalar(2));
I want to transpose the first and the second dimension of this matrix to 100x120x50.
I use 3 loop iteration to manually transpose
for ()
for ()
for ()
dst.at<float>( , , ) = src.at<float>( , , );
But it takes too long with this way. And, I cannot utilize channel as the third dimension because the third dimension is 50 whereas OpenCV mat only supports 4 channels in maximum. Are there any other way to do this work quicker? Just do something like OpenCV did with multichannel matrix because when I test with matrix 120x100x4 by using 4 channel as the third dimension, the execution time is much better.
I change to access values of matrix like array values. Execution time is much better. But it still accesses one-by-one, not copy block-by-block.
int indexOrder[3];
int order[3]={1,0,2};
int* _srcSize = srcMat.size.p;
int srcMatSize[] = {_srcSize[0],_srcSize[1],_srcSize[2]};
int dstMatSize[] = {srcMatSize[order[0]], srcMatSize[order[1]], srcMatSize[order[2]]};
Mat dstMat(3, dstMatSize, CV_32FC1);
float* srcArr, *dstArr;
srcArr = srcMat.ptr<float>();
dstArr = dstMat.ptr<float>();
for (indexOrder[0]=0;indexOrder[0]<srcMatSize[0];indexOrder[0]++)
for (indexOrder[1]=0;indexOrder[1]<srcMatSize[1];indexOrder[1]++)
for (indexOrder[2]=0;indexOrder[2]<srcMatSize[2];indexOrder[2]++)
dstArr[indexOrder[order[0]]*dstMatSize[1] + indexOrder[order[1]]*dstMatSize[2] + indexOrder[order[2]]] =
srcArr[indexOrder[0]*srcMatSize[1]+indexOrder[1]*srcMatSize[2]+indexOrder[2]];
I have a matrix of labels for training SVM of size (1 x 5).. 5 labels for 5 image.
Now, the problem is that, the data matrix with training data is of size (1 x 65000). Infact this matrix has to be of size (5 x 13000); that is 5 columns (image) of 13000 rows.
I tried reshaping but it didnt work. Can i get some help how to change the matrix size.
Thank you
The simplest thing to do here would be to create a new matrix and copy the elements over one at a time. I'm not sure what the structure of your matrix is, but let's say the first training example corresponds to what you currently have in entries 1-5 of your incorrectly-shaped matrix.
cv::Mat training_data = new cv::Mat(13000, 5, CV_32F);
for (unsigned int row = 0; row < training_data.rows; ++row)
{
for (unsigned int col = 0; col < training_data.cols; ++col)
{
training_data.at<float>(row, col) = old_training_data.at<float>(1, (training_data.cols * row) + col);
}
}
Then release the old training data matrix, and you have a brand new (correctly shaped!) matrix.
I is an mxn matrix and each element of I is a 1x3 vector (I is a 3-channel Mat image actually).
M is a 3x3 matrix.
J is an matrix having the same dimension as I and is computed as follows: each element of J is the vector-matrix product of the corresponding (i.e. having the same coordinates) element of I and M.
I.e. if v1(r1,g1,b1) is an element of I and v2(r2,g2,b2) is its corresponding element of J, then v2 = v1 * M (this is a vector-matrix product, not a per-element product).
Question: How to compute J efficiently (in terms of speed)?
Thank you for your help.
As far as I know, the most efficient way to implement such an operation is as follows:
Reshape I from mxnx3 to (m·n)x3, let's call it I'
Calculate J' = I' * M
Reshape J' from (m·n)x3 to mxnx3, this is the J we wanted
The idea is to stack each pixel-wise operation pi'·M into one single operation P'·M, where P is the 3x(m·n) matrix containing each pixel in columns (hence P' holds one pixel per row. It's just a convention, really).
Here is a code sample written in c++:
// read some image
cv::Mat I = cv::imread("image.png"); // rows x cols x 3
// some matrix M, that modifies each pixel
cv::Mat M = (cv::Mat_<float>(3, 3) << 0, 0, 0,
0, .5, 0,
0, 0, .5); // 3 x 3
// remember old dimension
uint8_t prevChannels = I.channels;
uint32_t prevRows = I.rows;
// reshape I
uint32_t newRows = I.rows * I.cols;
I = I.reshape(1, newRows); // (rows * cols) x 3
// compute J
cv::Mat J = I * M; // (rows * cols) x 3
// reshape to original dimensions
J = J.reshape(prevChannels, prevRows); // rows x cols x 3
OpenCV provides an O(1) reshaping operation.
Thus performance depends solely on matrix multiplication, which I expect to be as efficient as possible in a computer vision library.
To further enhance performance, you might want to take a look at matrix multiplication using the ocl and gpu modules.
In Matlab, If A is a matrix, sum(A) treats the columns of A as vectors, returning a row vector of the sums of each column.
sum(Image); How could it be done with OpenCV?
Using cvReduce has worked for me. For example, if you need to store the column-wise sum of a matrix as a row matrix you could do this:
CvMat * MyMat = cvCreateMat(height, width, CV_64FC1);
// Fill in MyMat with some data...
CvMat * ColSum = cvCreateMat(1, MyMat->width, CV_64FC1);
cvReduce(MyMat, ColSum, 0, CV_REDUCE_SUM);
More information is available in the OpenCV documentation.
EDIT after 3 years:
The proper function for this is cv::reduce.
Reduces a matrix to a vector.
The function reduce reduces the matrix to a vector by treating the
matrix rows/columns as a set of 1D vectors and performing the
specified operation on the vectors until a single row/column is
obtained. For example, the function can be used to compute horizontal
and vertical projections of a raster image. In case of REDUCE_MAX and
REDUCE_MIN , the output image should have the same type as the source
one. In case of REDUCE_SUM and REDUCE_AVG , the output may have a
larger element bit-depth to preserve accuracy. And multi-channel
arrays are also supported in these two reduction modes.
OLD:
I've used ROI method: move ROI of height of the image and width 1 from left to right and calculate means.
Mat src = imread(filename, 0);
vector<int> graph( src.cols );
for (int c=0; c<src.cols-1; c++)
{
Mat roi = src( Rect( c,0,1,src.rows ) );
graph[c] = int(mean(roi)[0]);
}
Mat mgraph( 260, src.cols+10, CV_8UC3);
for (int c=0; c<src.cols-1; c++)
{
line( mgraph, Point(c+5,0), Point(c+5,graph[c]), Scalar(255,0,0), 1, CV_AA);
}
imshow("mgraph", mgraph);
imshow("source", src);
EDIT:
Just out of curiosity, I've tried resize to height 1 and the result was almost the same:
Mat test;
cv::resize(src,test,Size( src.cols,1 ));
Mat mgraph1( 260, src.cols+10, CV_8UC3);
for(int c=0; c<test.cols; c++)
{
graph[c] = test.at<uchar>(0,c);
}
for (int c=0; c<src.cols-1; c++)
{
line( mgraph1, Point(c+5,0), Point(c+5,graph[c]), Scalar(255,255,0), 1, CV_AA);
}
imshow("mgraph1", mgraph1);
cvSum respects ROI, so if you move a 1 px wide window over the whole image, you can calculate the sum of each column.
My c++ got a little rusty so I won't provide a code example, though the last time I did this I used OpenCVSharp and it worked fine. However, I'm not sure how efficient this method is.
My math skills are getting rusty too, but shouldn't it be possible to sum all elements in columns in a matrix by multiplying it by a vector of 1s?
For an 8 bit greyscale image, the following should work (I think).
It shouldn't be too hard to expand to different image types.
int imgStep = image->widthStep;
uchar* imageData = (uchar*)image->imageData;
uint result[image->width];
memset(result, 0, sizeof(uchar) * image->width);
for (int col = 0; col < image->width; col++) {
for (int row = 0; row < image->height; row++) {
result[col] += imageData[row * imgStep + col];
}
}
// your desired vector is in result