I've implemented a Sobel Edge Detector and had some questions about computing edge orientations.
I'm using this function to compute edge intensities after having done the sobel kernel convolution.
Gxy = sqrt( pow(Gx, 2) + pow(Gy,2) )
Where Gx is sum of the convolution for the sobel kernel in the X direction and Gy is sum of the convolution for the sobel kernel in the Y direction. (note the sobel kernel in the X and Y direction are different kernels)
Y kernel:
1 2 1
0 0 0
-1 -2 -1
X kernel:
-1 0 1
-2 0 2
-1 0 1
when I try to compute the edge orientation (theta is in degrees), I'm using the following rules:
if Gy == 0 and Gx == 0, then theta = 0
if Gy != 0 and Gx == 0, then theta = 90
otherwise, theta = (arctan( Gy / Gx ) * 180) / PI
all my documentation is telling me the angles should be > 0 and < 360 and I continue to get edges with negative value orientations. Is there something I'm doing incorrectly when computing theta or my convolution? Or should i just add 360 or 180 to negative theta values?
thanks in advance,
It's hard to answer your question precisely because you haven't mentioned exactly how you calculate the arctan function.
For example, if you're using the standard library's atan function, then negative angles are to be expected.
Furthermore, you'll notice that atan with a single argument can only ever return values in the first and fourth quadrants (because, for example, tan 45 == tan 225 when using degrees).
If you really want an angle in one of the four quadrants (you should really ask yourself if this matters for your application), then have a look at atan2.
If you are using OpenCV, C++ you could do the following:
Mat gray = some grayscale image;
Calculate edge gradients in x/y axis'
Mat dx(gray.rows, gray.cols, CV_16SC1);
Mat dy(gray.rows, gray.cols, CV_16SC1);
int aperture_size=3;
Sobel(gray, dx, CV_32FC1, 1, 0, aperture_size, 1, 0, BORDER_REPLICATE);
Sobel(gray, dy, CV_32FC1, 0, 1, aperture_size, 1, 0, BORDER_REPLICATE);
Calculate the angle and magnitude using OpenCV's cartToPolar
Mat Mag(gray.size(), CV_32FC1);
Mat Angle(gray.size(), CV_32FC1);
cartToPolar(dx, dy, Mag, Angle, true);
Related
I want to implement the following using OpenCV (I'll post my attempt at the bottom of the post). I am aware that OpenCV has a function for something like this, but I want to try to write my own.
In an image (Mat) (the coordinate system is at the top left, since it is an image) of width width and height height, I want to display a filled ellipsewith the following properties:
it should be centered at (width/2, height/2)
the image should be binary, so the points corresponding to the ellipse should have a value of 1 and others should be 0
the ellipse should be rotated by angle radians around the origin (or degrees, this does not matter all that much, I can convert)
ellipse: semi-major axis parameter is a and semi-minor axis parameter is b and these two parameters also represent the size of these axes in the picture, so "no matter" the width and height, the ellipse should have a major axis of size 2*a and a minor axis of size 2*b
Ok, so I've found an equation similar to this (https://math.stackexchange.com/a/434482/403961) for my purpose. My code is as follows.. it does seem to do pretty well on the rotation side, but, sadly, depending on the rotation angle, the SIZE (major axis, not sure about the minor) visibly increases/decreases, which is not normal, since I want it to have the same size, independent of the rotation angle.
NOTE The biggest size is seemingly achieved when the angle is 45 or -45 degrees and the smallest for angles like -90, 0, 90.
Code:
inline double sqr(double x)
{
return x * x;
}
Mat ellipticalElement(double a, double b, double angle, int width, int height)
{
// just to make sure I don't use some bad values for my parameters
assert(2 * a < width);
assert(2 * b < height);
Mat element = Mat::zeros(height, width, CV_8UC1);
Point center(width / 2, height / 2);
for(int x = 0 ; x < width ; x++)
for(int y = 0 ; y < height ; y++)
{
if (sqr((x - center.x) * cos(angle) - (y - center.y) * sin(angle)) / sqr(a) + sqr((x - center.x) * sin(angle) - (y - center.y) * cos(angle)) / sqr(b) <= 1)
element.at<uchar>(y, x) = 1;
}
return element;
}
A pesky typo sneaked in your inequality. The first summand must be
sqr((x - center.x) * cos(angle) + (y - center.y) * sin(angle)) / sqr(a)
Note the plus sign instead of minus.
I'm using OpenCV 3.2.
I'd like to extract and draw all lines in this image.
For this, I first obtain the contours of the image. For example, I'm using the Canny algorithm, with a double threshold 100 (low) and 200 (high).
Mat image = cv::imread(<image_path>, cv::IMREAD_GRAYSCALE);
cv::Mat contours;
cv::Canny(image, contours, 100, 200);
Then, I call the HoughLines function with a resolution of 1 pixel and π / 45 radians. I just want those lines which have a length of at least 60 pixels.
std::vector<cv::Vec2f> lines;
cv::HoughLines(canny, lines, 1, CV_PI/45, 60);
This returns me a vector lines with the rho p and theta θ parameters in the Hough space of the desired lines. As we know, the line going through a contour pixel (x_i, y_i) is:
p = x_i cos(θ) + y_i sin(θ)
We know p and θ, so we know all the pixels in this line. Two easy points to calculate are A with x_i = 0 and B with y_i = 0.
A = (0, p / sin(θ))
B = (p / cos(θ), 0)
Let's draw them with the line function in blue color.
cv::cvtColor(image, image, CV_GRAY2BGR);
for (unsigned int i = 0; i < lines.size(); ++i) {
float p = lines[i][0];
float theta = lines[i][1];
cv::Point a(0, static_cast<int>(p / std::sin(theta)));
cv::Point b(static_cast<int>(p / std::cos(theta)), 0);
cv::line(image, a, b, cv::Scalar(255, 0, 0));
}
The result is that it only draws me 6 lines, of a total of 14 obtained. As you can see, only those lines that intersect the row 0 and column 0 of the image are drawn. What is the same, those lines which have A and B points in the image boundary. The rest of the lines have these points outside the image.
How can I achieve to draw all the lines in an easy way? I can calculate all the pixels of the obtained lines and draw them (we know them), but I'd like to draw them by minimizing lines of code and using OpenCV api.
I am currently trying to get the rotation of an object. I am using C++ and Bullet Physics. This is my code:
btScalar x, y, z;
body[0]->getCenterOfMassTransform().getBasis().getEulerZYX(z, y, x);
However, as I rotate the object around clockwise the number I get from the y (y is vertical in Bullet) axis goes from 0 to -90 to 0 to 90 and finally back to 0 for every quarter rotation. It is close but what I need is for it to go all the way from 0 to 360.
Bullet documentation says:
void getEulerZYX (btScalar &yaw, btScalar &pitch, btScalar &roll, unsigned int solution_number=1) const
and
solution_number Which solution of two possible solutions ( 1 or 2) are possible values
this is because euler angles are ambigous. have you tried solution 2?
I had the same problem. I using LibGDX with Bullet engine, so my code sample on Java, but I'm sure, that it will works on C++ too. Here is my solution (for Z axis):
body.getWorldTransform().getRotation(mRotation);
// That gives you an angle in all range but excluding (85, 95) and (-95, 85). For other axis you can try to get Pitch or Yaw.
float roll = mRotation.getRoll();
// That gives you an angle in range [0, 240). Clockwise and counterclockwise directions isn't detected.
float angle = mRotation.getAngleAround(0, 0, 1);
// Usually 0, but on (85, 95) and (-95, 85) becomes 1 and -1.
int gimbalPole = mRotation.getGimbalPole();
// Using roll (pitch/yaw for other axis) if it's defined, and using angle with gimble pole otherwise.
float rotation = (gimbalPole == 0) ? roll : angle * gimbalPole;
Obtained rotation will be in range (-180, 180). It can be easily converted to [0, 360) range:
if (rotation < 0) rotation += 360;
I am trying to implement a custom version of Histogram of Oriented Gradients. My gradient kernel is [-1.2 0 1.2]. This kernel has to be applied in x and y directions (along rows and along columns), to find the image gradients in x and y directions Gx and Gy.
In Matlab this would be something like
hx = [-1.2 0 1.2]
hy = hx' %transpose
Gx = imfilter(double(I),hx) %Gx is the gradient along x, I is the image
Gy = imfilter(double(I),hy) %Gy is the gradient along y
How do I do this in OpenCV ? I looked at create createSeparableLinearFilter, but it seems to give some sort of sum of Gx and Gy. I need to find Gx and Gy separately.
I am looking for something like
Ptr<FilterEngine> Fx = createRowFilter(...);
Ptr<FilterEngine> Fy = createColumnFilter(...);
Fx->apply(img, Gx, ...); //Gx is x gradient, Gx and Gy are float or double
Fy->apply(img, Gy, ...); //Gy is y gradient
Of course this can be done by writing my own for loop, visiting every pixel, but I was wondering whether there is any OpenCV way to do this.
I think you are looking for
filter2D
use it each time with a different kernel.
Solution from Mathai:
float kernelY[9] = {0,-1.0,0,0,0,0,0,1.0,0};
float kernelX[9] = {0,0,0,-1.0,0,1.0,0,0,0};
Mat filterY(3, 3, CV_32F, kernelY);
Mat filterX(3, 3, CV_32F, kernelX);
filter2D(img,dsty,-1 ,filterY, Point( -1, -1 ),0, BORDER_DEFAULT );
filter2D(img,dstx,-1 ,filterX, Point( -1, -1 ),0, BORDER_DEFAULT );
Follow this tutorial to make your own custom kernels. I think you need to make an NxN kernel for OpenCV to recognize it properly (basically it will be hx; hx; hx for Gx).
HTH
What I want to do is to raycast a pointcloud to a 2D image. What I have is a 3D PointCloud and a Viewpoint which is different to the general world coordinate system. I would like to raycast from this Viewpoint to generate a 2D image of the point cloud. So, I just need a method like getintersectedvoxel which is doing the casting for the whole area and not only for a single ray.
That is a projection from 3D to a camera. You can get it with the pinhole camera model equations (as shown here).
You need first 3 parameters that define your camera: the focal length f, and the center of the projection plane: cx, cy. With this you create a 3x3 matrix (I will use matlab syntax):
A = [ f 0 cx;
0 f cy;
0 0 1 ];
You can use something like cx = 0.5 * image_width, cy = 0.5 * image_height, and some value as f = 800 (try some of them to check how the image looks better).
Then, a 3x4 matrix with the transformation from the camera frame to the point cloud frame (you say you have it):
T = [ r11 r12 r13 tx;
r21 r22 r23 ty;
r31 r32 r33 tz ];
And finally, your point cloud in homogeneous coordinates, i.e. in a 4xN matrix for a point cloud with N points:
P = [ x1 x2 ... xN;
y1 y2 ... yN;
z1 z2 ... zN;
1 1 ... 1 ];
Now you can project the points:
S = A * T * P;
S is a 3xN matrix where the pixel coordinates of each i-th 3D point are:
x = S(1, i) / S(3, i);
y = S(2, i) / S(3, i);