Dimension reduction with PCA in OpenCV - c++

I have a 3xN Mat data, which is saved in yaml file and looks like:
%YAML:1.0
data1: !!opencv-matrix
rows: 50
cols: 3
dt: d
data: [ 7.1709999084472656e+01, -2.5729999542236328e+01,
-1.4074000549316406e+02, 7.1680000305175781e+01,
-2.5729999542236328e+01, -1.4075000000000000e+02,
7.1639999389648438e+01, -2.5729999542236328e+01,
-1.4075000000000000e+02, 7.1680000305175781e+01,
-2.5729999542236328e+01, -1.4075000000000000e+02, ...
I want to reduce the dimension of my 3D data to 1D or rather 2D and after that visualize it on a QwtPlotCurve. In order to do that, I have implemented pca function under opencv, but have no idea how to get the calculated x and y coordinates from pca result:
int numOfComponents= 100;
PCA pca(data, cv::Mat(), CV_PCA_DATA_AS_ROW, numOfComponents);
Mat mean= pca.mean.clone();
Mat eigenvalues= pca.eigenvalues.clone();
Mat eigenvectors= pca.eigenvectors.clone();

Here's an example of a 2D data set.
x=[2.5, 0.5, 2.2, 1.9, 3.1, 2.3, 2, 1, 1.5, 1.1];
y=[2.4, 0.7, 2.9, 2.2, 3.0, 2.7, 1.6, 1.1, 1.6, 0.9];
We can write these arrays in OpenCV with the following code.
float X_array[]={2.5,0.5,2.2,1.9,3.1,2.3,2,1,1.5,1.1};
float Y_array[]={2.4,0.7,2.9,2.2,3.0,2.7,1.6,1.1,1.6,0.9};
cv::Mat x(10,1,CV_32F,X_array); //Copy X_array to Mat (PCA need Mat form)
cv::Mat y(10,1,CV_32F,Y_array); //Copy Y_array to Mat
Next, we will combine x and y into a unified cv::Mat data. Because the whole data must be in one place for PCA function to work, we have to combine our data. (If your data is in 2D format, such as as an image, then you can simply convert the 2D to 1D signals and combine them)
x.col(0).copyTo(data.col(0)); //copy x into first column of data
y.col(0).copyTo(data.col(1)); //copy y into second column of data
the data after the last code will look like the following:
data=
[2.5, 2.4;
0.5, 0.7;
2.2, 2.9;
1.9, 2.2;
3.1, 3;
2.3, 2.7;
2, 1.6;
1, 1.1;
1.5, 1.6;
1.1, 0.9]
With cv::PCA, we can calculate the eigenvalues and eigenvectors of the 2D signal.
cv::PCA pca(data, //Input Array Data
Mat(), //Mean of input array, if you don't want to pass it simply put Mat()
CV_PCA_DATA_AS_ROW, //int flag
2); // number of components that you want to retain(keep)
Mat mean=pca.mean; // get mean of Data in Mat form
Mat eigenvalue=pca.eigenvalues;
Mat eigenvectors=pca.eigenvectors;
our eigenValue and eigenvectors will be as the below:
EigenValue=
[1.155625;
0.044175029]
EigenVectors=
[0.67787337, 0.73517865;
0.73517865, -0.67787337]
As you can see in the eigenValue, the first-row value is 1.55 and is much bigger than 0.044. So in eigenvectors, the first row is most important than the second row and if you retain the correspond row in EigenVectors, you can have almost whole data in 1D (Simply you have compressed the data, but your 2D pattern available in new 1D data)
How we can Extract final Data??
To extract final data you can multiply the eigenVector by the original data and get new data, for example, if I want to convert my data to 1D, I can use the below code
Mat final=eigenvectors.row(0)*data.t(); //firts_row_in_eigenVector * tranpose(data)
In your example, if you want to convert 3D to 2D then set the dimension to retain 2, and if you want to convert to 1D then set this argument to 1 like the below
1D
int numOfComponents= 1;
PCA pca(data, cv::Mat(), CV_PCA_DATA_AS_ROW, numOfComponents);
2
int numOfComponents= 2;
PCA pca(data, cv::Mat(), CV_PCA_DATA_AS_ROW, numOfComponents);

Related

How could I use opencv to multiply two tensors?

Suppose that I have an image im and a 3x3 convert matrix, the image is HxWx3, I need to do a multiplication like this:
Mat M = Mat::ones(3, 3, CV_64FC1);
M.at<double>(1,1) = 4.0;
Mat res = im * M;
The multiplication that I need is like the method of np.matmul of the python package of numpy. How could I do this please ?

fill a mulit-dimentional matrix with uniformly distributed random numbers with diffierent ranges

I want to fill a 10000x2 matrix in OpenCV (v3.2) with random numbers in uniform distribution but with different ranges for each column and here is the problem with the following code:
Mat centers(10000, 2, CV_32F);
RNG rng(time(NULL));
rng.fill(centers, RNG::UNIFORM, Scalar(0, 0), Scalar(10, 1000));
I expect the first column to be randomly filled with values between zero and 10 and the second column to be filled with values between zero and 1000. But both columns are filled with values between zero and 10,
So I decided to implemented it in the following form.
Mat centers(10000, 2, CV_32F);
RNG rng(time(NULL));
rng.fill(centers.colRange(0, 1), RNG::UNIFORM, 0, 10);
rng.fill(centers.colRange(1, 2), RNG::UNIFORM, 0, 1000);
But it does not work either. I think because RNG::fill does not support noncontinuous matrices (which is not mentioned in the documentation)
So the only remaining way is to use for loop which is waste of time and performance. Am I doing sth wrong above or should I give up and use a for loop
You have misinterpreted the API documentation of RNG::fill(), which clearly defines the parameters a and b as:
a - first distribution parameter; in case of the uniform distribution,
this is an inclusive lower boundary.
b - second distribution parameter;
in case of the uniform distribution, this is a non-inclusive upper
boundary.
So there is no mention in the documentation that you can pass multiple ranges in a and b. So the solution is to create two Mat of 1000 x 1 dimensions, use different values of a and b for both of them and later join both of them to create a unified mat with 1000 x 2 dimension.
cv::RNG rng = cv::RNG(0xFFFFFFFF);
cv::Mat centers(10000, 1, CV_32F);
cv::Mat centers2(10000, 1, CV_32F);
rng.fill(centers, cv::RNG::UNIFORM, cv::Scalar(0), cv::Scalar(10));
rng.fill(centers2, cv::RNG::UNIFORM, cv::Scalar(0), cv::Scalar(1000));
cv::Mat final;
cv::hconcat(centers, centers2, final);
Simply I made a mistake useing the RNG::fill api with multi-range distribution parameters. one should use multi-channels and not multi-columns matrices, even though they share the same underlying data structure. So, Mat centers(10000, 2, CV_32F); should be Mat centers(10000, 1, CV_32FC2);

creating 3D histogram using 2D data (OpenCV?)

I have data set 1 and 2. Those have 2D data.
For example,
Data1 :
(x1, y1), (x2, y2), (x3, y3) .... (xn, yn)
Data2 : (x1', y1'), (x2', y2'), .... (xm', ym')
I'd like to compare them using histogram and Earth Mover's Distance(EMD) if possible.
Because I have 2D data, the data should be placed on 2D map, and the height of the histogram on 2D map has the frequency of the data, thus it should be 3D histogram I guess. Even though I success to create example to draw histogram and compare them using 1D data, I failed to try to change it to 2D data. How it works?
For example,
calcHist(&greyImg, 1, channel_numbers, Mat(), histogram1, 1, &number_bins, &channel_ranges);
This code makes tha Image's grayscale intensity(1D data) to histogram. But I could not change it to 2D data.
my Idea is this :
I create cv::Mat Data1Mat, Data2Mat; (Mat size is set as maximum value of x and y)
Then, push the Data1's x values to Mat1's first channel, push y values to second channel. (Same to Data2 and Data2Mat)
For example, for (x1, y1), set
Data1Mat.at(x1,y1)[0] = x1, Data1Mat.at(x1, y1)[1] = y1;
like this.Then create Histogram of them and compare. Do I think correctly?
I think it is more correct to say: histogram of 1D data, of histogram of 2D data.
You need histogram of 2D data.
1D histogram computes number of scalar values hit bin intervals.
2D histogram divides plane by regions and compute number of 2D points
hit each region.
Here computed H,S 2D histogram for an image: Calculate HSV histogram of a coloured image is it different from H-S histogram?
You have near the same problem, but put your x to instead of H, and y instead of S.

How i can take the average of 100 image using opencv?

i have 100 image, each one is 598 * 598 pixels, and i want to remove the pictorial and noise by taking the average of pixels, but if i want to use Adding for "pixel by pixel"then dividing i will write a loop until 596*598 repetitions for one image, and 598*598*100 for hundred of image.
is there a method to help me in this operation?
You need to loop over each image, and accumulate the results. Since this is likely to cause overflow, you can convert each image to a CV_64FC3 image, and accumualate on a CV_64FC3 image. You can use also CV_32FC3 or CV_32SC3 for this, i.e. using float or integer instead of double.
Once you have accumulated all values, you can use convertTo to both:
make the image a CV_8UC3
divide each value by the number of image, to get the actual mean.
This is a sample code that creates 100 random images, and computes and shows the
mean:
#include <opencv2\opencv.hpp>
using namespace cv;
Mat3b getMean(const vector<Mat3b>& images)
{
if (images.empty()) return Mat3b();
// Create a 0 initialized image to use as accumulator
Mat m(images[0].rows, images[0].cols, CV_64FC3);
m.setTo(Scalar(0,0,0,0));
// Use a temp image to hold the conversion of each input image to CV_64FC3
// This will be allocated just the first time, since all your images have
// the same size.
Mat temp;
for (int i = 0; i < images.size(); ++i)
{
// Convert the input images to CV_64FC3 ...
images[i].convertTo(temp, CV_64FC3);
// ... so you can accumulate
m += temp;
}
// Convert back to CV_8UC3 type, applying the division to get the actual mean
m.convertTo(m, CV_8U, 1. / images.size());
return m;
}
int main()
{
// Create a vector of 100 random images
vector<Mat3b> images;
for (int i = 0; i < 100; ++i)
{
Mat3b img(598, 598);
randu(img, Scalar(0), Scalar(256));
images.push_back(img);
}
// Compute the mean
Mat3b meanImage = getMean(images);
// Show result
imshow("Mean image", meanImage);
waitKey();
return 0;
}
Suppose that the images will not need to undergo transformations (gamma, color space, or alignment). The numpy package lets you do this quickly and succinctly.
# List of images, all must be the same size and data type.
images=[img0, img1, ...]
avg_img = np.mean(images, axis=0)
This will auto-promote the elements to float. If you want the as BGR888, then:
avg_img = avg_img.astype(np.uint8)
Could also do uint16 for 16 bits per channel. If you are dealing with 8 bits per channel, you almost certainly won't need 100 images.
Firstly- convert images to floats. You have N=100 images. Imagine that a single image is an array of average pixel values of 1 image. You need to calculate an array of average pixel values of N images.
Let A- array of average pixel values of X images, B - array of average pixel values of Y images. Then C = (A * X + B * Y) / (X + Y) - array of average pixel values of X + Y images. To get better accuracy in floating point operations X and Y should be approximately equal
You may merge all you images like subarrays in merge sort. In you case merge operation is C = (A * X + B * Y) / (X + Y) where A and B are arrays of average pixel values of X and Y images

Filling Matrix with data points

In openCV there's a function called FindHomography which takes MATRICES as input.
I have data points representing the location of the features in a frame. I want to put these data points in a Matrix using C++ or C.
I want to put them in 2d array, which these features represent x and y location.
Please can you suggest how to do it ?
let's say I have 20 features in the Frame, now these features are just integers and I wanna put these features in A matrix in order to use them in the Matrix mentioned above
Here's an example based on the example in O'Reilly's Learning OpenCV on page 35-36:
float vals[] = { 0.1, 0.2, 0.3, 0.4 };
CvMat mat;
cvInitMatHeader(&mat, 2, 2, CV_32FC1, vals);
This creates a 2x2 float Matrix using the statically allocated data above.