I have referenced following link to develop following csharp code to detect angle of Image. https://stackoverflow.com/a/34285205/7805023
Image<Gray, byte> imgout = imgInput.Convert<Gray, byte>().Not().ThresholdBinary(new
Gray(50), new Gray(255));
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
Mat hier = new Mat();
CvInvoke.FindContours(imgout, contours, hier, Emgu.CV.CvEnum.RetrType.External, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);
for (int i = 0; i <= 1; i++)
{
Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
RotatedRect box = CvInvoke.MinAreaRect(contours[i]);
PointF[] Vertices = box.GetVertices();
PointF point = box.Center;
PointF edge1 = new PointF(Vertices[1].X - Vertices[0].X,
Vertices[1].Y - Vertices[0].Y);
PointF edge2 = new PointF(Vertices[2].X - Vertices[1].X,Vertices[2].Y - Vertices[1].Y);
double edge1Magnitude = Math.Sqrt(Math.Pow(edge1.X, 2) + Math.Pow(edge1.Y, 2));
double edge2Magnitude = Math.Sqrt(Math.Pow(edge2.X, 2) + Math.Pow(edge2.Y, 2));
PointF primaryEdge = edge1Magnitude > edge2Magnitude ? edge1 : edge2;
PointF reference = new PointF(Vertices[1].X, Vertices[0].Y);
double thetaRads = Math.Acos((primaryEdge.X * reference.X) + (primaryEdge.Y * reference.Y))/(edge1Magnitude* edge2Magnitude);
double thetaDeg = thetaRads * 180 / Math.PI;
}
I am getting NaN as output for angle. Please go through the code let me know what wrong I have done?
I have used emgucv for image processing.
Any help will be highly appreciated.
NaN, Not a Number, is returned by Math.Acos() when the provided value is not between -1 and 1.
Your calculation of thetaRads is wrong, you need to calculate the Acos of the vectors' dot product divided by the product of the vectors' magnitudes, as per:
double thetaRads = Math.Acos(((primaryEdge.X * reference.X) + (primaryEdge.Y * reference.Y)) / (primaryMagnitude * refMagnitude));
Related
I'm working on a Android app for receipts preprocessing. My first step would be to find the edges from the receipt in the given image. Then I would like to use the edges to remove the background and perform a perspective correction.
Im trying to do it using OpenCv 4.5.3.
My first try has been to perform HoughLinesP to detect the lines in the image, but the result is not that good.
Mat sampledImage = OpenCV.ImgCodecs.Imgcodecs.Imread(imagePath);
Mat gray = new Mat();
Imgproc.CvtColor(sampledImage, gray, Imgproc.ColorRgb2gray);
//Imgproc.GaussianBlur(gray, gray, new Size(7, 7), 0);
Mat edgeImage = new Mat();
Imgproc.Canny(gray, edgeImage, 50, 200, 3);
Mat lines = new Mat();
int threshold = 80;
Imgproc.HoughLinesP(edgeImage, lines, 1.0, Math.PI / 180, threshold, 30, 10);
//OpenCV.ImgCodecs.Imgcodecs.Imwrite(imagePath, edgeImage);
for (int x = 0; x < lines.Rows(); x++)
{
double[] vec = lines.Get(x, 0);
double x1 = vec[0],
y1 = vec[1],
x2 = vec[2],
y2 = vec[3];
Point start = new Point(x1, y1);
Point end = new Point(x2, y2);
double dx = x1 - x2;
double dy = y1 - y2;
double dist = Math.Sqrt(dx * dx + dy * dy);
if (dist > 50D)
Imgproc.Line(sampledImage, start, end, new Scalar(0, 255, 0), 5);
}
OpenCV.ImgCodecs.Imgcodecs.Imwrite(imagePath, sampledImage);
Original image
So basically the edges of the receipt are not 100% detected. Any idea of how could I improve this edge detection to remove the background and perform perspective correction afterwards?
I have an image on which I choose a random point. The image is rotated by any degree the user wishes. The goal here is to find the new coordinate of the pixel. I have tried it this way with the function RotatePoint where the original position of the pixel was (100, 370) and the image gets rotated by 270 degrees, but the new coordinate is not correct. How would I be able to get the correct new coordinate?
static public void RotatePoint(float angle)
{
var a = angle * System.Math.PI / 180.0;
float cosa = (float)Math.Cos(a), sina = (float)Math.Sin(a);
float x = 100 * cosa - 370 * sina;
float y = 100 * sina + 370 * cosa;
Console.WriteLine(x);
Console.WriteLine(y);
}
private static Bitmap RotateImage(Bitmap bmp, float angle)
{
Bitmap rotatedImage = new Bitmap(bmp.Width, bmp.Height);
rotatedImage.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
using (Graphics g = Graphics.FromImage(rotatedImage))
{
// Set the rotation point to the center in the matrix
g.TranslateTransform(bmp.Width / 2, bmp.Height / 2);
// Rotate
g.RotateTransform(angle);
// Restore rotation point in the matrix
g.TranslateTransform(-bmp.Width / 2, -bmp.Height / 2);
// Draw the image on the bitmap
g.DrawImage(bmp, new Point(0, 0));
}
return rotatedImage;
}
You perform 2 translation transformations on your image that you don't take into account into your coordinate calculations:
// Set the rotation point to the center in the matrix
g.TranslateTransform(bmp.Width / 2, bmp.Height / 2);
and
// Restore rotation point in the matrix
g.TranslateTransform(-bmp.Width / 2, -bmp.Height / 2);
Here is a fix, with both translations taken into account, where xRotationCenter and yRotationCenter should be your bitmap width and height:
public static void RotatePoint(float x = 100, float y = 377, float xRotationCenter, float yRotationCenter, float angleInDegree)
{
var angleInRadiant = (angleInDegree / 180.0) * Math.PI;
var cosa = (float)Math.Cos(angleInRadiant);
var sina = (float)Math.Sin(angleInRadiant);
// First translation
float t1x = x - xRotationCenter;
float t1y = y - yRotationCenter;
// Rotation
float rx = t1x * cosa - t1y * sina;
float ry = t1x * sina + t1y * cosa;
// seconde translation
float x = rx + xRotationCenter;
float y = ry + yRotationCenter;
Console.WriteLine(x);
Console.WriteLine(y);
}
I am having three image of pan card for testing skew of image using emgucv and c#.
1st image which is on top Detected 180 degree working properly.
2nd image which is in middle Detected 90 dgree should detected as 180 degree.
3rd image Detected 180 degree should detected as 90 degree.
One observation I am having that i wanted to share here is when i crop unwanted part of image from up and down side of pan card using paint brush, it gives me expected result using below mention code.
Now i wanted to understand how i can remove the unwanted part using programming.
I have played with contour and roi but I am not able to figure out how to fit the same. I am not able to understand whether emgucv itself selects contour or I have to do something.
Please suggest any suitable code example.
Please check code below for angle detection and please help me. Thanks in advance.
imgInput = new Image<Bgr, byte>(impath);
Image<Gray, Byte> img2 = imgInput.Convert<Gray, Byte>();
Bitmap imgs;
Image<Gray, byte> imgout = imgInput.Convert<Gray, byte>().Not().ThresholdBinary(new Gray(50), new Gray(125));
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
Emgu.CV.Mat hier = new Emgu.CV.Mat();
var blurredImage = imgInput.SmoothGaussian(5, 5, 0 , 0);
CvInvoke.AdaptiveThreshold(imgout, imgout, 255, Emgu.CV.CvEnum.AdaptiveThresholdType.GaussianC, Emgu.CV.CvEnum.ThresholdType.Binary, 5, 45);
CvInvoke.FindContours(imgout, contours, hier, Emgu.CV.CvEnum.RetrType.External, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);
if (contours.Size >= 1)
{
for (int i = 0; i <= contours.Size; i++)
{
Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
RotatedRect box = CvInvoke.MinAreaRect(contours[i]);
PointF[] Vertices = box.GetVertices();
PointF point = box.Center;
PointF edge1 = new PointF(Vertices[1].X - Vertices[0].X, Vertices[1].Y - Vertices[0].Y);
PointF edge2 = new PointF(Vertices[2].X - Vertices[1].X, Vertices[2].Y - Vertices[1].Y);
double r = edge1.X + edge1.Y;
double edge1Magnitude = Math.Sqrt(Math.Pow(edge1.X, 2) + Math.Pow(edge1.Y, 2));
double edge2Magnitude = Math.Sqrt(Math.Pow(edge2.X, 2) + Math.Pow(edge2.Y, 2));
PointF primaryEdge = edge1Magnitude > edge2Magnitude ? edge1 : edge2;
double primaryMagnitude = edge1Magnitude > edge2Magnitude ? edge1Magnitude : edge2Magnitude;
PointF reference = new PointF(1, 0);
double refMagnitude = 1;
double thetaRads = Math.Acos(((primaryEdge.X * reference.X) + (primaryEdge.Y * reference.Y)) / (primaryMagnitude * refMagnitude));
double thetaDeg = thetaRads * 180 / Math.PI;
imgInput = imgInput.Rotate(thetaDeg, new Bgr());
imgout = imgout.Rotate(box.Angle, new Gray());
Bitmap bmp = imgout.Bitmap;
break;
}
}
The Problem
Let us start with the problem before the solution:
Your Code
When you submit code, asking for help, at least make some effort to "clean" it. Help people help you! There's so many lines of code here that do nothing. You declare variables that are never used. Add some comments that let people know what it is that you think your code should do.
Bitmap imgs;
var blurredImage = imgInput.SmoothGaussian(5, 5, 0, 0);
Rectangle rect = CvInvoke.BoundingRectangle(contours[i]);
PointF point = box.Center;
double r = edge1.X + edge1.Y;
// Etc
Adaptive Thresholding
The following line of code produces the following images:
CvInvoke.AdaptiveThreshold(imgout, imgout, 255, Emgu.CV.CvEnum.AdaptiveThresholdType.GaussianC, Emgu.CV.CvEnum.ThresholdType.Binary, 5, 45);
Image 1
Image 2
Image 3
Clearly this is not what you're aiming for since the primary contour, the card edge, is completely lost. As a tip, you can always use the following code to display images at runtime to help you with debugging.
CvInvoke.NamedWindow("Output");
CvInvoke.Imshow("Output", imgout);
CvInvoke.WaitKey();
The Soltuion
Since your in example images the card is primarily a similar Value (in the HSV sense) to the background. I do not think simple gray scale thresholding is the correct approach in this case. I purpose the following:
Algorithm
Use Canny Edge Detection to extract the edges in the image.
Dilate the edges so as the card content combines.
Use Contour Detection to filter for the combined edges with the largest bounding.
Fit this primary contour with a rotated rectangle in order to extract the corner points.
Use the corner points to define a transformation matrix to be applied using WarpAffine.
Warp and crop the image.
The Code
You may wish to experiment with the parameters of the Canny Detection and Dilation.
// Working Images
Image<Bgr, byte> imgInput = new Image<Bgr, byte>("Test1.jpg");
Image<Gray, byte> imgEdges = new Image<Gray, byte>(imgInput.Size);
Image<Gray, byte> imgDilatedEdges = new Image<Gray, byte>(imgInput.Size);
Image<Bgr, byte> imgOutput;
// 1. Edge Detection
CvInvoke.Canny(imgInput, imgEdges, 25, 80);
// 2. Dilation
CvInvoke.Dilate(
imgEdges,
imgDilatedEdges,
CvInvoke.GetStructuringElement(
ElementShape.Rectangle,
new Size(3, 3),
new Point(-1, -1)),
new Point(-1, -1),
5,
BorderType.Default,
new MCvScalar(0));
// 3. Contours Detection
VectorOfVectorOfPoint inputContours = new VectorOfVectorOfPoint();
Mat hierarchy = new Mat();
CvInvoke.FindContours(
imgDilatedEdges,
inputContours,
hierarchy,
RetrType.External,
ChainApproxMethod.ChainApproxSimple);
VectorOfPoint primaryContour = (from contour in inputContours.ToList()
orderby contour.GetArea() descending
select contour).FirstOrDefault();
// 4. Corner Point Extraction
RotatedRect bounding = CvInvoke.MinAreaRect(primaryContour);
PointF topLeft = (from point in bounding.GetVertices()
orderby Math.Sqrt(Math.Pow(point.X, 2) + Math.Pow(point.Y, 2))
select point).FirstOrDefault();
PointF topRight = (from point in bounding.GetVertices()
orderby Math.Sqrt(Math.Pow(imgInput.Width - point.X, 2) + Math.Pow(point.Y, 2))
select point).FirstOrDefault();
PointF botLeft = (from point in bounding.GetVertices()
orderby Math.Sqrt(Math.Pow(point.X, 2) + Math.Pow(imgInput.Height - point.Y, 2))
select point).FirstOrDefault();
PointF botRight = (from point in bounding.GetVertices()
orderby Math.Sqrt(Math.Pow(imgInput.Width - point.X, 2) + Math.Pow(imgInput.Height - point.Y, 2))
select point).FirstOrDefault();
double boundingWidth = Math.Sqrt(Math.Pow(topRight.X - topLeft.X, 2) + Math.Pow(topRight.Y - topLeft.Y, 2));
double boundingHeight = Math.Sqrt(Math.Pow(botLeft.X - topLeft.X, 2) + Math.Pow(botLeft.Y - topLeft.Y, 2));
bool isLandscape = boundingWidth > boundingHeight;
// 5. Define warp crieria as triangles
PointF[] srcTriangle = new PointF[3];
PointF[] dstTriangle = new PointF[3];
Rectangle ROI;
if (isLandscape)
{
srcTriangle[0] = botLeft;
srcTriangle[1] = topLeft;
srcTriangle[2] = topRight;
dstTriangle[0] = new PointF(0, (float)boundingHeight);
dstTriangle[1] = new PointF(0, 0);
dstTriangle[2] = new PointF((float)boundingWidth, 0);
ROI = new Rectangle(0, 0, (int)boundingWidth, (int)boundingHeight);
}
else
{
srcTriangle[0] = topLeft;
srcTriangle[1] = topRight;
srcTriangle[2] = botRight;
dstTriangle[0] = new PointF(0, (float)boundingWidth);
dstTriangle[1] = new PointF(0, 0);
dstTriangle[2] = new PointF((float)boundingHeight, 0);
ROI = new Rectangle(0, 0, (int)boundingHeight, (int)boundingWidth);
}
Mat warpMat = new Mat(2, 3, DepthType.Cv32F, 1);
warpMat = CvInvoke.GetAffineTransform(srcTriangle, dstTriangle);
// 6. Apply the warp and crop
CvInvoke.WarpAffine(imgInput, imgInput, warpMat, imgInput.Size);
imgOutput = imgInput.Copy(ROI);
imgOutput.Save("Output1.bmp");
Two extension methods are used:
static List<VectorOfPoint> ToList(this VectorOfVectorOfPoint vectorOfVectorOfPoint)
{
List<VectorOfPoint> result = new List<VectorOfPoint>();
for (int contour = 0; contour < vectorOfVectorOfPoint.Size; contour++)
{
result.Add(vectorOfVectorOfPoint[contour]);
}
return result;
}
static double GetArea(this VectorOfPoint contour)
{
RotatedRect bounding = CvInvoke.MinAreaRect(contour);
return bounding.Size.Width * bounding.Size.Height;
}
Outputs
Meta Example
I am using following thread to perform angle detection for a rectangle image.
Detect centre and angle of rectangles in an image using Opencv
I am stuck at following piece of code.
cv::Point2f edge1 = cv::Vec2f(rect_points[1].x, rect_points[1].y) - cv::Vec2f(rect_points[0].x, rect_points[0].y);
cv::Point2f edge2 = cv::Vec2f(rect_points[2].x, rect_points[2].y) - cv::Vec2f(rect_points[1].x, rect_points[1].y);
cv::Point2f usedEdge = edge1;
if(cv::norm(edge2) > cv::norm(edge1)) usedEdge = edge2;
cv::Point2f reference = cv::Vec2f(1,0); // horizontal edge
angle = 180.0f/CV_PI * acos((reference.x*usedEdge.x + reference.y*usedEdge.y) / (cv::norm(reference) *cv::norm(usedEdge)));
I am not able to figure out following few lines which i required to convert in emgu csharp.
cv::Point2f edge1 = cv::Vec2f(rect_points[1].x, rect_points[1].y) - cv::Vec2f(rect_points[0].x, rect_points[0].y);
cv::Point2f edge2 = cv::Vec2f(rect_points[2].x, rect_points[2].y) - cv::Vec2f(rect_points[1].x, rect_points[1].y);
angle = 180.0f/CV_PI * acos((reference.x*usedEdge.x + reference.y*usedEdge.y) / (cv::norm(reference) *cv::norm(usedEdge)));
if(cv::norm(edge2) > cv::norm(edge1)) usedEdge = edge2;
cv::Point2f reference = cv::Vec2f(1,0);
Can anyone help me how to resolve the same? Any help or suggestion will be highly appreciated?
The Point2f here are simply points, having float precision properties of X and Y, being used to store 2D vectors of I and J. Their method if declaration is setting the edges to be the vector between two points, i.e. the delta between those two points. In C#, I would write this as:
float deltaX = rect_points[1].X - rect_points[0].X;
float deltaY = rect_points[1].Y - rect_points[0].Y;
PointF edge1 = new PointF(deltaX, deltaY);
OR of course...
PointF edge1 = new PointF(rect_points[1].X - rect_points[0].X, rect_points[1].Y - rect_points[0].Y);
PointF edge2 = new PointF(rect_points[2].X - rect_points[1].X, rect_points[2].Y - rect_points[1].Y);
These PointF are now the two vectors, or edges, that join at rect_points[1]. Next, norm is performed in order to compare the magnitude of the two. This is simply Pythagoras if we perform the same manually:
edge1Magnitude = Math.Sqrt(Math.Pow(edge1.X, 2) + Math.Pow(edge1.Y, 2));
edge2Magnitude = Math.Sqrt(Math.Pow(edge2.X, 2) + Math.Pow(edge2.Y, 2));
The longer of the edges, that with the greatest magnitude, is considered the "primary", or longer edge the rectangle:
PointF primaryEdge = edge1Magnitude > edge2Magnitude ? edge1 : edge2;
double primaryMagnitude = edge1Magnitude > edge2Magnitude ? edge1Magnitude : edge2Magnitude;
Finally, to find the angle between the primaryEdge, and a horizontal vector, reference. This is the acos, of the "Dot Product", of the two, or:
PointF reference = new PointF(1,0);
double refMagnitude = 1;
double thetaRads = Math.Acos(((primaryEdge.X * reference.X) + (primaryEdge.Y * reference.Y)) / (primaryMagnitude * refMagnitude));
double thetaDeg = thetaRads * 180 / Math.PI;
Now, thetaDeg is the angle between edge1 and the horizontal, in degrees.
I'm working on a method to programatically draw equilateral polygon shapes in C#, WPF. But I have been stuck and I can't solve calculating the angles. Where is the problem? How should I correct this? I have given the public int, R(radius) a value of 100.
private Path EquilateralPolygon(int sides)
{
//Centering
Point center = new Point(canvasSize.Width / 2, canvasSize.Height / 2);
PathFigure myPathFigure = new PathFigure();
int alfa = 360 / sides;
int[] angles = new int[6];
for (int i = 0; i < sides; i++)
angles[i] = 360 - alfa * i;
MessageBox.Show(angles.Sum().ToString());
Point A = new Point(center.X, center.Y - R);
myPathFigure.StartPoint = A;
PolyLineSegment myLineSegment = new PolyLineSegment();
for (int i = 1; i < sides; i++)
{
myLineSegment.Points.Add(new Point(center.X + Math.Cos(angles[i]) * R, center.Y + Math.Sin(angles[i]) * R));
}
myLineSegment.Points.Add(A);
PathSegmentCollection myPathSegmentCollection = new PathSegmentCollection();
myPathSegmentCollection.Add(myLineSegment);
myPathFigure.Segments = myPathSegmentCollection;
PathFigureCollection myPathFigureCollection = new PathFigureCollection();
myPathFigureCollection.Add(myPathFigure);
PathGeometry myPathGeometry = new PathGeometry();
myPathGeometry.Figures = myPathFigureCollection;
Path myPath = new Path();
myPath.Stroke = Brushes.Red;
myPath.StrokeThickness = 1;
myPath.Data = myPathGeometry;
return myPath;
}
You've posted a lot of code and were not specific about how it's not working, so there may be more than one issue with your code. However, one big issue is that the Math.Cos (and related trig methods) take the angle in the form of radians, not degrees as you have them.
Parameters
d
Type: System.Double An angle, measured in radians.
You will need to convert them to radians. To convert, multiply by π (available via Math.PI) then divide by 180 degrees.
myLineSegment.Points.Add(
new Point(center.X + Math.Cos(angles[i] * Math.PI / 180.0) * R,
center.Y + Math.Sin(angles[i] * Math.PI / 180) * R));
EDIT: In addition to the radians/degrees issue, I can see you may be experiencing integer truncation, both in the use of your angles array and your calculation of alfa. I would suggest you try changing your use of integers to double so that your code works fine with fractions of a degree.