Applying a kernel with OpenCV in C++ - c++

If I apply a Sobel filter to an image in Python using scipy.ndimage.filters.convole I get meaningful results, for example, for this simple input image img
0 255 0
0 255 0
0 255 0
the convolution
dimage.filters.convolve(img, Kx)
with Kx
-1 0 1
-2 0 2
-1 0 1
returns a meaningful gradient in x-direction:
-1020 0 1020
-1020 0 1020
-1020 0 1020
I don't know how to get a equivalent result using openCV2 in C++ though. When I define the input image by
int image_data[9] = {0, 255, 0, 0, 255, 0, 0, 255, 0};
cv::Mat image = cv::Mat(3, 3, CV_32F, image_data);
and apply the kernel by
cv::Mat gradientx;
double sobelx_data[9] = {-1, 0, 1, -2, 0, 2, -1, 0, 1};
cv::Mat sobelx = cv::Mat(3, 3, CV_32F, sobelx_data);
cv::filter2D(image, gradientx, -1, sobelx);
I get the following result from
for(int row=0; row<gradientx.rows; row++) {
for(int col=0; col<gradientx.cols; col++) {
std::cout << gradientx.at<int>(row,col) << std::endl;
}
}
it returns the following image
478 -2147482660 478
478 -2147482660 478
478 -2147482660 478
There seems to be an overflow problem, but I don't know why. Trying to get values from gradientx.at<double>(row,col) produces
-1.68911e-311 8.10602e-312 8.11663e-312
-1.68911e-311 8.10602e-312 8.11663e-312
-1.68911e-311 2.122e-314 8.54412e-72
Can someone tell me why this is? Isn't filter2D supposed to do a 2D convolution on the image, and why do I get weird values when addressing the output pixels with <double>? Thank you.

Okay, here's your code with the types corrected (I've also added more parameters to filter2D):
float image_data[9] = {0, 255, 0, 0, 255, 0, 0, 255, 0};
cv::Mat image = cv::Mat(3, 3, CV_32F, image_data);
std::cout << "image = " << std::endl << image << std::endl;
cv::Mat gradientx;
float sobelx_data[9] = {-1, 0, 1, -2, 0, 2, -1, 0, 1};
cv::Mat sobelx = cv::Mat(3, 3, CV_32F, sobelx_data);
std::cout << "sobelx = " << std::endl << sobelx << std::endl;
cv::filter2D(image, gradientx, -1, sobelx, cv::Point(-1, -1), 0,
cv::BORDER_DEFAULT);
std::cout << "gradientx = " << std::endl << gradientx << std::endl;
The result is:
image =
[0, 255, 0;
0, 255, 0;
0, 255, 0]
sobelx =
[-1, 0, 1;
-2, 0, 2;
-1, 0, 1]
gradientx =
[0, 0, 0;
0, 0, 0;
0, 0, 0]
If you look at the top of the documentation page on filtering, you'll see all of the border types that OpenCV uses. By default, filter2D uses BORDER_REFLECT_101. This is probably not what we want, so let's change it to BORDER_REPLICATE.
cv::filter2D(image, gradientx, -1, sobelx, cv::Point(-1, -1), 0,
cv::BORDER_REPLICATE);
Result:
image =
[0, 255, 0;
0, 255, 0;
0, 255, 0]
sobelx =
[-1, 0, 1;
-2, 0, 2;
-1, 0, 1]
gradientx =
[1020, 0, -1020;
1020, 0, -1020;
1020, 0, -1020]
That's better, but the values are flipped. If you look at the bottom of the function description for filter2D you'll see that it actually computes the cross correlation rather than the convolution. So we need to flip the kernel to get the correct results.
cv::Mat sobelxflip;
cv::flip(sobelx, sobelxflip, -1);
cv::filter2D(image, gradientx, -1, sobelxflip, cv::Point(-1, -1), 0,
cv::BORDER_REPLICATE);
std::cout << "gradientx = " << std::endl << gradientx << std::endl;
Result:
gradientx =
[-1020, 0, 1020;
-1020, 0, 1020;
-1020, 0, 1020]

Related

How to change the color of each pixel of image using openCV [duplicate]

This question already has answers here:
How to fill OpenCV image with one solid color?
(9 answers)
Closed 4 years ago.
I am new in OpenCV. I have a image what I want is to change the color of each and every pixel of image with any single color.
I found that code but when I run this part of code then a exception is generated.
for (i = 0;i < img1.rows;i++) {
for (j = 0;j < img1.cols;j++) {
img1.at<Vec3b>(i, j) = Vec3b(255, 105, 240);
}
}
Can anyone please tell me the solution.
Or what I found is that this take a lot of time for the conversion So if their is any other approach then please tell me.
// Make a 3 channel image
cv::Mat img(480,640,CV_8UC3);
// Fill entire image with cyan (blue and green)
img = cv::Scalar(255,255,0);
You can use Mat::operator() to select the roi and then assign a value to it.
void main()
{
cv::Mat img = cv::Mat::ones(5, 5, CV_8UC3);
img({2, 4}, {2, 4}) = cv::Vec3b(7, 8, 9);
cout << img;
}
This will print
[ 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0;
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0;
1, 0, 0, 1, 0, 0, 7, 8, 9, 7, 8, 9, 1, 0, 0;
1, 0, 0, 1, 0, 0, 7, 8, 9, 7, 8, 9, 1, 0, 0;
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0]
To fill image with single color, use rectangle with CV_FILLED argument.
(there might be some reasons for exception - image was not created, wrong pixel format etc - it is hard to diagnose a problem with given information)

Find elements in Mat, Opencv c++

I came across this:
cv::Mat Mat_out;
cv::Mat Mat2(openFingerCentroids.size(), CV_8UC1, cv::Scalar(2)); imshow("Mat2", Mat2);
cv::Mat Mat3(openFingerCentroids.size(), CV_8UC1, cv::Scalar(3)); imshow("Mat3", Mat3);
cv::bitwise_and(Mat2, Mat3, Mat_out); imshow("Mat_out", Mat_out);
Why does Mat_out contain all 2? Bit-wise operation of a matrix of all 2s and 3s should give me 0, right? Since 2 is not equal to 3?
Anyway, this is the simple thing I tried to implement: (like find function of MATLAB)
Mat_A = {1, 1, 0, 9, 0, 5;
5, 0, 0, 0, 9, 0;
1, 2, 0, 0, 0, 0};
Output expected, if I'm searching for all 5s:
Mat_out = {0, 0, 0, 0, 0, 5;
5, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0 };
How can I do this in OpenCV using C++??

The safest way to add integer into array value without having to call it?

What is the safest way to add integer into array value without having to call the array value ? In this case I would rather not to call the array value because the array is inside a nested loop and the loop itself can repeat for thousands times.
For example dots[1] = I want to add value of this array with 3. Here's my sample code :
void box(const Mat &dots, Mat &newdots, int rows, int cols)
{
for (int i = 0; i < dots.rows; i++) {
for (int j = 0; j < dots.cols; j++) {
newdots.at<Vec3b>(i, j)[0] = dots.at<Vec3b>(i, j)[0];
newdots.at<Vec3b>(i, j)[1] = dots.at<Vec3b>(i, j)[1]; //add this with 3
newdots.at<Vec3b>(i, j)[2] = dots.at<Vec3b>(i, j)[2]; //add this with 5
}
}
Is it possible ? Any suggestion how to do it ? Thanks.
This simplest way is to use cv::add, which overloads the + operator for the Mat class:
// Create a Mat of all 0's
cv::Mat dots = cv::Mat(5, 4, CV_8UC3, cv::Scalar(0,0,0));
std::cout << "dots:\n" << dots << std::endl;
// Add 0 to the B channel, 3 to the G channel, and 5 to R
cv::Mat newdots = dots + cv::Scalar(0, 3, 5);
std::cout << "newdots:\n" << newdots << std::endl;
Result:
dots:
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
newdots:
[ 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5;
0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5;
0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5;
0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5;
0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5]
Note that dots += Scalar(0,3,5) also works if you just want to modify the original Mat.

How to resize without interpolation (zero-padding) in OpenCV?

Is there an efficient way to resize an image in OpenCV without using any interpolation? Instead of the conventional "resize" I would like my image to remap the pixels into a larger image but pad everything else with 0.
e.g. to scale up img1 below 2x to img2:
img1 = [ 1, 2, 3,
4, 5, 6,
7, 8, 9 ]
cv::resize(img1, img2, cv::Size(6, 6));
img2 = [ 1, 0, 2, 0, 3, 0,
0, 0, 0, 0, 0, 0,
4, 0, 5, 0, 6, 0,
0, 0, 0, 0, 0, 0,
7, 0, 8, 0, 9, 0,
0, 0, 0, 0, 0, 0 ]
I know the obvious way is to just use a for loop, but I'm wondering if there is a more efficient way using an OpenCV call?
One option that comes to mind would be to use cv::resize with INTER_NEAREST and then mask out the unwanted pixels.
Example:
#include <opencv2/opencv.hpp>
#include <cstdint>
#include <iostream>
int main()
{
cv::Mat m1((cv::Mat_<uint8_t>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9));
std::cout << "Input:\n" << m1 << "\n\n";
cv::Mat mask((cv::Mat_<uint8_t>(2, 2) << 255, 0, 0, 0));
mask = cv::repeat(mask, m1.rows, m1.cols);
std::cout << "Mask:\n" << mask << "\n\n";
cv::Mat m2;
cv::resize(m1, m2, cv::Size(), 2, 2, cv::INTER_NEAREST);
std::cout << "Resized:\n" << m2 << "\n\n";
cv::bitwise_and(m2, mask, m2);
std::cout << "Masked:\n" << m2 << "\n\n";
}
Console output:
Input:
[ 1, 2, 3;
4, 5, 6;
7, 8, 9]
Mask:
[255, 0, 255, 0, 255, 0;
0, 0, 0, 0, 0, 0;
255, 0, 255, 0, 255, 0;
0, 0, 0, 0, 0, 0;
255, 0, 255, 0, 255, 0;
0, 0, 0, 0, 0, 0]
Resized:
[ 1, 1, 2, 2, 3, 3;
1, 1, 2, 2, 3, 3;
4, 4, 5, 5, 6, 6;
4, 4, 5, 5, 6, 6;
7, 7, 8, 8, 9, 9;
7, 7, 8, 8, 9, 9]
Masked:
[ 1, 0, 2, 0, 3, 0;
0, 0, 0, 0, 0, 0;
4, 0, 5, 0, 6, 0;
0, 0, 0, 0, 0, 0;
7, 0, 8, 0, 9, 0;
0, 0, 0, 0, 0, 0]
Update
If we eliminate parts of Miki's code that are unnecessary for our specific scenario, we pretty much reduce it to a simple loop.
Doing some quick comparisons, this turns out to be somewhat faster.
#include <opencv2/opencv.hpp>
#include <chrono>
#include <cstdint>
#include <iostream>
cv::Mat resize_1(cv::Mat image)
{
cv::Mat result(cv::Mat::zeros(image.rows * 2, image.cols * 2, CV_8UC1));
for (int ra(0); ra < image.rows; ++ra) {
for (int ca = 0; ca < image.cols; ++ca) {
result.at<uint8_t>(ra * 2, ca * 2) = image.at<uint8_t>(ra, ca);
}
}
return result;
}
cv::Mat resize_2(cv::Mat image)
{
cv::Mat mask((cv::Mat_<uint8_t>(2, 2) << 255, 0, 0, 0));
mask = cv::repeat(mask, image.rows, image.cols);
cv::Mat result;
cv::resize(image, result, cv::Size(), 2, 2, cv::INTER_NEAREST);
cv::bitwise_and(result, mask, result);
return result;
}
template<typename T>
void timeit(T f)
{
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::microseconds;
cv::Mat m1((cv::Mat_<uint8_t>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9));
m1 = cv::repeat(m1, 1024, 1024);
high_resolution_clock::time_point t1 = high_resolution_clock::now();
for (uint32_t i(0); i < 256; ++i) {
cv::Mat result = f(m1);
}
high_resolution_clock::time_point t2 = high_resolution_clock::now();
auto duration = duration_cast<microseconds>(t2 - t1).count();
double t_ms(static_cast<double>(duration) / 1000.0);
std::cout
<< "Total = " << t_ms << " ms\n"
<< "Iteration = " << (t_ms / 256) << " ms\n"
<< "FPS = " << (256 / t_ms * 1000.0) << "\n";
}
int main()
{
timeit(&resize_1);
timeit(&resize_2);
}
Timing:
resize_1
Total = 6344.86 ms
Iteration = 24.7846 ms
FPS = 40.3476
resize_2
Total = 7271.31 ms
Iteration = 28.4036 ms
FPS = 35.2068
Update 2
Parallelized version:
class ResizeInvoker : public cv::ParallelLoopBody
{
public:
ResizeInvoker(cv::Mat const& src, cv::Mat& dst)
: image(src)
, result(dst)
{
}
void operator()(const cv::Range& range) const
{
for (int y(range.start); y < (range.end); ++y) {
for (int x(0); x < image.cols; ++x) {
result.at<uint8_t>(y * 2, x * 2) = image.at<uint8_t>(y, x);
}
}
}
cv::Mat const& image;
cv::Mat& result;
};
cv::Mat resize_3(cv::Mat image)
{
cv::Mat result(cv::Mat::zeros(image.rows * 2, image.cols * 2, CV_8UC1));
ResizeInvoker loop_body(image, result);
cv::parallel_for_(cv::Range(0, image.rows)
, loop_body
, result.total() / (double)(1 << 16));
return result;
}
Timing:
resize_3
Total = 3876.63 ms
Iteration = 15.1431 ms
FPS = 66.0367
Update 3
We can do a little better if we use raw pointers in the invoker:
void operator()(const cv::Range& range) const
{
for (int y(range.start); y < (range.end); ++y) {
uint8_t* D = result.data + result.step * y * 2;
uint8_t const* S = image.data + image.step * y;
for (int x(0); x < image.cols; ++x) {
D[x * 2] = S[x];
}
}
}
Timing:
Total = 3387.87 ms
Iteration = 13.2339 ms
FPS = 75.5636
You can use the Kronecker product of your image and a pattern like:
1, 0
0, 0
The result is:
Input:
[1, 2, 3;
4, 5, 6;
7, 8, 9]
Output:
[1, 0, 2, 0, 3, 0;
0, 0, 0, 0, 0, 0;
4, 0, 5, 0, 6, 0;
0, 0, 0, 0, 0, 0;
7, 0, 8, 0, 9, 0;
0, 0, 0, 0, 0, 0]
Code:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
Mat1b kron(const Mat1b& A, const Mat1b& B)
{
Mat1b K(A.rows * B.rows, A.cols * B.cols, uchar(0));
for (int ra = 0; ra < A.rows; ++ra)
{
for (int ca = 0; ca < A.cols; ++ca)
{
K(Range(ra*B.rows, (ra + 1)*B.rows), Range(ca*B.cols, (ca + 1)*B.cols)) = B.mul(A(ra, ca));
}
}
return K;
}
int main()
{
Mat1b img = (Mat1b(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
std::cout << "Input:\n" << img << "\n\n";
// Define the pattern
Mat1b pattern = (Mat1b(2, 2) << 1, 0,
0, 0);
Mat1b out = kron(img, pattern);
std::cout << "Output:\n" << out << "\n\n";
return 0;
}
OpenCV doesn't have the Kronecker product implemented, so you need to write a custom function. For a more general implementation that works with all data types (1 channel) have a look here.
I found that #Dan Masek approach is faster. This is because my kron implementation is not optimized. I expect this approach to work pretty well with a good implementation.
Thought of sharing the following approach as it is a bit different. I don't know how efficient this would be compared to other methods. At least you can use opencv calls without any looping and easily use arbitrary scale factors for x and y.
First convert your image to floating point type, then scale it using warpAffine (use linear interpolation). Resize the same image using nearest-neighbor method. Compare the two resulting images element-wise to get a mask. Use this mask to copy the relevant elements from any of the result images.
Here's the code and some results I get:
uchar data[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Mat im(3, 3, CV_8U, data);
im.convertTo(im, CV_32F);
// x and y scale
int xscale = 2, yscale = 2;
Size size(im.cols * xscale, im.rows * yscale);
float tr[] = {xscale, 0, 0, 0, yscale, 0};
Mat m(2, 3, CV_32F, tr); // transformation matrix
Mat resized1, resized2;
warpAffine(im, resized1, m, size); // affine scaling with linear interpolation
resize(im, resized2, size, 0, 0, INTER_NEAREST); // resize with nearest neighbor
// get the mask
Mat resized = resized1 == resized2;
// copy the pixels
resized1.copyTo(resized, resized);
cout << "image:\n" << im << endl;
cout << "M:\n" << m << endl;
cout << "affine(scaled):\n" << resized1 << endl;
cout << "resized:\n" << resized2 << endl;
cout << "mask:\n" << resized << endl;
cout << "output:\n" << resized << endl;
For xscale = 2, yscale = 2
image:
[1, 2, 3;
4, 5, 6;
7, 8, 9]
M:
[2, 0, 0;
0, 2, 0]
affine(scaled):
[1, 1.5, 2, 2.5, 3, 1.5;
2.5, 3, 3.5, 4, 4.5, 2.25;
4, 4.5, 5, 5.5, 6, 3;
5.5, 6, 6.5, 7, 7.5, 3.75;
7, 7.5, 8, 8.5, 9, 4.5;
3.5, 3.75, 4, 4.25, 4.5, 2.25]
resized:
[1, 1, 2, 2, 3, 3;
1, 1, 2, 2, 3, 3;
4, 4, 5, 5, 6, 6;
4, 4, 5, 5, 6, 6;
7, 7, 8, 8, 9, 9;
7, 7, 8, 8, 9, 9]
mask:
[1, 0, 2, 0, 3, 0;
0, 0, 0, 0, 0, 0;
4, 0, 5, 0, 6, 0;
0, 0, 0, 0, 0, 0;
7, 0, 8, 0, 9, 0;
0, 0, 0, 0, 0, 0]
output:
[1, 0, 2, 0, 3, 0;
0, 0, 0, 0, 0, 0;
4, 0, 5, 0, 6, 0;
0, 0, 0, 0, 0, 0;
7, 0, 8, 0, 9, 0;
0, 0, 0, 0, 0, 0]
For xscale = 4, yscale = 3
output:
[1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Opencv Matrix Range L value : Is this a bug?

It seems to me that using Matrix with Ranges as an L-value (assignment target) should work or not (and if not a compiler error would be nice) but not both depending on the particulars of a legitimate r-value.
cout << "hi mom" << endl;
Mat Img0=Mat::zeros(7,7,CV_8UC1);
Mat Img1=Mat::ones(7,7,CV_8UC1);
cout << Img0 << endl;
cout << Img1 << endl;
Img0(Range::all(), Range::all()) = Img1;
cout << Img0 << endl;
Img0(Range::all(), Range::all()) = 1;
cout << Img0 << endl;
Below is the output from the above. The first two matrix print outs are of Img0 and Img1 as initialized by Mat::zeros and Mat::ones respectively.
The third matrix print out is Img0 again but after
Img0(Range::all(), Range::all()) = Img1;
which I expected would set Img0 to Img1; i.e. all ones; but it's not. It's still all zeros.
The fourth/last matrix print out is the result of
Img0(Range::all(), Range::all()) = 1;
Which has the same L value as the third assignment but it works when a scalar is the Rvalue (unlike the third which as a matrix as the RValue).
Is there some sense in this that I'm missing? Should this r-value distinction behavior be allowed? It seems inconsistent to me.
[0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0]
[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, 1, 1, 1, 1, 1, 1;
1, 1, 1, 1, 1, 1, 1;
1, 1, 1, 1, 1, 1, 1]
[0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0]
[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, 1, 1, 1, 1, 1, 1;
1, 1, 1, 1, 1, 1, 1;
1, 1, 1, 1, 1, 1, 1]
No, this is not a bug.
This line Img0(Range::all(), Range::all()) = Img1; doesn't work as expected because Img0(Range::all(), Range::all()) forms a temporary header that is further assigned to another header, which is Img1. Remember that each of these operations is O(1), that is, no data is copied. Thus, no real assignment happens.
You can realize this effect more clearly by doing this:
(Img0(Range::all(), Range::all()) = Img1) = 2;
cout << Img0 << endl;
cout << Img1 << endl;
If you have understood what I described above, you should be aware of that the code will only change the value of Img1. And the output is:
[0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0;
0, 0, 0, 0, 0, 0, 0]
[2, 2, 2, 2, 2, 2, 2;
2, 2, 2, 2, 2, 2, 2;
2, 2, 2, 2, 2, 2, 2;
2, 2, 2, 2, 2, 2, 2;
2, 2, 2, 2, 2, 2, 2;
2, 2, 2, 2, 2, 2, 2;
2, 2, 2, 2, 2, 2, 2]
Further reading: check out similar effect happened to Mat::row().