I am doing image manipulation, I have a class that I've inherited that I can't change because it's referenced too many times to completely refactor. Here is the class as it stands.
I have a Bitmap and I am trying to get a collection of all of the LinkedPixels. For example pixel
x, y would be a key and a pixel in the center would be attached to four pixels, (x+1, y), (x, y + 1), (x-1, y), (y-1, x). Pixels on the sides would have three attached Pixels, and Pixels in the corners only two.
imWrap.Arr is an array with all of the Pixels inside of a Bitmap, this is built as the program spools up so this is not built on the fly it is already available.
Since I cannot change the class itself (I could change it to a struct if necessary) I need to find the fastest way to create the LinkedPix, I am aware that there are many better ways to do this but I am locked in to this "LinkedPix" class, there are over 100 references to it
public class LinkedPix
{
public Point P;
public override string ToString()
{
return P.ToString() + " " + Links.Count;
}
public HashSet<LinkedPix> Links;
public SegObject MemberOfObject { get; set; }
public bool IsMemberOfObject { get => MemberOfObject != null; }
private void Init()
{
MemberOfObject = null;
Links = new HashSet<LinkedPix>();
}
public LinkedPix(int x, int y)
{
Init();
P = new Point(x, y);
}
public LinkedPix(Point p)
{
Init();
P = p;
}
public LinkedPix RotateCopy(double theta_radians)
{
//Assumes that 0,0 is the origin
LinkedPix LP2 = new LinkedPix(new Point(
(int)(P.X * Math.Cos(theta_radians) - P.Y * Math.Sin(theta_radians)),
(int)(P.Y * Math.Cos(theta_radians) + P.X * Math.Sin(theta_radians))));
return LP2;
}
public void LinksRemove(LinkedPix ToRemove, bool TwoWay = false)
{
//Remove from each others
if (TwoWay) ToRemove.Links.Remove(this);
Links.Remove(ToRemove);
}
/// <summary>
/// Removes this from each of its links and removes these links
/// </summary>
internal void LinksCrossRemove()
{
foreach (LinkedPix tPix in Links) tPix.LinksRemove(this, false);
Links = new HashSet<LinkedPix>();
}
}
Here are the three functions I used to populate the information about how LinkedPix are related.
Dictionary<Point, LinkedPix> PointDict = new Dictionary<Point, LinkedPix>();
void EstablishLinks(int xi, int yi, LinkedPix Pixi)
{
if (imWrap.Arr[xi, yi] > threshold)
{
Point pi = new Point(xi, yi);
LinkedPix Pix2 = GetOrAdd(pi);
Pixi.Links.Add(Pix2);
Pix2.Links.Add(Pixi);
}
}
LinkedPix Pix; Point p;
int height = imWrap.Height;
for (int y = 0; y < height - 1; y++)
{
if (Slice && y % Slice_ModulusFactor < 1) continue; //This is for Axon Degeneration to break up the axons
for (int x = 0; x < height - 1; x++)
{
if (Slice && x % Slice_ModulusFactor < 1) continue;
if (imWrap.Arr[x, y] > threshold)
{
Pixels++;
if (CreateLinks)
{
p = new Point(x, y);
Pix = GetOrAdd(p);
if (y + 1 < height) EstablishLinks(x, y + 1, Pix);
if (x + 1 < height) EstablishLinks(x + 1, y, Pix);
}
}
}
}
}
public LinkedPix GetOrAdd(Point p)
{
if (PointDict.ContainsKey(p))
{
return PointDict[p];
}
else
{
LinkedPix LP = new LinkedPix(p);
PointDict.Add(p, LP);
return LP;
}
}
}
imWrap.Arr[x, y] is an array that has already been pre-populated with the contents of a bitmap.
things I have tried
Switching to ConcurrentDictionaries and running in Parallel. The inferior Hashing Algorithm of ConcurrentDictionary compared to HashSet means running this in Parallel actually takes more time, at least when I tried it.
switching to a different collection with faster adds such as a Que. This gains me some speed but because of the way LinkedPix is used, I ultimately have to convert to a HashSet anyway which costs time in the end so this is not ultimately a win.
Various types of Asynchronicity, I either gain very little or nothing at all.
I'm definitely stuck and could use some outside of the box thinking. I definitely need to speed this up and any help would be greatly appreciated.
These are some timings
GetOrAdd: 4.190254371881144 ticks
LinksAdd: 1.7282694093881512 ticks
LinksAddTwo: 1.6570632131524925 ticks
PointCreateTime: 0.20380008184398646 ticks
ArrayCheck: 0.3061780882729328 ticks
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I want to visualize Activities and their relations with a network model like this
I have the table and want to draw the model. Which method do you recommend for doing this issue?
Edit:
When I add a Node Data(a DataTable contains more than 100 rows with Activities and Predecessors Columns) to this Program and using as Node resources , got the
"Index was out of range. Must be non-negative and less than the size of the collection"
(according to #TaW's answer),
in the layoutNodeY() part
line:
nodes.Values.ElementAt(i)[j].VY = 1f * j - c / 2
NodeChart NC = new NodeChart();
private void Form1_Load(object sender, EventArgs e)
{
for (int i = 0; i < sortedtable.Rows.Count - 1; i++)
{ List<string> pred = sortedtable.Rows[i][2].ToString().Split(',').ToList();
for (int j = 0; j < sortedtable.Rows.Count - 1; j++)
{
foreach (var item in pred)
{
if (item == sortedtable.Rows[j][0].ToString() + "." + sortedtable.Rows[j][1].ToString())
{
NC.theNodes.Add(new NetNode(sortedtable.Rows[i][0].ToString() + "." + sortedtable.Rows[i][1].ToString(), item));
}
}
}
}
}
Part of Datatable's Screenshot:
I recommend putting as much of the complexity as possible into data structures.
I make much use of List<T> and at one time of a Dictionary<float, List<NetNode>>
Note that this post is a good deal longer than SO answers usually are; I hope it is instructive..
Let's start with a node class
It should know its own name and other text data you want to print
It also needs to know the previous nodes and to allow tranversing in both direction..
..a list of next nodes
It will also know its virtual position in the layout; this will have to be scaled when drawing to fit the given area..
And it should know how to draw itself and the connections to its neighbours.
These nodes can then be collected and managed in a second class that can analyse them to fill in the lists and the positions.
Here is the result, using your data plus one extra node:
Now let's have a closer lok at the code.
The node class first:
class NetNode
{
public string Text { get; set; }
public List<NetNode> prevNodes { get; set; }
public List<NetNode> nextNodes { get; set; }
public float VX { get; set; }
public float VY { get; set; }
public string prevNodeNames;
public NetNode(string text, string prevNodeNames)
{
this.prevNodeNames = prevNodeNames;
prevNodes = new List<NetNode>();
nextNodes = new List<NetNode>();
Text = text;
VX = -1;
VY = -1;
}
...
}
As you can see it make use of List<T> to hold lists of itself. Its constructor takes a string that is expected to contain a list of node names; it will be parsed later by the NodeChart object, because for this we need the full set of nodes.
The drawing code is simple and only meant as a proof of concept. For nicer curves you can easily improve on it using DrawCurves with either a few extra points or construct the needed bezier control points.
The arrowhead is also a cheap one; unfortunately the built-in endcap is not very good. To improve you would create a custom one, maybe with a graphicspath..
Here we go:
public void draw(Graphics g, float scale, float size)
{
RectangleF r = new RectangleF(VX * scale, VY * scale, size, size);
g.FillEllipse(Brushes.Beige, r);
g.DrawEllipse(Pens.Black, r);
using (StringFormat fmt = new StringFormat()
{ Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center})
using (Font f = new Font("Consolas", 20f))
g.DrawString(Text, f, Brushes.Blue, r, fmt);
foreach(var nn in nextNodes)
{
using (Pen pen = new Pen(Color.Green, 1f)
{ EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor })
g.DrawLine(pen, getConnector(this, scale, false, size),
getConnector(nn, scale, true, size));
}
}
PointF getConnector(NetNode n, float scale, bool left, float size)
{
RectangleF r = new RectangleF(n.VX * scale, n.VY * scale, size, size);
float x = left ? r.Left : r.Right;
float y = r.Top + r.Height / 2;
return new PointF(x, y);
}
You will want to expand the node class to include more text, colors, fonts etc..
The draw method above is one of the longest pieces of code. Let's look at the NodeChart class now.
It holds ..:
a list of nodes and..
a list of start nodes. There really should only be one, though; so you may want to throw an 'start node not unique' exception..
a list of methods to analyse the node data.
I have left out anything related to fitting the graphics into a given area as well as any error checking..
class NodeChart
{
public List<NetNode> theNodes { get; set; }
public List<NetNode> startNodes { get; set; }
public NodeChart()
{
theNodes = new List<NetNode>();
startNodes = new List<NetNode>();
}
..
}
The first method parses the strings with the names of the previous nodes:
public void fillPrevNodes()
{
foreach (var n in theNodes)
{
var pn = n.prevNodeNames.Split(',');
foreach (var p in pn)
{
var hit = theNodes.Where(x => x.Text == p);
if (hit.Count() == 1) n.prevNodes.Add(hit.First());
else if (hit.Count() == 0) startNodes.Add(n);
else Console.WriteLine(n.Text + ": prevNodeName '" + p +
"' not found or not unique!" );
}
}
}
The next method fills in the nextNodes lists:
public void fillNextNodes()
{
foreach (var n in theNodes)
{
foreach (var pn in n.prevNodes) pn.nextNodes.Add(n);
}
}
Now we have the data and need to lay out the nodes. The horizontal layout is simple but, as usual with branched data, recursion is needed:
public void layoutNodeX()
{
foreach (NetNode n in startNodes) layoutNodeX(n, n.VX + 1);
}
public void layoutNodeX(NetNode n, float vx)
{
n.VX = vx;
foreach (NetNode nn in n.nextNodes) layoutNodeX(nn, vx + 1);
}
The vertical layout is a bit more complicated. It counts the nodes for each x-position and spreads them out equally. A Dictionary takes on most of the work: First we fill it in, then we loop over it to set the values. Finally we push the nodes up as much as is needed to center them..:
public void layoutNodeY()
{
NetNode n1 = startNodes.First();
n1.VY = 0;
Dictionary<float, List<NetNode>> nodes =
new Dictionary<float, List<NetNode>>();
foreach (var n in theNodes)
{
if (nodes.Keys.Contains(n.VX)) nodes[n.VX].Add(n);
else nodes.Add(n.VX, new List<NetNode>() { n });
}
for (int i = 0; i < nodes.Count; i++)
{
int c = nodes[i].Count;
for (int j = 0; j < c; j++)
{
nodes.Values.ElementAt(i)[j].VY = 1f * j - c / 2;
}
}
float min = theNodes.Select(x => x.VY).Min();
foreach (var n in theNodes) n.VY -= min;
}
To wrap it up here is how I call it from a Form with a PictureBox:
NodeChart NC = new NodeChart();
private void Form1_Load(object sender, EventArgs e)
{
NC.theNodes.Add(new NetNode("A",""));
NC.theNodes.Add(new NetNode("B","A"));
NC.theNodes.Add(new NetNode("C","B"));
NC.theNodes.Add(new NetNode("D","B"));
NC.theNodes.Add(new NetNode("T","B"));
NC.theNodes.Add(new NetNode("E","C"));
NC.theNodes.Add(new NetNode("F","D,T"));
NC.theNodes.Add(new NetNode("G","E,F"));
NC.fillPrevNodes();
NC.fillNextNodes();
NC.layoutNodeX();
NC.layoutNodeY();
pictureBox1.Invalidate();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (NC.theNodes.Count <= 0) return;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
foreach (var n in NC.theNodes) n.draw(e.Graphics, 100, 33);
}
In addition to the things already mentioned you may want to add a y-scaling or 'leading' parameter to spread the nodes vertically to make more room for extra text..
Update:
Here is the result of the data you provided:
I have made a few changes:
I have changed the 2nd "35.2" to "35.3". You probably meant "25.2" but that brings up more data errors; you should take care of them all! Do check the output pane!!
I have changed the scales to n.draw(e.Graphics, 50, 30); in the Paint event
And finally I changed the font size to Font("Consolas", 10f) in the NetNode.draw.
You must also make sure the pbox is large enough and/or docked/anchored to allow resizing with the form.
edit: It works now, I moved the getting X and Y into a seperate function, and it seemed to have fixed it (I'm not sure exactly what was wrong, sorry)
I've been making a Tile Editor using WPF and C#, and I seem to have come across a rather odd problem.
When I first run the fill algorithm on a blank canvas, it fills up all tiles except one (usually around (2,7)). Depending on where I start, it jumps around slightly in that area.
What really confuses me is if I flood fill that same area, it will fix the missing tile.
I tried the recursive flood fill as well as the one using a Queue. Here's the code for the Recursion one:
private void FloodFill(Tile node, int targetID, int replaceID) {
if (targetID == replaceID) return;
if (node.TileID != targetID) return;
node.TileID = replaceID;
SetImageAtCoord(node.X, node.Y);
if (node.Y + 1 != Map.Height) FloodFill(Map.TileInformation[node.X, node.Y + 1], targetID, replaceID);
if (node.Y - 1 != -1) FloodFill(Map.TileInformation[node.X, node.Y - 1], targetID, replaceID);
if (node.X - 1 != -1) FloodFill(Map.TileInformation[node.X - 1, node.Y], targetID, replaceID);
if (node.X + 1 != Map.Width) FloodFill(Map.TileInformation[node.X + 1, node.Y], targetID, replaceID);
}
Now since I also tried a different method and got the same problem, I thought it might be in the SetImageAtCoord():
private void SetImageAtCoord(int x, int y) {
IEnumerable<Rectangle> rectangles = MapViewer.Children.OfType<Rectangle>();
Map.TileInformation[x, y].TileID = CurrentTile;
foreach (Rectangle rect in rectangles) {
int X = (int)Canvas.GetLeft(rect) / 32;
int Y = (int)Canvas.GetTop(rect) / 32;
if (X == x && Y == y) {
rect.Fill = CurrentImage;
}
}
}
My only guess is that it either misses it on the pass (yet somehow make it every other time), or it has something to do with the placement of the rectangle on the Canvas.
edit:
Here is the code for selecting what CurrentImage (and CurrentTile are):
private void Sprite_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
Rectangle curr = (Rectangle)e.OriginalSource;
CurrentImage = (ImageBrush)curr.Fill;
int x = (int)Canvas.GetLeft(curr) / 32;
int y = (int)Canvas.GetTop(curr) / 32;
CurrentTile = y * Map.SpriteSheet.GetLength(0) + x;
}
and the code that calls FloodFill
if (CurrentContol == MapEditControl.Fill) {
Rectangle ClickedRectangle = (Rectangle)e.OriginalSource;
ClickedRectangle.Fill = CurrentImage;
int x = (int)Canvas.GetLeft(ClickedRectangle) / 32;
int y = (int)Canvas.GetTop(ClickedRectangle) / 32;
int oldTile = Map.TileInformation[x, y].TileID;
FloodFill(Map.TileInformation[x, y], oldTile, CurrentTile);
}
Map.TileInformation is actually only really used to store the ID for exporting to JSON, and the actual rectangles aren't bound to that in anyway (probably should have)
I've been trying to implement a function that lets me draw the tiles inside a tiled file (.tmx). I've looked around and found a kind of working piece of code but it stretches out the tiles.
This is the code I found and edited a bit:
private void DrawLayer(int index, SpriteBatch batch) {
for (var i = 0; i < Map.Layers[index].Tiles.Count; i++) {
//Get the identification of the tile
int gid = Map.Layers[index].Tiles[i].Gid;
// Empty tile, do nothing
if (gid == 0) { }
else {
int tileFrame = gid - 1 ;
int column = tileFrame % (tileset.Width / tileWidth);
int row = tileFrame / (tileset.Height / tileHeight);
float x = (i % Map.Width) * Map.TileWidth;
float y = (float)Math.Floor(i / (double)Map.Width) * Map.TileHeight;
//Put all the data together in a new rectangle
Rectangle tilesetRec = new Rectangle(tileWidth * column, tileHeight * row, tileWidth, tileHeight);
//Draw the tile that is within the tilesetRec
batch.Draw(tileset, new Rectangle((int)x, (int)y, tileWidth, tileHeight), tilesetRec, Color.White);
}
}
}
The MonoGame.Extended library has support for loading and rendering Tiled (.tmx) maps. It's open source so you can check out how it works if you want.
The layer rendering code supports different map types (orthogonal, isometric), different rendering order (right down, right up, left down, left up) and multiple tilesets so it's not boiled down into a single method like yours.
If you where to extract the relevant bits of code you might end up with something like this:
for (var y = 0; y < layerHeight; y++)
{
for (var x = 0; x < layerWidth; x++)
{
var region = tile.Id == 0 ? null : _regions[tile.Id];
if (region != null)
{
var tx = tile.X * _map.TileWidth;
var ty = tile.Y * _map.TileHeight;
var sourceRectangle = region.Bounds;
var destinationRectangle = new Rectangle(tx, ty, region.Width, region.Height);
_spriteBatch.Draw(region.Texture, destinationRectangle, sourceRectangle, Color.White);
}
}
}
Of course, there's still a few missing bits, like the dictionary of texture regions created when loading the tileset.
_regions = new Dictionary<int, TextureRegion2D>();
for (var y = Margin; y < texture.Height - Margin; y += TileHeight + Spacing)
{
for (var x = Margin; x < texture.Width - Margin; x += TileWidth + Spacing)
{
_regions.Add(id, new TextureRegion2D(Texture, x, y, TileWidth, TileHeight));
id++;
}
}
And the definition of what a texture region actually is.
public class TextureRegion2D
{
public Texture2D Texture { get; protected set; }
public int X { get; private set; }
public int Y { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
public Rectangle Bounds { get { return new Rectangle(X, Y, Width, Height); } }
}
Keep in mind that I've mostly copy and pasted code out of MonoGame.Extended. It won't work exactly as it's written here but I think I've provided enough detail to figure out what all the other variables do if you want to write your own Tiled rendering code.
Given the image below, what algorithm might I use to detect whether regions one and two (identified by color) have a border?
http://img823.imageshack.us/img823/4477/borders.png
If there's a C# example out there, that would be awesome, but I'm really just looking for any example code.
Edit: Using Jaro's advice, I came up with the following...
public class Shape
{
private const int MAX_BORDER_DISTANCE = 15;
public List<Point> Pixels { get; set; }
public Shape()
{
Pixels = new List<Point>();
}
public bool SharesBorder(Shape other)
{
var shape1 = this;
var shape2 = other;
foreach (var pixel1 in shape1.Pixels)
{
foreach (var pixel2 in shape2.Pixels)
{
var xDistance = Math.Abs(pixel1.X - pixel2.X);
var yDistance = Math.Abs(pixel1.Y - pixel2.Y);
if (xDistance > 1 && yDistance > 1)
{
if (xDistance * yDistance < MAX_BORDER_DISTANCE)
return true;
}
else
{
if (xDistance < Math.Sqrt(MAX_BORDER_DISTANCE) &&
yDistance < Math.Sqrt(MAX_BORDER_DISTANCE))
return true;
}
}
}
return false;
}
// ...
}
Clicking on two shapes that do share a border returns fairly quickly, but very distance shapes or shapes with a large number of pixels take 3+ seconds at times. What options do I have for optimizing this?
2 regions having border means that within a certain small area there should be 3 colors present: red, black and green.
So a very ineffective solution presents itself:
using Color pixelColor = myBitmap.GetPixel(x, y); you could scan an area for those 3 colors. The area must be larger than the width of the border of course.
There is of course plenty room for optimizations (like going in 50 pixels steps and decreasing the precision continually).
Since black is the least used color, you would search around black areas first.
This should explain what I have written in various comments in this topic:
namespace Phobos.Graphics
{
public class BorderDetector
{
private Color region1Color = Color.FromArgb(222, 22, 46);
private Color region2Color = Color.FromArgb(11, 189, 63);
private Color borderColor = Color.FromArgb(11, 189, 63);
private List<Point> region1Points = new List<Point>();
private List<Point> region2Points = new List<Point>();
private List<Point> borderPoints = new List<Point>();
private Bitmap b;
private const int precision = 10;
private const int distanceTreshold = 25;
public long Miliseconds1 { get; set; }
public long Miliseconds2 { get; set; }
public BorderDetector(Bitmap b)
{
if (b == null) throw new ArgumentNullException("b");
this.b = b;
}
private void ScanBitmap()
{
Color c;
for (int x = precision; x < this.b.Width; x += BorderDetector.precision)
{
for (int y = precision; y < this.b.Height; y += BorderDetector.precision)
{
c = this.b.GetPixel(x, y);
if (c == region1Color) region1Points.Add(new Point(x, y));
else if (c == region2Color) region2Points.Add(new Point(x, y));
else if (c == borderColor) borderPoints.Add(new Point(x, y));
}
}
}
/// <summary>Returns a distance of two points (inaccurate but very fast).</summary>
private int GetDistance(Point p1, Point p2)
{
return Math.Abs(p1.X - p2.X) + Math.Abs(p1.Y - p2.Y);
}
/// <summary>Finds the closests 2 points among the points in the 2 sets.</summary>
private int FindClosestPoints(List<Point> r1Points, List<Point> r2Points, out Point foundR1, out Point foundR2)
{
int minDistance = Int32.MaxValue;
int distance = 0;
foundR1 = Point.Empty;
foundR2 = Point.Empty;
foreach (Point r1 in r1Points)
foreach (Point r2 in r2Points)
{
distance = this.GetDistance(r1, r2);
if (distance < minDistance)
{
foundR1 = r1;
foundR2 = r2;
minDistance = distance;
}
}
return minDistance;
}
public bool FindBorder()
{
Point r1;
Point r2;
Stopwatch watch = new Stopwatch();
watch.Start();
this.ScanBitmap();
watch.Stop();
this.Miliseconds1 = watch.ElapsedMilliseconds;
watch.Start();
int distance = this.FindClosestPoints(this.region1Points, this.region2Points, out r1, out r2);
watch.Stop();
this.Miliseconds2 = watch.ElapsedMilliseconds;
this.b.SetPixel(r1.X, r1.Y, Color.Green);
this.b.SetPixel(r2.X, r2.Y, Color.Red);
return (distance <= BorderDetector.distanceTreshold);
}
}
}
It is very simple. Searching this way only takes about 2 + 4 ms (scanning and finding the closest points).
You could also do the search recursively: first with precision = 1000, then precision = 100 and finally precision = 10 for large images.
FindClosestPoints will practically give you an estimated rectangual area where the border should be situated (usually borders are like that).
Then you could use the vector approach I have described in other comments.
I read your question as asking whether the two points exist in different regions. Is this correct? If so, I would probably use a variation of Flood Fill. It's not super difficult to implement (don't implement it recursively, you will almost certainly run out of stack space) and it will be able to look at complex situations like a U-shaped region that has a border between two points, but are not actually different regions. Basically run flood fill, and return true when your coordinate matches the target coordinate (or perhaps when it's close enough for your satisfaction, depending on your use case)
[Edit] Here is an example of flood fill that I wrote for a project of mine. The project is CPAL-licensed, but the implementation is pretty specific to what I use it for anyway, so don't worry about copying parts of it. And it doesn't use recursion, so it should be able to scale to pixel data.
[Edit2] I misunderstood the task. I don't have any example code that does exactly what you're looking for, but I can say that comparing pixel-per-pixel the entire two regions is not something you want to do. You can reduce the complexity by partitioning each region into a larger grid (maybe 25x25 pixels), and comparing those sectors first, and if any of those are close enough, do a pixel-per-pixel comparison just within those two sectors.
[Edit2.5] [Quadtree]3 might be able to help you too. I don't have a lot of experience with it, but I know it's popular in 2D collision detection, which is similar to what you're doing here. Might be worth researching.
I am working in OMR project and we are using C#. When we come to scan the answer sheets, the images are skewed. How can we deskew them?
VB.Net Code for this is available here, however since you asked for C# here is a C# translation of their Deskew class (note: Binarize (strictly not necessary, but works much better) and Rotate are exercises left to the user).
public class Deskew
{
// Representation of a line in the image.
private class HougLine
{
// Count of points in the line.
public int Count;
// Index in Matrix.
public int Index;
// The line is represented as all x,y that solve y*cos(alpha)-x*sin(alpha)=d
public double Alpha;
}
// The Bitmap
Bitmap _internalBmp;
// The range of angles to search for lines
const double ALPHA_START = -20;
const double ALPHA_STEP = 0.2;
const int STEPS = 40 * 5;
const double STEP = 1;
// Precalculation of sin and cos.
double[] _sinA;
double[] _cosA;
// Range of d
double _min;
int _count;
// Count of points that fit in a line.
int[] _hMatrix;
public Bitmap DeskewImage(Bitmap image, int type, int binarizeThreshold)
{
Size oldSize = image.Size;
_internalBmp = BitmapFunctions.Resize(image, new Size(1000, 1000), true, image.PixelFormat);
Binarize(_internalBmp, binarizeThreshold);
return Rotate(image, GetSkewAngle());
}
// Calculate the skew angle of the image cBmp.
private double GetSkewAngle()
{
// Hough Transformation
Calc();
// Top 20 of the detected lines in the image.
HougLine[] hl = GetTop(20);
// Average angle of the lines
double sum = 0;
int count = 0;
for (int i = 0; i <= 19; i++)
{
sum += hl[i].Alpha;
count += 1;
}
return sum / count;
}
// Calculate the Count lines in the image with most points.
private HougLine[] GetTop(int count)
{
HougLine[] hl = new HougLine[count];
for (int i = 0; i <= count - 1; i++)
{
hl[i] = new HougLine();
}
for (int i = 0; i <= _hMatrix.Length - 1; i++)
{
if (_hMatrix[i] > hl[count - 1].Count)
{
hl[count - 1].Count = _hMatrix[i];
hl[count - 1].Index = i;
int j = count - 1;
while (j > 0 && hl[j].Count > hl[j - 1].Count)
{
HougLine tmp = hl[j];
hl[j] = hl[j - 1];
hl[j - 1] = tmp;
j -= 1;
}
}
}
for (int i = 0; i <= count - 1; i++)
{
int dIndex = hl[i].Index / STEPS;
int alphaIndex = hl[i].Index - dIndex * STEPS;
hl[i].Alpha = GetAlpha(alphaIndex);
//hl[i].D = dIndex + _min;
}
return hl;
}
// Hough Transforamtion:
private void Calc()
{
int hMin = _internalBmp.Height / 4;
int hMax = _internalBmp.Height * 3 / 4;
Init();
for (int y = hMin; y <= hMax; y++)
{
for (int x = 1; x <= _internalBmp.Width - 2; x++)
{
// Only lower edges are considered.
if (IsBlack(x, y))
{
if (!IsBlack(x, y + 1))
{
Calc(x, y);
}
}
}
}
}
// Calculate all lines through the point (x,y).
private void Calc(int x, int y)
{
int alpha;
for (alpha = 0; alpha <= STEPS - 1; alpha++)
{
double d = y * _cosA[alpha] - x * _sinA[alpha];
int calculatedIndex = (int)CalcDIndex(d);
int index = calculatedIndex * STEPS + alpha;
try
{
_hMatrix[index] += 1;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
}
private double CalcDIndex(double d)
{
return Convert.ToInt32(d - _min);
}
private bool IsBlack(int x, int y)
{
Color c = _internalBmp.GetPixel(x, y);
double luminance = (c.R * 0.299) + (c.G * 0.587) + (c.B * 0.114);
return luminance < 140;
}
private void Init()
{
// Precalculation of sin and cos.
_cosA = new double[STEPS];
_sinA = new double[STEPS];
for (int i = 0; i < STEPS; i++)
{
double angle = GetAlpha(i) * Math.PI / 180.0;
_sinA[i] = Math.Sin(angle);
_cosA[i] = Math.Cos(angle);
}
// Range of d:
_min = -_internalBmp.Width;
_count = (int)(2 * (_internalBmp.Width + _internalBmp.Height) / STEP);
_hMatrix = new int[_count * STEPS];
}
private static double GetAlpha(int index)
{
return ALPHA_START + index * ALPHA_STEP;
}
}
Scanned document are always skewed for an average [-10;+10] degrees angle.
It's easy to deskew them using the Hough transform, like Lou Franco said. This transform detects lines on your image for several angles. You just have to select the corresponding one to your document horizontal lines, then rotate it.
try to isolate the pixel corresponding to your document horizontal lines (for instance, black pixels that have a white pixel at their bottom).
Run Hough transform. Do not forget to use 'unsafe' mode in C# to fasten the process of your whole image by using a pointor.
Rotate your document in the opposite angle found.
Works like a charm on binary documents (easily extendable to grey level ones)
Disclaimer: I work at Atalasoft, DotImage Document Imaging can do this with a couple of lines of code.
Deskew is a term of art that describes what you are trying to do. As Ben Voigt said, it's technically rotation, not skew -- however, you will find algorithms under automatic deskew if you search.
The normal way to do this is to do a hough transform to look for the prevalent lines in the image. With normal documents, many of them will be orthogonal to the sides of the paper.
Are you sure it's "skew" rather than "rotation" (rotation preserves angles, skew doesn't).
Use some sort of registration mark (in at least two places) which you can recognize even when rotated.
Find the coordinates of these marks and calculate the rotation angle.
Apply a rotation transformation matrix to the image.