Element-wise power using OpenCV - opencv

I am currently reading this book. The author wrote a code snippet on page 83 in order to (if i understand it correctly) calculate the element-wise power of two matrices. But i think the code doesn't fulfill its purpose because the matrix dst does not contain the element-wise power after the execution.
Here is the original code:
const Mat* arrays[] = { src1, src2, dst, 0 };
float* ptrs[3];
NAryMatIterator it(arrays, (uchar**)ptrs);
for( size_t i = 0; i < it.nplanes; i++, ++it )
{
for( size_t j = 0; j < it.size; j++ )
{
ptrs[2][j] = std::pow(ptrs[0][j], ptrs[1][j]);
}
}
Since the parameter of the constructor or cv::NAryMatIterator is const cv::Mat **, i think change of values in the matrix dst is not allowed.
I tried to assign ptrs[2][j] back in dst but failed to get the correct indices of dst. My questions are as follows:
Is there a convenient method for the matrix element-wise power, like A .^ B in Matlab?
Is there a way to use cv::NAryMatIterator to achieve this goal? If no, then what is the most efficient way to implement it?

You can get this working by converting the src1, src2 and dst to float (CV_32F) type matrices. This is because the code treats them that way in float* ptrs[3];.
An alternative implementation using opencv functions log, multiply and exp is given at the end.
As an example for your 2nd question,
Mat src1 = (Mat_<int>(3, 3) <<
1, 2, 3,
4, 5, 6,
7, 8, 9);
Mat src2 = (Mat_<uchar>(3, 3) <<
1, 2, 3,
1, 2, 3,
1, 2, 3);
Mat dst = (Mat_<float>(3, 3) <<
1, 2, 3,
4, 5, 6,
7, 8, 9);
src1.convertTo(src1, CV_32F);
src2.convertTo(src2, CV_32F);
cout << "before\n";
cout << dst << endl;
const Mat* arrays[] = { &src1, &src2, &dst, 0 };
float* ptrs[3];
NAryMatIterator it(arrays, (uchar**)ptrs);
for( size_t i = 0; i < it.nplanes; i++, ++it )
{
for( size_t j = 0; j < it.size; j++ )
{
ptrs[2][j] = std::pow(ptrs[0][j], ptrs[1][j]);
}
}
cout << "after\n";
cout << dst << endl;
outputs
before
[1, 2, 3;
4, 5, 6;
7, 8, 9]
after
[1, 4, 27;
4, 25, 216;
7, 64, 729]
If you remove the src1.convertTo(src1, CV_32F); or src2.convertTo(src2, CV_32F);, you won't get the desired result. Try it.
If this is a separate function, don't place the convertTo within the function, as it modifies the image representation, that could affect later operations. Instead, use convertTo on temporary Mats, like
Mat src132f, src232f, dst32f;
src1.convertTo(src132f, CV_32F);
src2.convertTo(src132f, CV_32F);
dst.convertTo(dst32f, CV_32F);
pow_mat(&src132f, &src232f, &dst32f); /* or whatever the name */
As for your first question, I'm not aware of such function. But you can try something like
Mat tmp;
cv::log(src1, tmp);
cv::multiply(src2, tmp, dst);
cv::exp(dst, dst);
using the relation that c = a^b is equivalent to c = e^(b.ln(a)). Here, the matrices should have type 32F or 64F. This produces
[1, 4, 27.000002;
4, 25.000002, 216.00002;
6.9999995, 64, 729.00006]
for the example above.

Related

Add vector to Matrix in Opencv

I have a question like this, ex:
I have a vector =
[1, 2, 3]
I have a matrix =
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
expected result is:
[1+1, 2+2, 3+3]
[4+1, 5+2, 6+3]
[7+1, 8+2, 9+3]
it is to "add vector (or row) to matrix".
Is there an API or convenient way?
ps: I am coding in C++.
You can't add a vector to a matrix.
You can add matrix and scalar or matrix and matrix.
To do what you want, you can add your row matrix to each row of your 3x3 matrix :
unsigned char values[9] = { 1,2,3,4,5,6,7,8,9 };
cv::Mat matrix(3,3,CV_8UC1, values);
unsigned char rowval[3] = { 1,2,3 };
cv::Mat rowmat(1,3,CV_8UC1, rowval);
std::cout << "input:\n";
std::cout << matrix << "\n";
std::cout << "rowmat:\n";
std::cout << rowmat << "\n";
for(int i=0;i<3;i++)
matrix.row(i) += rowmat;
std::cout << "result:\n";
std::cout << matrix << "\n";
Which outputs:
input:
[ 1, 2, 3;
4, 5, 6;
7, 8, 9]
rowmat:
[ 1, 2, 3]
result:
[ 2, 4, 6;
5, 7, 9;
8, 10, 12]
There is now straightfoward way of doing this. The two of many approaches I can think of are: mat addition and subsequent col addition.
Mat addition:
cv::Mat a = (cv::Mat_<uchar>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cv::Mat b = (cv::Mat_<uchar>(3, 3) << 1, 2, 3, 1, 2, 3, 1, 2, 3);
cv::Mat r = a + b;
Per col addition:
std::vector s{1,2,3};
for (int col = 0; col < a.cols; ++col)
{
a.col(col) += s[col];
}

Unable to access 6 channel Mat of type 16UC(6)

I am unable to access a Mat of type 16UC(6). Below is the code used to iterate over the Mat.
//6 channel Mat
int cols=1280, rows=720;
Mat mat1=Mat(cols, rows, CV_16UC(6), Scalar::all(0));
Mat grid(Size(cols, rows), CV_16UC2, Scalar::all(0));
//create a grid of numbers - the value of each pixel in the grid
contains the coordinate of the pixel
for (int i = 0; i < grid.rows; ++i) {
for (int j = 0; j < grid.cols; ++j) {
grid.at<Vec2s>(i, j)[0] = (ushort)j;
grid.at<Vec2s>(i, j)[1] = (ushort)i;
}
}
vector<Mat> imgs(2); //create copies of the grid for each image
for(int i=0;i<2;i++){
imgs[i] = grid.clone();
}
//Values in Mat1 are filled with values of imgs[0] and imgs[1] using
// some logic.
int rows=mat1.rows;
int channels=mat1.channels();
int cols=mat1.cols * channels;
uchar* p;
for(int i=0;i<rows;i++){
p=mat1.ptr<uchar>(i);
for(int j=0;j<cols;j+=6){
cout<<"Value 0 :"<<p[j]<<endl;
cout<<"Value 1 :"<<p[j+1]<<endl;
cout<<"Value 2 :"<<p[j+2]<<endl;
cout<<"Value 3 :"<<p[j+3]<<endl;
cout<<"Value 4 :"<<p[j+4]<<endl;
cout<<"Value 5 :"<<p[j+5]<<endl;
}
}
But im getting ^E and ^# as values. When tried casting to (int) I am getting all zeros.
I am able to access the Mat properly using MatIterator. I am not sure where I went wrong, there must be some issue with the Mat ype and the way I am trying to access the value.Can anyone help me in solving the issue.
You have:
grid.at<Vec2s>(i, j)[0] = (ushort)j;
Vec2s is for shorts, but you have Unsigned Short matrix. You should use Vec2w (not sure who came with the w... or why) that is for unsigned short.
This can be rewritten as:
grid.at<cv::Vec2w>(i, j)[0] = static_cast<ushort>(j);
Then, you show uchar values from the 16U matrix... each uchar is 8bit and each pixel is 16bit...
Here is an example of how you can access each pixel with iterators in a CV_16UC(6) matrix.
// create dummy 3x3 matrix
cv::Mat b(3,3,CV_16UC(6));
// use the templated functions begin and end (you may templated with <ushort>
// directly and it will represent the value of each channel of each pixel)
for (auto it = b.begin<cv::Vec<ushort, 6>>(); it != b.end<cv::Vec<ushort, 6>>(); ++it)
{
// assign some values (this part can be skipped if it is already filled)
(*it)[0] = 5;
(*it)[1] = 7;
(*it)[2] = 8;
(*it)[3] = 9;
(*it)[4] = 1;
(*it)[5] = 2;
// Print the Vec<ushort, 6>, OpenCV already has a way to print it
std::cout << *it << std::endl;
}
And the result of this small code is:
[5, 7, 8, 9, 1, 2]
[5, 7, 8, 9, 1, 2]
[5, 7, 8, 9, 1, 2]
[5, 7, 8, 9, 1, 2]
[5, 7, 8, 9, 1, 2]
[5, 7, 8, 9, 1, 2]
[5, 7, 8, 9, 1, 2]
[5, 7, 8, 9, 1, 2]
[5, 7, 8, 9, 1, 2]
Which is what we expect. You may have notice that I used cv::Vec<ushort, 6>, cv::Vec can be templated with any number of channels (probably there is a limit) and any type (I have only tested it with native numeric types). Actually cv::Vec2w or cv::Vec2s are just typedef of cv::Vec and cv::Vec respectively, and you can also create your typedef if you use it all over the code.
using Vec6w = cv::Vec<ushort, 6>;
and then you can replace it in the for loop:
...
for (auto it = b.begin<Vec6w>(); it != b.end<Vec6w>(); ++it)
...
and achieve the same result

SVM Predict returns a large value that isnt 1 or -1

So my goal here is to classify vehicles between sedans and SUVs. The training images I'm using are 29 150x200 images of sedans and SUVs, so my training_mat is a 29x30000 Mat and I use a double nested for loop to do this instead of .reshape because reshape wasn't working properly.
labels_mat is written so that a -1 corresponds to a sedan and a 1 corresponds to an SUV. I finally got svm->train to accept both Mats, and I expected that a new test_image fed into svm->predict would either yield a -1 or a 1. Unfortunately, svm->predict(test_image) returns an extremely high or low value like -8.38e08. Can anyone help me with this?
Here is the majority of my code:
for (int file_count = 1; file_count < (num_train_images + 1); file_count++)
{
ss << name << file_count << type; //'Vehicle_1.jpg' ... 'Vehicle_2.jpg' ... etc ...
string filename = ss.str();
ss.str("");
Mat training_img = imread(filename, 0); //Reads the training images from the folder
int ii = 0; //Scans each column
for (int i = 0; i < training_img.rows; i++)
{
for (int j = 0; j < training_img.cols; j++)
{
training_mat.at<float>(file_count - 1, ii) = training_img.at<uchar>(i, j); //Fills the training_mat with the read image
ii++;
}
}
}
imshow("Training Mat", training_mat);
waitKey(0);
//Labels are used as the supervised learning portion of the SVM. If it is a 1, its an SUV test image. -1 means a sedan.
int labels[29] = { 1, 1, -1, -1, 1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, 1 };
//Place the labels into into a 29 row by 1 column matrix.
Mat labels_mat(num_train_images, 1, CV_32S);
cout << "Beginning Training..." << endl;
//Set SVM Parameters (not sure about these values)
Ptr<SVM> svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setC(.1);
svm->setKernel(SVM::POLY);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
svm->setGamma(3);
svm->setDegree(3);
cout << "Parameters Set..." << endl;
svm->train(training_mat, ROW_SAMPLE, labels_mat);
cout << "End Training" << endl;
waitKey(0);
Mat test_image(1, image_area, CV_32FC1); //Creates a 1 x 1200 matrix to house the test image.
Mat SUV_image = imread("SUV_1.jpg", 0); //Read the file folder
int jj = 0;
for (int i = 0; i < SUV_image.rows; i++)
{
for (int j = 0; j < SUV_image.cols; j++)
{
test_image.at<float>(0, jj) = SUV_image.at<uchar>(i, j);
jj++;
}
}
//Should return a 1 if its an SUV, or a -1 if its a sedan
float result = svm->predict(test_image);
cout << "Result: " << result << endl;
The output will not be -1 and 1. Machine learning methods, such as SVM, predict membership as the sign of the result. So a negative value means -1 and a positive value means 1.
Similarly, some other methods, such as logistic regression method use probability to predict membership where there are often 0 and 1. If probability <0.5, its membership is 0, otherwise 1.
BTW: your question is not a C++ question.
You forgot to fill your labels into the labels_mat. Simple mistake but it happens to everyone...
Mat labels_mat(num_train_images, 1, CV_32S, labels);
And that should work out fine.

What are the different ways of constructing cv::Mat?

OpenCV Version 3.2.0
I am reading Bradski and trying to make Different cv::Mat constructors - single channel.
Can someone please tell, why the constructors do not work?
float data1[6] = {1,2,3,4,5,6};
float data2[6] = {10,20,30,40,50,60};
float data3[6] = {100,200,300,400,500,600};
cv::Mat mat1(3,4,CV_32FC1); //OK
cv::Mat mat2(3,4,CV_32FC1,cv::Scalar(33.3)); //OK
cv::Mat mat3(3,4,CV_32FC1,data1,sizeof(float)); //OK
cv::Mat mat4(cv::Size(3,4),CV_32FC1); //OK
cv::Mat mat5(cv::Size(3,4),CV_32FC1,cv::Scalar(66.6)); //OK
cv::Mat mat6(cv::Size(3,4),CV_32FC1,data2,sizeof(float)); //OK
int sz[] = {8, 8, 8};
cv::Mat bigCube1(3, sz, CV_32FC1); // OK
cv::Mat bigCube2(3, sz, CV_32FC1, cv::Scalar::all(99)); // OK
cv::Mat bigCube3(3, sz, CV_32FC1, data3, 4); // Not OK, How to initialise a 3D from data?
std::cout << mat1 << std::endl << mat2 << std::endl << mat3 << std::endl << mat4 << std::endl << mat5 << std::endl << mat6 << std::endl; // OK
std::cout << bigCube1.at<float>(10,10,10) << std::endl << bigCube2.at<float>(10,10,10) << std::endl; // OK
cv::Mat img_rgb = cv::imread("lena.jpg", CV_LOAD_IMAGE_COLOR);
std::vector<cv::Range> ranges(3, cv::Range(2,3));
cv::Mat roiRange( img_rgb, cv::Range(100, 300), cv::Range(0, 512)); //OK
cv::Mat roiRect( img_rgb, cv::Rect(0,100,512,200)); // OK
cv::Mat roiRangeMultiple( bigCube1, ranges); // OK
cv::namedWindow("range", CV_WINDOW_AUTOSIZE);
imshow("range", roiRange); // OK
cv::namedWindow("rect", CV_WINDOW_AUTOSIZE);
imshow("rect", roiRect); // OK
std::cout << roiRangeMultiple.at<float>(0,1,1); // Not OK. Expecting a float value as answer
cv::waitKey(0);
The corresponding answers are:
[4.6634629e-10, 0, 0, 0;
0, 0, 0, 0;
127.62516, 2.8025969e-45, 0, 0]
[33.299999, 33.299999, 33.299999, 33.299999;
33.299999, 33.299999, 33.299999, 33.299999;
33.299999, 33.299999, 33.299999, 33.299999]
[1, 2, 3, 4;
2, 3, 4, 5;
3, 4, 5, 6]
[0, 0, 0;
0, 0, 0;
0, 0, 0;
0, 0, 0]
[66.599998, 66.599998, 66.599998;
66.599998, 66.599998, 66.599998;
66.599998, 66.599998, 66.599998;
66.599998, 66.599998, 66.599998]
[10, 20, 30;
20, 30, 40;
30, 40, 50;
40, 50, 60]
0 // bigCube1
99 // bigCube2
And then the corresponding answers for lena.jpg is the cropped version from Range and Rect. I dont know how to use the ranges though.
Multiple issues.
cv::Mat mat3(3,4,CV_32FC1,data1,sizeof(float));
This will crash in debug mode, failing an assertion. Even though this is not in the documentation, the step size must be at least the length of a row (i.e. no overlap is allowed).
The correct code for this scenario would be something like.
float data1[12] = { 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6 };
cv::Mat mat3(3, 4, CV_32FC1, data1, 4 * sizeof(float));
cv::Mat mat6(cv::Size(3,4),CV_32FC1,data2,sizeof(float));
Similar situation as in previous case. Also note that this produces a differently shaped array -- previous was 3 rows, 4 columns, this one has 4 rows and 3 columns (see docs of cv::Size).
float data2[12] = { 10, 20, 30, 40, 20, 30, 40, 50, 30, 40, 50, 60 };
cv::Mat mat6(cv::Size(3, 4), CV_32FC1, data2, 3 * sizeof(float));
cv::Mat bigCube1(3, sz, CV_8UC1);
std::cout << bigCube1 << std::endl;
Formatting of arrays with more than 2 dimensions is not supported.
You can test that the array was correctly created by manually printing all the values:
for (auto const& v : cv::Mat1b(bigCube2)) {
std::cout << uint(v) << " ";
}
std::cout << "\n";
cv::Mat bigCube3(3, sz, CV_32FC1, data3, 4);
The problem here is the last parameter. From the docs
cv::Mat::Mat(int ndims,
const int * sizes,
int type,
void * data,
const size_t * steps = 0
)
steps - Array of ndims-1 steps in case of a multi-dimensional array (the last step is always set to the element size). If not specified, the matrix is assumed to be continuous.
You're not passing an array of steps as the last parameter (only a single integer)
you don't pass enough data
and the rows would again overlap.
One way to do this would be something like
float data3[8 * 8 * 8];
// Populate the data with sequence 0..511
std::iota(std::begin(data3), std::end(data3), 0.0f);
int sz[] = { 8, 8, 8 };
size_t steps[] = { 8 * 8 * sizeof(float), 8 * sizeof(float) };
cv::Mat bigCube3(3, sz, CV_32FC1, data3, steps);
cv::Mat bigCube1(3, sz, CV_8UC1);
// ...
std::cout << roiRangeMultiple.at<float>(0,1,1); // Not OK. Expecting a float value as answer
The data type is CV_8UC1, so each element is an unsigned char. That means you shouldn't be extracting float values from it. Your expectation is incorrect. (Now I see you changed the code in your question).
Also, note that with cv::Range "start is an inclusive left boundary of the range and end is an exclusive right boundary of the range". Since you extract cv::Range(2,3) in each axis, the resulting Mat is 1 x 1 x 1. Hence, you're accessing elements out of range (again, this would trigger a debug mode assertion).
std::cout << roiRangeMultiple.at<unsigned char>(0,0,0);
After the change you have the correct type. However notice that you never initialize bigCube1. You most likely get a 0.0f as a result, which will print as 0. You can try this yourself, just execute std::cout << 0.0f; and see.

can't use copyTo to copy a rectangular matrix into a 3D matrix

I have 5rows x 4cols cv::Mat:
int output_size[] = {5,4};
cv::Mat im1(2, output_size, CV_32FC1);
float* ptr = (float*)im1.data;
for (unsigned int r = 0; r < output_size[0]; r++) {
for (unsigned int c = 0; c < output_size[1]; c++) {
*ptr = (r*im1.size.p[1])+c;
std::cout << *ptr++ << ",";
}
std::cout << std::endl;
}
So that matrix looks like this:
[ 0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19]
Furthermore, I have another 3depth x 5rows x 4cols cv::Mat:
int output_size2[] = {3,5,4};
cv::Mat im2(3, output_size2, CV_32FC1);
im2 = 0;
Now I want to copy im1 into, let's say, the second layer of im2. I do the following:
cv::Range rngs[] = {cv::Range(1,2), cv::Range::all(), cv::Range::all()};
cv::Mat dst = im2(rngs);
im1.copyTo(dst);
This doesn't seem to work. im1.copyTo(dst) has no effect on im2 - all the second layer values remain zero after the operation. After some introspection, it seems like opencv is finding that since the size of dst is 1x5x4 and not 5x4, it reassigns dst.
What would be the right way to copy a rectangular matrix into one layer of a 3D matrix?
Ok this works:
void* ptr = im2.data + im2.step[0]*1;
memcpy(ptr, (void*)im1.data, im1.total()*sizeof(float));
but is there an "opencv" way to solve it.
If you have n 2-dim images and you want to use them as layers in a 3D matrix that has a third dimension of size n you can use cv::merge. see documentation.
See also:
cv:split documentation

Resources