opencv C++ neural network predict() function throws "Bad argument" error - c++

I have managed to train a neural network to recognize numbers in an image and have saved the network parameters to an .xml file.
However, when testing the network against a new image the code fails at the predict() stage with the error:
OpenCV Error: Bad argument (Both input and output must be floating-point matrices of the same type and have the same number of rows) in CvANN_MLP::predict, file ........\opencv\modules\ml\src\ann_mlp.cpp, line 279.
ann_mlp.cpp line 279 is:
if( !CV_IS_MAT(_inputs) || !CV_IS_MAT(_outputs) ||
!CV_ARE_TYPES_EQ(_inputs,_outputs) ||
(CV_MAT_TYPE(_inputs->type) != CV_32FC1 &&
CV_MAT_TYPE(_inputs->type) != CV_64FC1) ||
_inputs->rows != _outputs->rows )
CV_Error( CV_StsBadArg, "Both input and output must be floating-point matrices "
"of the same type and have the same number of rows" );
I have checked input rows by running this code:
cv::Size s = newVec.size();
int rows = s.height;
int cols = s.width;
cout << "newVec dimensions: " << rows << " x " << cols << endl;
...and it comes out with the expected 1 x 900 vector / matrix.
I have set the input and output matrices to be CV_32FC1 as per the error dialog like this:
Input matrix
cv::Mat newVec(1, 900, CV_32FC1);
newVec = crop_img.reshape(0, 1); //reshape / unroll image to vector
CvMat n = newVec;
newVec = cv::Mat(&n);
Output matrix
cv::Mat classOut = cvCreateMatHeader(1, CLASSES, CV_32FC1);
And I try to run the prediction function like this:
CvANN_MLP* nnetwork = new CvANN_MLP;
nnetwork->load("nnetwork.xml", "nnetwork");
int maxIndex = 0;
cv::Mat classOut = cvCreateMatHeader(1, CLASSES, CV_32FC1);
//prediction
nnetwork->predict(newVec, classOut);
float value;
float maxValue = classOut.at<float>(0, 0);
for (int index = 1; index<CLASSES; index++)
{
value = classOut.at<float>(0, index);
if (value>maxValue)
{
maxValue = value;
maxIndex = index;
}
}
Any ideas? Much appreciated...

I suspect the problem is your input, not your output.
First it's important to understand that OpenCV deserves a lot of the blame for this, not you. Their C++ API is quite mediocre, and it caused major confusion to you.
See, normally in C++ when you define a 1x900 matrix of floats, it stays a matrix of floats. C++ has strong type safety.
OpenCV does not. If you assign a matrix of bytes to a matrix of floats, the latter will change its type (!).
Your code initializes newVec to such a matrix of floats, then assigns a second matrix, and then yet another matrix. I suspect that crop_img is still an image, i.e. 8 bits. Reshaping it will make it 1x900, but not floating point. That's the job of .convertTo.

Related

Compute the similarity rate between two Images with opencv/c++

I'm using OpenCV/C++ to compute the similarity rate between two images. I want to tell the user how much % image A looks like image B.
Let's take a look at the code below :
double getSimilarityRate(const cv::Mat A, const cv::Mat B){
double cpt = 0.0;
cv::Mat imgGray1, imgGray2;
cv::cvtColor(A, imgGray1, CV_BGR2GRAY);
cv::cvtColor(B, imgGray2, CV_BGR2GRAY);
imgGray1 = imgGray1 > 128;
imgGray2 = imgGray2 > 128;
double total = imgGray1.cols * imgGray1.rows;
if(imgGray1.rows > 0 && imgGray1.rows == B.rows && imgGray1.cols > 0 && imgGray1.cols == B.cols){
for(int rows = 0; rows < imgGray1.rows; rows++){
for(int cols = 0; cols < imgGray1.cols; cols++){
if(imgGray1.at<int>(rows, cols) == imgGray2.at<int>(rows,cols)) cpt ++;
}
}
}else{
std::cout << "No similartity between the two images ... [EXIT]" << std::endl;
exit(0);
}
double rate = cpt / total;
return rate * 100.0;
}
int main(void)
{
/* ------------------------------------------ # ALGO GETSIMILARITY BETWEEN 2 IMAGES # -------------------------------------- */
double rate;
string fileNameImage1("C:\\Users\\hugoo\\Documents\\Prog\\NexterMU\\Qt\\OpenCV\\DetectionShapeProgram\\mire.jpg");
cv::Mat image1 = imread(fileNameImage1);
string fileNameImage2("C:\\Users\\hugoo\\Documents\\Prog\\NexterMU\\Qt\\OpenCV\\DetectionShapeProgram\\mire.jpg");
cv::Mat image2 = imread(fileNameImage2);
if(image1.empty() || image2.empty()){
std::cout << "Images couldn't be loaded" << std::endl;
exit(-1);
}
rate = getSimilarityRate(image1, image2) ;
First I convert the matrices from BGR to GRAY. So I have only one channel remaining. (Much more easier to compare).
cv::Mat imgGray1, imgGray2;
cv::cvtColor(A, imgGray1, CV_BGR2GRAY);
cv::cvtColor(B, imgGray2, CV_BGR2GRAY);
Then I make them binary (255 or 0 --> pixel's White or Black) :
imgGray1 = imgGray1 > 128;
imgGray2 = imgGray2 > 128;
In my for loops I pass through each pixel and compare him to other one in the second image.
If it matches I increase a variable (cpt ++).
I compute the rate and turn it to a %, with :
double rate = cpt / total;
return rate * 100.0;
The thing is it doesn't seem to compute correctly, because it doesn't return me the rate value in the console...
I think the problem comes from the at() function maybe I don't use it properly.
I suspect imgGray1.at<int>(rows, cols) should be imgGray1.at<uchar>(rows, cols) instead.
Currently .at() function call has int as a template argument, but typically cv::Mat consist of uchar elements. Are you pretty sure that your image has int elements? If it does consist of uchar elements, then using int template argument will result in accessing memory beyond what corresponds to the image (basically all pointer offsets would now be 4x as large as they should be).
More generally, if you use cv::Mat::at(), you need to use different template arguments depending on the output of cv::Mat::type():
8-bit 3-channel image (CV_8UC3) --> .at<cv::Vec3b>(row, column)
8-bit 1-channel image (CV_8UC1) --> .at<uchar>(row, column)
32-bit 3-channel image (CV_32FC3) --> .at<cv::Vec3f>(row, column)
32-bit 1-channel image (CV_32FC1) --> .at<float>(row, column)
For this reason, if a function should support arbitrary cv::Mat's, one either needs to write a bunch of if-else clauses, or to avoid .at() altogether. In your situation, since imgGray1 and imgGray2 are "binarized", I wonder if rate can be calculated using cv::norm, possibly like so:
// NORM_INF counts the number of non-equal elements.
int num_non_equal = cv::norm(imgGray1, imgGray2, NORM_INF);
double rate = 1.0 - num_non_equal / static_cast<double>(total);

Opencv convolution matrix gives unusual results

So I have a program that is trying to apply a simple 3x3 convolution matrix to an image.
This is the function that is doing the work:
Mat process(Mat image) {
int x = 2;
int y = 2;
Mat nimage(image); //just a new mat to put the resulting image on
while (y < image.rows-2) {
while (x < image.cols-2) {
nimage.at<uchar>(y,x) = //apply matrix to pixel
image.at<char>(y-1,x-1)*matrix[0]+
image.at<char>(y-1,x)*matrix[1]+
image.at<char>(y-1,x+1)*matrix[2]+
image.at<char>(y,x-1)*matrix[3]+
image.at<char>(y,x)*matrix[4]+
image.at<char>(y,x+1)*matrix[5]+
image.at<char>(y+1,x-1)*matrix[6]+
image.at<char>(y+1,x)*matrix[7]+
image.at<char>(y+1,x+1)*matrix[8];
//if (total < 0) total = 0;
//if (total > 255) total = 255;
//cout << (int)total << ": " << x << "," << y << endl;
x++;
}
x = 0;
y++;
}
cout << "done" << endl;
return nimage;
}
And the matrix looks like this
double ar[9] = {-1,0,0,
0,2,0,
0,0,0};
And the image that is used as input looks like this:
The desired output (I ran the same matrix on the input image in GIMP):
And the result is... weird:
I think this has to do with the data type I use when I set a pixel of the new image (nimage.at<uchar>(y,x) = ...), because whenever I change it I get a different, yet still incorrect result.
From the OpenCV documentation about the copy constructor of Mat, emphasis mine:
m – Array that (as a whole or partly) is assigned to the constructed matrix. No data is copied by these constructors. Instead, the header pointing to m data or its sub-array is constructed and associated with it. The reference counter, if any, is incremented. So, when you modify the matrix formed using such a constructor, you also modify the corresponding elements of m. If you want to have an independent copy of the sub-array, use Mat::clone().
So
Mat nimage(image); //just a new mat to put the resulting image on
doesn't actually create a new matrix; it creates a new Mat object, but that object still refers to the same matrix. From then on nimage.at(y,x) acts like image.at(y,x).
To copy the image, use
Mat nimage(image.clone()); //just a new mat to put the resulting image on

Need a faster way to convert a cv::Mat into 1 dimensional vector form

I have a for loop the takes an OpenCV Mat object of n x n dimensions, and returns a Mat object of n^2 x 1 dimensions. It works, but when I time the method it takes between 1 and 2 milliseconds. Since I am calling this method 3 or 4 million times its taking my program about an hour to run. A research paper I'm referencing suggests the author was able to produce a program with the same function that ran in only a few minutes, without running any threads in parallel. After timing each section of code, the only portion taking >1 ms is the following method.
static Mat mat2vec(Mat mat)
{
Mat toReturn = Mat(mat.rows*mat.cols, 1, mat.type());
float* matPt;
float* retPt;
for (int i = 0; i < mat.rows; i++) //rows
{
matPt = mat.ptr<float>(i);
for (int j = 0; j < mat.row(i).cols; j++) //col
{
retPt = toReturn.ptr<float>(i*mat.cols + j);
retPt[0] = matPt[j];
}
}
return toReturn;
}
Is there any way that I can increase the speed at which this method converts an n x n matrix into an n^2 x 1 matrix (or cv::Mat representing a vector)?
that solved most of the problem #berak, its running a lot faster now. however in some cases like below, the mat is not continuous. Any idea of how I can get an ROI in a continuous mat?
my method not looks like this:
static Mat mat2vec(Mat mat)
{
if ( ! mat.isContinuous() )
{
mat = mat.clone();
}
return mat.reshape(1,2500);
}
Problems occur at:
Mat patch = Mat(inputSource, Rect((inputPoint.x - (patchSize / 2)), (inputPoint.y - (patchSize / 2)), patchSize, patchSize));
Mat puVec = mat2vec(patch);
assuming that the data in your Mat is continuous, Mat::reshape() for the win.
and it's almost for free. only rows/cols get adjusted, no memory moved. i.e, mat = mat.reshape(1,1) would make a 1d float array of it.
Seeing this in OpenCV 3.2, but the function is now mat.reshape(1).

Assert thrown by OpenCV checkVector() on otherwise valid vector<vector<Point3f>>

Been chasing this bug all night, so please forgive any incoherence.
I'm attempting to use the OpenCV's calibrateCamera() to extract intrinsic and extrinsic parameters from a set of fifteen pictures whose object points and world points are given. From what I can tell from debugging, I'm grabbing valid points from the input files and placing them in a vector<Point3f>, which is itself placed into another vector.
I pass the whole shebang to calibrateCamera(),
double rms = calibrateCamera(worldPoints, pixelPoints, src.size(), intrinsic, distCoeffs, rvecs, tvecs);
which throws Assertion failed (ni >= 0) in unknown function, file ...\calibration.cpp, line 3173
Pulling up this file gives us
static void collectCalibrationData( InputArrayOfArrays objectPoints,
InputArrayOfArrays imagePoints1,
InputArrayOfArrays imagePoints2,
Mat& objPtMat, Mat& imgPtMat1, Mat* imgPtMat2,
Mat& npoints )
{
int nimages = (int)objectPoints.total();
int i, j = 0, ni = 0, total = 0;
CV_Assert(nimages > 0 && nimages == (int)imagePoints1.total() &&
(!imgPtMat2 || nimages == (int)imagePoints2.total()));
for( i = 0; i < nimages; i++ )
{
ni = objectPoints.getMat(i).checkVector(3, CV_32F);
CV_Assert( ni >= 0 );
total += ni;
}
...
So far as I know, a Point3f is of CV_32F depth, and I can see good data in the double vector just before calibrateCamera is called.
Any ideas what might be happening here? calibrateCamera() requires a vector<vector<Point3f>>, as said by http://aishack.in/tutorials/calibrating-undistorting-with-opencv-in-c-oh-yeah/ and the documentation; hopefully getMat(i) isn't failing due to that.
Could it possibly have been called on the vector<vector<Point2f>> of pixel points just after it? I have been over so many errors I am willing to believe anything.
Edit:
Consequently, checkVector()'s documentation was not really helpful
int cv::Mat::checkVector (int elemChannels, int depth = -1, bool RequireContinuous = true) const
returns N if the matrix is 1-channel (N x ptdim) or ptdim-channel (1 x N) or (N x 1); negative number otherwise
The problem is possibly in one of your InputArrayOfArrays arguments (in worldPoints precisely, if the assertion is thrown from the line pasted in your question). Mat:s should work just fine here.
I solved the same assertion error in my code by making all the 3 InputArrayOfArrays (or vector > and vector > in my case) same length vectors with fully populated entries. So my problem was in my architecture: my objectPoints vector was containing empty entries (even though the existing data was valid), and calibrate.cpp requires that no empty entries are present in any of the 3 InputArrayOfArrays. Btw I am using greyscale images for calibration so single channel data.
In calib3d source the most probable reason for throwing error is a null value if you have checked that data types match. You might try double-checking your valid input data:
1) count the # of valid calibration images from your chosen structure
validCalibImages = (int)goodCalibrationImages.size()
2) define worldPoints as vector<vector<Point3f> > worldPoints
3) IMPORTANT: resize to accommodate for data for each calibration entry
worldPoints.resize(validCalibImages)
4) populate with data e.g.
for(int k = 0; k < (int)goodCalibImages.size(); k++){
for(int i = 0; i < chessboardSize.height; i++){
for(int j = 0; j < chessboardSize.width; j++){
objectPoints[k].push_back(Point3f(i*squareSize, j*squareSize, 0));
}
}
}
'
Hope it helps!
I agree with FSaccilotto - call checkVector and make sure you are passing a vector of size n of Mat 1x1:{3 channel} and not vector of Mat 1 x n:{3 channel} or worse Mat 1 x n:{2 channel} which is what MatOfPoint spits out. That usually fixes 90% of assert failed issues. Explicitly declare the Mat yourself.
The object pattern is somewhat strange in that the x y z coords are in the channels not in the Mat dimensions.

C++ eigenvalue/vector decomposition, only need first n vectors fast

I have a ~3000x3000 covariance-alike matrix on which I compute the eigenvalue-eigenvector decomposition (it's a OpenCV matrix, and I use cv::eigen() to get the job done).
However, I actually only need the, say, first 30 eigenvalues/vectors, I don't care about the rest. Theoretically, this should allow to speed up the computation significantly, right? I mean, that means it has 2970 eigenvectors less that need to be computed.
Which C++ library will allow me to do that? Please note that OpenCV's eigen() method does have the parameters for that, but the documentation says they are ignored, and I tested it myself, they are indeed ignored :D
UPDATE:
I managed to do it with ARPACK. I managed to compile it for windows, and even to use it. The results look promising, an illustration can be seen in this toy example:
#include "ardsmat.h"
#include "ardssym.h"
int n = 3; // Dimension of the problem.
double* EigVal = NULL; // Eigenvalues.
double* EigVec = NULL; // Eigenvectors stored sequentially.
int lowerHalfElementCount = (n*n+n) / 2;
//whole matrix:
/*
2 3 8
3 9 -7
8 -7 19
*/
double* lower = new double[lowerHalfElementCount]; //lower half of the matrix
//to be filled with COLUMN major (i.e. one column after the other, always starting from the diagonal element)
lower[0] = 2; lower[1] = 3; lower[2] = 8; lower[3] = 9; lower[4] = -7; lower[5] = 19;
//params: dimensions (i.e. width/height), array with values of the lower or upper half (sequentially, row major), 'L' or 'U' for upper or lower
ARdsSymMatrix<double> mat(n, lower, 'L');
// Defining the eigenvalue problem.
int noOfEigVecValues = 2;
//int maxIterations = 50000000;
//ARluSymStdEig<double> dprob(noOfEigVecValues, mat, "LM", 0, 0.5, maxIterations);
ARluSymStdEig<double> dprob(noOfEigVecValues, mat);
// Finding eigenvalues and eigenvectors.
int converged = dprob.EigenValVectors(EigVec, EigVal);
for (int eigValIdx = 0; eigValIdx < noOfEigVecValues; eigValIdx++) {
std::cout << "Eigenvalue: " << EigVal[eigValIdx] << "\nEigenvector: ";
for (int i = 0; i < n; i++) {
int idx = n*eigValIdx+i;
std::cout << EigVec[idx] << " ";
}
std::cout << std::endl;
}
The results are:
9.4298, 24.24059
for the eigenvalues, and
-0.523207, -0.83446237, -0.17299346
0.273269, -0.356554, 0.893416
for the 2 eigenvectors respectively (one eigenvector per row)
The code fails to find 3 eigenvectors (it can only find 1-2 in this case, an assert() makes sure of that, but well, that's not a problem).
In this article, Simon Funk shows a simple, effective way to estimate a singular value decomposition (SVD) of a very large matrix. In his case, the matrix is sparse, with dimensions: 17,000 x 500,000.
Now, looking here, describes how eigenvalue decomposition closely related to SVD. Thus, you might benefit from considering a modified version of Simon Funk's approach, especially if your matrix is sparse. Furthermore, your matrix is not only square but also symmetric (if that is what you mean by covariance-like), which likely leads to additional simplification.
... Just an idea :)
It seems that Spectra will do the job with good performances.
Here is an example from their documentation to compute the 3 first eigen values of a dense symmetric matrix M (likewise your covariance matrix):
#include <Eigen/Core>
#include <Spectra/SymEigsSolver.h>
// <Spectra/MatOp/DenseSymMatProd.h> is implicitly included
#include <iostream>
using namespace Spectra;
int main()
{
// We are going to calculate the eigenvalues of M
Eigen::MatrixXd A = Eigen::MatrixXd::Random(10, 10);
Eigen::MatrixXd M = A + A.transpose();
// Construct matrix operation object using the wrapper class DenseSymMatProd
DenseSymMatProd<double> op(M);
// Construct eigen solver object, requesting the largest three eigenvalues
SymEigsSolver< double, LARGEST_ALGE, DenseSymMatProd<double> > eigs(&op, 3, 6);
// Initialize and compute
eigs.init();
int nconv = eigs.compute();
// Retrieve results
Eigen::VectorXd evalues;
if(eigs.info() == SUCCESSFUL)
evalues = eigs.eigenvalues();
std::cout << "Eigenvalues found:\n" << evalues << std::endl;
return 0;
}