Finding receipt vertices using HoughLinesP - c#

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?

Related

Finding the new coordinate of a pixel in an image rotated by center

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);
}

Getting NaN for image angle in emgucv with c#

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));

Zooming in on quadratic curve line in c#

I am relatively new to c# and i am trying to draw the quadratic curve with an X and Y graph to scale with. The i drew curve although appears at the upper left corner of the screen which is very small and barely noticeable. Is there possible way to enlarge my curve line and align it to the middle so it can be shown properly?
protected override void OnPaint(PaintEventArgs e)
{
float a = 1, b = -3, c = -4;
double x1, x2, x3, y1, y2, y3, delta;
delta = (b * b) - (4 * a * c);
x1 = ((b * (-1)) + Math.Sqrt(delta)) / (2 * a);
y1 = a * (x1 * x1) + b * (x1) + c;
x2 = x1 + 1;
y2 = a * (x2 * x2) + b * (x2) + c;
x3 = x1 - 3;
y3 = a * (x3 * x3) + b * (x3) + c;
int cx1 = Convert.ToInt32(x1);
int cx2 = Convert.ToInt32(x2);
int cx3 = Convert.ToInt32(x3);
int cy1 = Convert.ToInt32(y1);
int cy2 = Convert.ToInt32(y2);
int cy3 = Convert.ToInt32(y3);
Graphics g = e.Graphics;
Pen aPen = new Pen(Color.Blue, 1);
Point point1 = new Point(cx1, cy1);
Point point2 = new Point(cx2, cy2);
Point point3 = new Point(cx3, cy3);
Point[] Points = { point1, point2, point3 };
g.DrawCurve(aPen, Points);
Yes it is possible and even rather simple to both move (Translate) and enlarge (Scale) the Graphics results by using Graphics.TranslateTransform and Matrix and Graphics.MultiplyTransform:
using System.Drawing.Drawing2D;
//..
int deltaX = 100;
int deltaY = 100;
g.TranslateTransform(deltaX, deltaY);
float factor = 2.5f;
Matrix m = new Matrix();
m.Scale(factor, factor);
g.MultiplyTransform(m);
Note that the scaling works like a lens and will enlarge the pixels. So you may want to scale down the Pen.Width when you scale up the Graphics..
Using one before..
g.DrawEllipse(Pens.Blue, 11, 11, 55, 55);
..and two after the transformations..
g.DrawEllipse(Pens.Red, 11, 11, 55, 55);
using (Pen pen = new Pen(Color.Green, 1/factor))
g.DrawEllipse(pen, 11, 11, 44, 44);
..these calls result in this image:
(I have changed the green circle's radius to avoid complete overlaying..)
It will be up to you to find the desired numbers for the moving and scaling; this will probably involve finding the minimum and maximum values for points involved..
I would suggest that you look into Microsoft Chart controls, it has a lots of interesting features regarding how to do this kind of curves with the ability to parameterize them.
A link to a more recent version of it: here

C# WPF Draw equilateral polygons programatically

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.

Transcribing a polygon on a circle

i am currently try to inscribe diagonals of a decagon inside a circle
like this
in c# my approach would be creating a circle
e.Graphics.DrawEllipse(myPen, 0, 0, 100, 100);
and draw lines inside using
e.Graphics.DrawLine(myPen, 20, 5, 50, 50);
after that i would draw a decagon polygon.
currently im stuck at how to divide the circle into 10 parts/ finding the correct coordiantes of the points on the circumference of the circles because im not good in math,
i want to know how would i know the next point in a circumference of the circle the size of my circle is indicated above.
and also i want also to ask a better approach for my problem.
Thank you :)
Just for grits and shins, here's a generic implementation that will inscribe an X-sided polygon into the Rectangle you pass it. Note that in this approach I'm not actually calculating any absolute points. Instead, I am translating the origin, rotating the surface, and drawing the lines only with respect to the origin using a fixed length and an angle. This is repeated in a loop to achieve the end result below, and is very similar to commanding the Turtle in Logo:
public partial class Form1 : Form
{
PictureBox pb = new PictureBox();
NumericUpDown nud = new NumericUpDown();
public Form1()
{
InitializeComponent();
this.Text = "Inscribed Polygon Demo";
TableLayoutPanel tlp = new TableLayoutPanel();
tlp.RowCount = 2;
tlp.RowStyles.Clear();
tlp.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tlp.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
tlp.ColumnCount = 2;
tlp.ColumnStyles.Clear();
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
tlp.Dock = DockStyle.Fill;
this.Controls.Add(tlp);
Label lbl = new Label();
lbl.Text = "Number of Sides:";
lbl.TextAlign = ContentAlignment.MiddleRight;
tlp.Controls.Add(lbl, 0, 0);
nud.Minimum = 3;
nud.Maximum = 20;
nud.AutoSize = true;
nud.ValueChanged += new EventHandler(nud_ValueChanged);
tlp.Controls.Add(nud, 1, 0);
pb.Dock = DockStyle.Fill;
pb.Paint += new PaintEventHandler(pb_Paint);
pb.SizeChanged += new EventHandler(pb_SizeChanged);
tlp.SetColumnSpan(pb, 2);
tlp.Controls.Add(pb, 0, 1);
}
void nud_ValueChanged(object sender, EventArgs e)
{
pb.Refresh();
}
void pb_SizeChanged(object sender, EventArgs e)
{
pb.Refresh();
}
void pb_Paint(object sender, PaintEventArgs e)
{
// make circle centered and 90% of PictureBox size:
int Radius = (int)((double)Math.Min(pb.ClientRectangle.Width, pb.ClientRectangle.Height) / (double)2.0 * (double).9);
Point Center = new Point((int)((double)pb.ClientRectangle.Width / (double)2.0), (int)((double)pb.ClientRectangle.Height / (double)2.0));
Rectangle rc = new Rectangle(Center, new Size(1, 1));
rc.Inflate(Radius, Radius);
InscribePolygon(e.Graphics, rc, (int)nud.Value);
}
private void InscribePolygon(Graphics G, Rectangle rc, int numSides)
{
if (numSides < 3)
throw new Exception("Number of sides must be greater than or equal to 3!");
float Radius = (float)((double)Math.Min(rc.Width, rc.Height) / 2.0);
PointF Center = new PointF((float)(rc.Location.X + rc.Width / 2.0), (float)(rc.Location.Y + rc.Height / 2.0));
RectangleF rcF = new RectangleF(Center, new SizeF(1, 1));
rcF.Inflate(Radius, Radius);
G.DrawEllipse(Pens.Black, rcF);
float Sides = (float)numSides;
float ExteriorAngle = (float)360 / Sides;
float InteriorAngle = (Sides - (float)2) / Sides * (float)180;
float SideLength = (float)2 * Radius * (float)Math.Sin(Math.PI / (double)Sides);
for (int i = 1; i <= Sides; i++)
{
G.ResetTransform();
G.TranslateTransform(Center.X, Center.Y);
G.RotateTransform((i - 1) * ExteriorAngle);
G.DrawLine(Pens.Black, new PointF(0, 0), new PointF(0, -Radius));
G.TranslateTransform(0, -Radius);
G.RotateTransform(180 - InteriorAngle / 2);
G.DrawLine(Pens.Black, new PointF(0, 0), new PointF(0, -SideLength));
}
}
}
I got the formula for the length of the side here at Regular Polygon Calculator.
One way of dealing with this is using trigonometric functions sin and cos. Pass them the desired angle, in radians, in a loop (you need a multiple of 2*π/10, i.e. a = i*π/5 for i between 0 and 9, inclusive). R*sin(a) will give you the vertical offset from the origin; R*cos(a) will give you the horizontal offset.
Note that sin and cos are in the range from -1 to 1, so you will see both positive and negative results. You will need to add an offset for the center of your circle to make the points appear at the right spots.
Once you've generated a list of points, connect point i to point i+1. When you reach the ninth point, connect it to the initial point to complete the polygon.
I don't test it, but i think it is ok.
#define DegreeToRadian(d) d * (Pi / 180)
float r = 1; // radius
float cX = 0; // centerX
float cY = 0; // centerY
int numSegment = 10;
float angleOffset = 360.0 / numSegment;
float currentAngle = 0;
for (int i = 0; i < numSegment; i++)
{
float startAngle = DegreeToRadian(currentAngle);
float endAngle = DegreeToRadian(fmod(currentAngle + angleOffset, 360));
float x1 = r * cos(startAngle) + cX;
float y1 = r * sin(startAngle) + cY;
float x2 = r * cos(endAngle) + cX;
float y2 = r * sin(endAngle) + cY;
currentAngle += angleOffset;
// [cX, cY][x1, y1][x2, y2]
}
(fmod is c++ function equals to floatNumber % floatNumber)

Categories