I write the program in the console, at startup, first play the music, and then the animation. Help make animation and music at the same time. Rummaged through the Internet and found nothing
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Imaging;
using System.Media;
using System.Threading;
namespace Animation_and_music
{
class animation
{
public void music()
{
SoundPlayer player = new SoundPlayer("C:\\audio.wav");
player.PlaySync();
}
public void gif()
{
Console.SetWindowSize(102, 49);
Image image = Image.FromFile(#"1.gif");
FrameDimension dimension = new FrameDimension(image.FrameDimensionsList[0]);
int frameCount = image.GetFrameCount(dimension);
StringBuilder sb;
int left = Console.WindowLeft, top = Console.WindowTop;
char[] chars = { '#', '#', '#', '%', '=', '+', '*', ':', '-', '.', ' ' };
for (int i = 0; ; i = (i + 1) % frameCount)
{
sb = new StringBuilder();
image.SelectActiveFrame(dimension, i);
for (int h = 0; h < image.Height; h++)
{
for (int w = 0; w < image.Width; w++)
{
Color cl = ((Bitmap)image).GetPixel(w, h);
int gray = (cl.R + cl.R + cl.B) / 3;
int index = (gray * (chars.Length - 1)) / 255;
sb.Append(chars[index]);
}
sb.Append('\n');
}
Console.SetCursorPosition(left, top);
Console.Write(sb.ToString());
System.Threading.Thread.Sleep(50);
}
}
static Image ScaleImage(Image source, int width, int height)
{
Image dest = new Bitmap(width, height);
using (Graphics gr = Graphics.FromImage(dest))
{
gr.FillRectangle(Brushes.White, 0, 0, width, height);
gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
float srcwidth = source.Width;
float srcheight = source.Height;
float dstwidth = width;
float dstheight = height;
if (srcwidth <= dstwidth && srcheight <= dstheight)
{
int left = (width - source.Width) / 2;
int top = (height - source.Height) / 2;
gr.DrawImage(source, left, top, source.Width, source.Height);
}
else if (srcwidth / srcheight > dstwidth * dstheight)
{
float cy = srcheight / srcwidth * dstwidth;
float top = ((float)dstheight - cy) / 2.0f;
if (top < 1.0f) top = 0;
gr.DrawImage(source, 0, top, dstwidth, cy);
}
else
{
float cx = srcwidth / srcheight * dstheight;
float left = ((float)dstwidth - cx) / 2.0f;
if (left < 1.0f) left = 0;
gr.DrawImage(source, 0, left, cx, dstheight);
}
return dest;
}
}
}
}
"static void Main(string[] args)" is in another code
Thanks in advance for your help
(Sorry for my english, I use a translator)
Your problem is that your trying to do two things at the same time in only one thread, which is impossible, one thread can only do one thing at a time. You need to use multiple threads to achieve your goal, which in this case is simple enough just use player.Play(); instead of player.PlaySync();.
player.Play(); automatically starts a new thread and runs the task there.
Here is a good tutorial/ introduction to threads in C#
As #MindSwipe said you need to use more threads.
Example:
class Animation
{
public Animation
{
Thread mythread = new Thread(DoSomething);
Thread mythread2 = new Thread(DoSomething2);
mythread.Start();
mythread2.Start();
}
public void DoSomething()
{
Do_gif_animation();
}
public void DoSomething2()
{
Do_music();
}
}
Related
I'm working on writing a Mendelbrot renderer in C# to practice multithreading, but am having an issue where my calculation code maxes out at 2 iterations. I don't really understand why since the online references I've been looking at seem to calculate it the same way as me. No matter what coordinates of pixels I provide, it always returns 2 (aside from 0,0 and 0,1).
`
using System;
using System.Numerics;
using SkiaSharp;
namespace Mandelbrot
{
internal class Program
{
const int MaxIterations = 100;
static void Main(string[] args)
{
int size = 250;
int[,] grid = Run(size, 0, 0, 1);
// Console.WriteLine("Please specify a square size for the image.");
// try
// {
// Console.Write("Length: ");
// height = Int32.Parse(Console.ReadLine());
// }
// catch (FormatException e)
// {
// Console.WriteLine(e);
// throw;
// }
using (var surface = SKSurface.Create(width: size, height: size, SKColorType.Rgba8888, SKAlphaType.Premul))
{
SKCanvas canvas = surface.Canvas;
canvas.DrawColor(SKColors.Coral);
for (int i = 0; i < grid.GetLength(0); i++)
{
for (int j = 0; j < grid.GetLength(1); j++)
{
if (grid[i, j] >= MaxIterations)
canvas.DrawPoint(new SKPoint(i, j), SKColors.Black);
}
}
canvas.DrawPoint(new SKPoint(250, 250), SKColors.Chartreuse);
OutputImage(surface);
}
Console.WriteLine("Program successfully completed.");
}
public static int[,] Run(int size, int fromX, int fromY, int h)
{
int[] oulltput = new int[size * size];
int[,] output = new int[size, size];
for (int i = 0; i < output.GetLength(0); i += 1)
{
for (int j = 0; j < output.GetLength(1); j += 1)
{
float x = fromX + i * h;
float y = fromY + j * h;
output[i, j] = IterCount(x, y, MaxIterations);
}
}
return output;
}
public static int IterCount(float constX, float constY, int maxIterations)
{
const float maxMagnitude = 2f;
const float maxMagnitudeSquared = maxMagnitude * maxMagnitude;
int i = 0;
float x = 0.0f, y = 0.0f;
float xSquared = 0.0f, ySquared = 0.0f;
while (xSquared + ySquared <= maxMagnitudeSquared && i < maxIterations)
{
xSquared = x * x;
ySquared = y * y;
float xtmp = xSquared - ySquared + constX;
y = 2.0f * x * y + constY;
x = xtmp;
i++;
}
return i;
}
private static void OutputImage(SKSurface surface)
{
Console.WriteLine("Attempting to write .png to disk...");
using (var image = surface.Snapshot())
using (var data = image.Encode(SKEncodedImageFormat.Png, 80))
using (var stream = File.OpenWrite("out.png"))
{
// save the data to a stream
data.SaveTo(stream);
Console.WriteLine("Success!");
}
}
}
}
`
I tried to use breakpoints and writeline statements to debug but I can't figure out where my math is going wrong. I keep getting 2 for my iteration count.
Im doing school project. My task is to write a small Winform application that represents the Bezier Curve, but with some constraints.
I did almost everything, just one more step is ahead of me.
The whole program starts with an empty canvas, then the user can click on it, and a circle is drawn. After every 4th click, the bezier curve appears to that polygon. Now comes my problem.
What I am stuck with is that I have to controll somehow where the 5th click is going to be. It must be on a line that comes from 2 points: the 3rd and 4th points.
Can anybody help me with this? I have really no idea how to even start.
So far, this is my code.
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace grafika_beadando_kettesert
{
public partial class MainForm : Form
{
Graphics g;
int counter = 0;
Pen PenBlack = Pens.Black; //ezzel a tollal rajzolom a vonalat
Pen PenCurve = new Pen(Color.Blue, 3f); //ezzel a tollal rajzolom a görbét
Brush PenPoint; //Ezzel töltöm ki a pontot
int size = 4; // a lerakott pont mérete
int found = -1;
List<PointF> Points = new List<PointF>(); //ebbe a listába tárolom a pontokat
PointF p0, p1;
public MainForm()
{
InitializeComponent();
PenPoint = new SolidBrush(canvas.BackColor);
this.DoubleBuffered = true;
}
private void canvas_Paint(object sender, PaintEventArgs e)
{
g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
for (int i = 0; i < Points.Count - 1; i++) // mindig meg kell rajzolni az eddig meghúzott vonalakat a polygonból újra
g.DrawLine(PenBlack, Points[i], Points[i + 1]);
if (counter == 4)
{
DrawBeziergorbe();
counter = 0;
}
for (int i = 0; i < Points.Count; i++) // ezzel rajzolom meg az eddig felrakott pontokat újra
{
g.FillEllipse(PenPoint, Points[i].X - size, Points[i].Y - size, 2 * size, 2 * size);
g.DrawEllipse(PenBlack, Points[i].X - size, Points[i].Y - size, 2 * size, 2 * size);
}
}
private void canvas_MouseUp(object sender, MouseEventArgs e)
{
found = -1;
}
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (found != -1)
{
Points[found] = e.Location;
canvas.Invalidate();
}
}
private void canvas_MouseDown(object sender, MouseEventArgs e)
{
for (int i = 0; i < Points.Count; i++)
{
if (Math.Abs(Points[i].X - e.X) <= size && Math.Abs(Points[i].Y - e.Y) <= size)
{
found = i;
break;
}
}
if (found == -1)
{
Points.Add(e.Location); //ha nincs túl közel a lerakott pont egy jelenlegihez, akkor hozzáadja a
//"Points" listához, hogy innen kiolvasva újra belehessen rajzolni
found = Points.Count - 1;
counter++;
canvas.Invalidate();
}
}
private void DrawBeziergorbe() //Mivel n-ed fokú bezier görbe kell, ezért használom a binomiálisos megoldást
{
int n = Points.Count - 1;
double t = 0;
double h = 1.0 / 500.0;
double b = 0.0;
p0 = new PointF(0, 0);
for (int i = 0; i <= n; i++)
{
b = B(n, i, t);
p0.X += (float)(b * Points[i].X);
p0.Y += (float)(b * Points[i].Y);
}
while (t < 1)
{
t += h;
p1 = new PointF(0, 0);
for (int i = 0; i <= n; i++)
{
b = B(n, i, t);
p1.X += (float)(b * Points[i].X);
p1.Y += (float)(b * Points[i].Y);
}
g.DrawLine(PenCurve, p0, p1);
p0 = p1;
}
}
private double B(int n, int i, double t)
{
return Binom(n, i) * (Math.Pow(1 - t, n - i) * Math.Pow(t, i));
}
private uint Binom(int n, int k)
{
if (n == 0) return 0;
else if (k == 0 || k == n) return 1;
else return Binom(n - 1, k - 1) + Binom(n - 1, k);
}
}
}
You can simply project the click position on the desired line.
If c is the click position and A and B are the two last control points, then the projected position p is:
d = B - A
p = A + dot(c - A, d) / dot(d, d) * d
I'm currently working on a project which is digitalization of analog radar. I have a PCI card which acquires data that I use. I need to do a PPI indicator, like on the picture radar PPI
But I have a problem: The data display is very slow. I'm currently displaying data point-by-point (if signal is detected it'll draw a small rectangle at the calculated position). The azimuth discrete is 0,1° so that's 3600 discretes of angles and the radius is 500 discretes, so that makes it 3600*500 = 1 800 000 points max. Which is a lot.
Here is the part of the code where display of data is done:
private void kreslenie()
{
for (int i = 0; i < bufferCH1.Length - 1; i++)
{
if (bufferCH0[i] > 4000)
{
if (azimutR >= 0 && azimutR <= Math.PI)
{
surX = (int)(sx + (i) * azimutX);
surY = (int)(sy - (i) * azimutY);
}
else
{
surX = (int)(sx - (i) * azimutX);
surY = (int)(sy - (i) * azimutY);
}
this.CreateGraphics().DrawRectangle(p, surX, surY, 1, 1);
}
}
}
Is there any other way to display such big amount of points in real-time (or near real-time)
One thing you could try is using a PictureBox control, and dynamically generating a Bitmap to display. You can do this by maintaining an array of the raw pixel data and updating it when information you want to display changes. Use the Bitmap.LockBits method to get a pointer to the raw image data, and then use Marshal.Copy to update it with the array of image data you are maintaining. Here is rough example that just randomly generates an image with points filled at a given location.
Edited:
To draw shapes you could just create a second array of image data for the background. You can create a Bitmap the same size as the other image, and draw to it using GDI. Next, save the background image as a byte array. When you generate your whole image, you first set it to the background image, and then draw your points over the background.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace PointDrawTest
{
public partial class Form1 : Form
{
public struct BoundingArea
{
public int x;
public int y;
public int width;
public int height;
}
public struct Point
{
public int x;
public int y;
}
int bytesPerPixel;
byte[] backgroundImageRgbData;
byte[] imageRgbData;
private Bitmap displayImage;
private BoundingArea area;
private List<Point> points = new List<Point>();
public Form1()
{
InitializeComponent();
LoadPoints();
}
private void LoadPoints()
{
this.points = new List<Point>();
this.area = new BoundingArea();
this.area.x = 0;
this.area.y = 0;
this.area.width = 1200;
this.area.height = 800;
displayImage = new Bitmap(this.area.width, this.area.height, PixelFormat.Format24bppRgb);
//There are three bytes per pixel in format PixelFormat.Format24bppRgb
bytesPerPixel = 3;
//Rgb byte data for the image
int rgbDataSize = this.area.width * this.area.height * bytesPerPixel;
imageRgbData = new byte[rgbDataSize];
GenerateRandomPoints();
GenerateBackgroundImage();
UpdateBitmap();
}
private void GenerateBackgroundImage()
{
Bitmap backgroundImage = new Bitmap(this.area.width, this.area.height, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(backgroundImage);
int gridSize = 40;
int rowCount = this.area.height / gridSize;
int columnCount = this.area.width / gridSize;
//Set background color to white
g.Clear(System.Drawing.Color.White);
int penWidth = 1;
Pen linePen = new Pen(System.Drawing.Color.Gray, penWidth);
//Draw horizontal lines
for (int i = 0; i < rowCount; i++)
{
float y = i * gridSize;
g.DrawLine(linePen, this.area.x, y, this.area.x + this.area.width, y);
}
//Draw vertical lines
for (int i = 0; i < columnCount; i++)
{
float x = i * gridSize;
g.DrawLine(linePen, x, this.area.y, x, this.area.y + this.area.height);
}
//Get rgb data from drawn background image and save it to array
var backgroundData = backgroundImage.LockBits(new Rectangle(this.area.x, this.area.y, this.area.width, this.area.height),
ImageLockMode.ReadWrite,
backgroundImage.PixelFormat);
IntPtr ptrFirstPixel = backgroundData.Scan0;
int rgbDataSize = this.area.width * this.area.height * bytesPerPixel;
backgroundImageRgbData = new byte[rgbDataSize];
Marshal.Copy(ptrFirstPixel, backgroundImageRgbData, 0, backgroundImageRgbData.Length);
backgroundImage.UnlockBits(backgroundData);
}
private void GenerateRandomPoints()
{
int pointCount = 100000;
var r = new Random();
for (int i = 0; i < pointCount; i++)
{
int pointX = r.Next(this.area.x, this.area.x + this.area.width);
int pointY = r.Next(this.area.y, this.area.y + this.area.height);
points.Add(new Point() { x = pointX, y = pointY });
}
}
private void UpdateBitmap()
{
var bmpData = this.displayImage.LockBits(new Rectangle(this.area.x, this.area.y, this.area.width, this.area.height),
ImageLockMode.ReadWrite,
this.displayImage.PixelFormat);
IntPtr ptrFirstPixel = bmpData.Scan0;
//Set image array to default background image
for (int i = 0; i < imageRgbData.Length; i++)
{
imageRgbData[i] = backgroundImageRgbData[i];
}
Color pixelColor = System.Drawing.Color.LightGreen;
for (int i = 0; i < this.points.Count; i++)
{
Point p = this.points[i];
int bitmapRgbIndex = (p.y * this.area.width + p.x) * bytesPerPixel;
//Apply color at a specific pixel
imageRgbData[bitmapRgbIndex] = pixelColor.B;
imageRgbData[bitmapRgbIndex + 1] = pixelColor.G;
imageRgbData[bitmapRgbIndex + 2] = pixelColor.R;
}
//Copy your bitmap byte array to the image
Marshal.Copy(imageRgbData, 0, ptrFirstPixel, imageRgbData.Length);
this.displayImage.UnlockBits(bmpData);
pbDisplay.Image = this.displayImage;
}
}
}
In my previous question, I transformed this image:
into this:
which Tesseract OCR interprets as this:
1O351
Putting a frame around the image
actually improves the OCR result.
1CB51
However, I need all 5 characters to OCR correctly, so as an experiment I used Paint.NET to rotate and align each individual letter into its proper orientation:
Resulting in the correct answer:
1CB52
How would I go about performing this correction in C#?
I've done a bit of research on various text alignment algorithms, but they all assume the existence of lines of text in the source image, lines from which you can derive a rotation angle, but which already contain the proper spacing and orientation relationships between the letters.
You can use the code in the following code project article to segment each individual character. However, when trying to deskew these characters individually any result you get is not going to be very good because there isn't very much information to go off of.
I tried using AForge.NETs HoughLineTransformation class and I got angles in the range of 80 - 90 degrees. So I tried using the following code to deskew them:
private static Bitmap DeskewImageByIndividualChars(Bitmap targetBitmap)
{
IDictionary<Rectangle, Bitmap> characters = new CCL().Process(targetBitmap);
using (Graphics g = Graphics.FromImage(targetBitmap))
{
foreach (var character in characters)
{
double angle;
BitmapData bitmapData = character.Value.LockBits(new Rectangle(Point.Empty, character.Value.Size), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
try
{
HoughLineTransformation hlt = new HoughLineTransformation();
hlt.ProcessImage(bitmapData);
angle = hlt.GetLinesByRelativeIntensity(0.5).Average(l => l.Theta);
}
finally
{
character.Value.UnlockBits(bitmapData);
}
using (Bitmap bitmap = RotateImage(character.Value, 90 - angle, Color.White))
{
g.DrawImage(bitmap, character.Key.Location);
}
}
}
return targetBitmap;
}
With the RotateImage method taken from here. However, the results didn't seem to be the best. Maybe you can try and make them better.
Here is the code from the code project article for your reference. I have made a few changes to it so that it behaves a bit safer, such as adding try-finally around the LockBits and disposing of objects properly using the using statement etc.
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
namespace ConnectedComponentLabeling
{
public class CCL
{
private Bitmap _input;
private int[,] _board;
public IDictionary<Rectangle, Bitmap> Process(Bitmap input)
{
_input = input;
_board = new int[_input.Width, _input.Height];
Dictionary<int, List<Pixel>> patterns = Find();
var images = new Dictionary<Rectangle, Bitmap>();
foreach (KeyValuePair<int, List<Pixel>> pattern in patterns)
{
using (Bitmap bmp = CreateBitmap(pattern.Value))
{
images.Add(GetBounds(pattern.Value), (Bitmap)bmp.Clone());
}
}
return images;
}
protected virtual bool CheckIsBackGround(Pixel currentPixel)
{
return currentPixel.color.A == 255 && currentPixel.color.R == 255 && currentPixel.color.G == 255 && currentPixel.color.B == 255;
}
private unsafe Dictionary<int, List<Pixel>> Find()
{
int labelCount = 1;
var allLabels = new Dictionary<int, Label>();
BitmapData imageData = _input.LockBits(new Rectangle(0, 0, _input.Width, _input.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
try
{
int bytesPerPixel = 3;
byte* scan0 = (byte*)imageData.Scan0.ToPointer();
int stride = imageData.Stride;
for (int i = 0; i < _input.Height; i++)
{
byte* row = scan0 + (i * stride);
for (int j = 0; j < _input.Width; j++)
{
int bIndex = j * bytesPerPixel;
int gIndex = bIndex + 1;
int rIndex = bIndex + 2;
byte pixelR = row[rIndex];
byte pixelG = row[gIndex];
byte pixelB = row[bIndex];
Pixel currentPixel = new Pixel(new Point(j, i), Color.FromArgb(pixelR, pixelG, pixelB));
if (CheckIsBackGround(currentPixel))
{
continue;
}
IEnumerable<int> neighboringLabels = GetNeighboringLabels(currentPixel);
int currentLabel;
if (!neighboringLabels.Any())
{
currentLabel = labelCount;
allLabels.Add(currentLabel, new Label(currentLabel));
labelCount++;
}
else
{
currentLabel = neighboringLabels.Min(n => allLabels[n].GetRoot().Name);
Label root = allLabels[currentLabel].GetRoot();
foreach (var neighbor in neighboringLabels)
{
if (root.Name != allLabels[neighbor].GetRoot().Name)
{
allLabels[neighbor].Join(allLabels[currentLabel]);
}
}
}
_board[j, i] = currentLabel;
}
}
}
finally
{
_input.UnlockBits(imageData);
}
Dictionary<int, List<Pixel>> patterns = AggregatePatterns(allLabels);
patterns = RemoveIntrusions(patterns, _input.Width, _input.Height);
return patterns;
}
private Dictionary<int, List<Pixel>> RemoveIntrusions(Dictionary<int, List<Pixel>> patterns, int width, int height)
{
var patternsCleaned = new Dictionary<int, List<Pixel>>();
foreach (var pattern in patterns)
{
bool bad = false;
foreach (Pixel item in pattern.Value)
{
//Horiz
if (item.Position.X == 0)
bad = true;
else if (item.Position.Y == width - 1)
bad = true;
//Vert
else if (item.Position.Y == 0)
bad = true;
else if (item.Position.Y == height - 1)
bad = true;
}
if (!bad)
patternsCleaned.Add(pattern.Key, pattern.Value);
}
return patternsCleaned;
}
private IEnumerable<int> GetNeighboringLabels(Pixel pix)
{
var neighboringLabels = new List<int>();
for (int i = pix.Position.Y - 1; i <= pix.Position.Y + 2 && i < _input.Height - 1; i++)
{
for (int j = pix.Position.X - 1; j <= pix.Position.X + 2 && j < _input.Width - 1; j++)
{
if (i > -1 && j > -1 && _board[j, i] != 0)
{
neighboringLabels.Add(_board[j, i]);
}
}
}
return neighboringLabels;
}
private Dictionary<int, List<Pixel>> AggregatePatterns(Dictionary<int, Label> allLabels)
{
var patterns = new Dictionary<int, List<Pixel>>();
for (int i = 0; i < _input.Height; i++)
{
for (int j = 0; j < _input.Width; j++)
{
int patternNumber = _board[j, i];
if (patternNumber != 0)
{
patternNumber = allLabels[patternNumber].GetRoot().Name;
if (!patterns.ContainsKey(patternNumber))
{
patterns[patternNumber] = new List<Pixel>();
}
patterns[patternNumber].Add(new Pixel(new Point(j, i), Color.Black));
}
}
}
return patterns;
}
private unsafe Bitmap CreateBitmap(List<Pixel> pattern)
{
int minX = pattern.Min(p => p.Position.X);
int maxX = pattern.Max(p => p.Position.X);
int minY = pattern.Min(p => p.Position.Y);
int maxY = pattern.Max(p => p.Position.Y);
int width = maxX + 1 - minX;
int height = maxY + 1 - minY;
Bitmap bmp = DrawFilledRectangle(width, height);
BitmapData imageData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
try
{
byte* scan0 = (byte*)imageData.Scan0.ToPointer();
int stride = imageData.Stride;
foreach (Pixel pix in pattern)
{
scan0[((pix.Position.X - minX) * 3) + (pix.Position.Y - minY) * stride] = pix.color.B;
scan0[((pix.Position.X - minX) * 3) + (pix.Position.Y - minY) * stride + 1] = pix.color.G;
scan0[((pix.Position.X - minX) * 3) + (pix.Position.Y - minY) * stride + 2] = pix.color.R;
}
}
finally
{
bmp.UnlockBits(imageData);
}
return bmp;
}
private Bitmap DrawFilledRectangle(int x, int y)
{
Bitmap bmp = new Bitmap(x, y);
using (Graphics graph = Graphics.FromImage(bmp))
{
Rectangle ImageSize = new Rectangle(0, 0, x, y);
graph.FillRectangle(Brushes.White, ImageSize);
}
return bmp;
}
private Rectangle GetBounds(List<Pixel> pattern)
{
var points = pattern.Select(x => x.Position);
var x_query = points.Select(p => p.X);
int xmin = x_query.Min();
int xmax = x_query.Max();
var y_query = points.Select(p => p.Y);
int ymin = y_query.Min();
int ymax = y_query.Max();
return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
}
}
}
With the above code I got the following input/output:
As you can see the B has rotated quite well but the others aren't as good.
An alternative to trying to deskew the individual characters is to find there location using the segmentation routine above. Then passing each individual character through to your recognition engine separately and seeing if this improves your results.
I have used the following method to find the angle of the character using the List<Pixel> from inside the CCL class. It works by finding the angle between the "bottom left" and "bottom right" points. I haven't tested if it works if the character is rotated the other way around.
private double GetAngle(List<Pixel> pattern)
{
var pixels = pattern.Select(p => p.Position).ToArray();
Point bottomLeft = pixels.OrderByDescending(p => p.Y).ThenBy(p => p.X).First();
Point rightBottom = pixels.OrderByDescending(p => p.X).ThenByDescending(p => p.Y).First();
int xDiff = rightBottom.X - bottomLeft.X;
int yDiff = rightBottom.Y - bottomLeft.Y;
double angle = Math.Atan2(yDiff, xDiff) * 180 / Math.PI;
return -angle;
}
Note my drawing code is a bit broken so that is why the 5 is cut off on the right but this code produces the following output:
Note that the B and the 5 are rotated further than you'd expect because of their curvature.
Using the following code by getting the angle from the left and right edges and then choosing the best one, the rotations seems to be better. Note I have only tested it with letters that need rotating clockwise so if they need to go the opposite way it might not work too well.
This also "quadrants" the pixels so that each pixel is chosen from it's own quadrant as not to get two that are too nearby.
The idea in selecting the best angle is if they are similar, at the moment within 1.5 degrees of each other but can easily be updated, average them. Else we pick the one that is closest to zero.
private double GetAngle(List<Pixel> pattern, Rectangle bounds)
{
int halfWidth = bounds.X + (bounds.Width / 2);
int halfHeight = bounds.Y + (bounds.Height / 2);
double leftEdgeAngle = GetAngleLeftEdge(pattern, halfWidth, halfHeight);
double rightEdgeAngle = GetAngleRightEdge(pattern, halfWidth, halfHeight);
if (Math.Abs(leftEdgeAngle - rightEdgeAngle) <= 1.5)
{
return (leftEdgeAngle + rightEdgeAngle) / 2d;
}
if (Math.Abs(leftEdgeAngle) > Math.Abs(rightEdgeAngle))
{
return rightEdgeAngle;
}
else
{
return leftEdgeAngle;
}
}
private double GetAngleLeftEdge(List<Pixel> pattern, double halfWidth, double halfHeight)
{
var topLeftPixels = pattern.Select(p => p.Position).Where(p => p.Y < halfHeight && p.X < halfWidth).ToArray();
var bottomLeftPixels = pattern.Select(p => p.Position).Where(p => p.Y > halfHeight && p.X < halfWidth).ToArray();
Point topLeft = topLeftPixels.OrderBy(p => p.X).ThenBy(p => p.Y).First();
Point bottomLeft = bottomLeftPixels.OrderByDescending(p => p.Y).ThenBy(p => p.X).First();
int xDiff = bottomLeft.X - topLeft.X;
int yDiff = bottomLeft.Y - topLeft.Y;
double angle = Math.Atan2(yDiff, xDiff) * 180 / Math.PI;
return 90 - angle;
}
private double GetAngleRightEdge(List<Pixel> pattern, double halfWidth, double halfHeight)
{
var topRightPixels = pattern.Select(p => p.Position).Where(p => p.Y < halfHeight && p.X > halfWidth).ToArray();
var bottomRightPixels = pattern.Select(p => p.Position).Where(p => p.Y > halfHeight && p.X > halfWidth).ToArray();
Point topRight = topRightPixels.OrderBy(p => p.Y).ThenByDescending(p => p.X).First();
Point bottomRight = bottomRightPixels.OrderByDescending(p => p.X).ThenByDescending(p => p.Y).First();
int xDiff = bottomRight.X - topRight.X;
int yDiff = bottomRight.Y - topRight.Y;
double angle = Math.Atan2(xDiff, yDiff) * 180 / Math.PI;
return Math.Abs(angle);
}
This now produces the following output, again my drawing code is slightly broken. Note that the C looks to not have deskewed very well but looking closely it is just the shape of it that has caused this to happen.
I improved the drawing code and also attempted to get the characters onto the same baseline:
private static Bitmap DeskewImageByIndividualChars(Bitmap bitmap)
{
IDictionary<Rectangle, Tuple<Bitmap, double>> characters = new CCL().Process(bitmap);
Bitmap deskewedBitmap = new Bitmap(bitmap.Width, bitmap.Height, bitmap.PixelFormat);
deskewedBitmap.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
using (Graphics g = Graphics.FromImage(deskewedBitmap))
{
g.FillRectangle(Brushes.White, new Rectangle(Point.Empty, deskewedBitmap.Size));
int baseLine = characters.Max(c => c.Key.Bottom);
foreach (var character in characters)
{
int y = character.Key.Y;
if (character.Key.Bottom != baseLine)
{
y += (baseLine - character.Key.Bottom - 1);
}
using (Bitmap characterBitmap = RotateImage(character.Value.Item1, character.Value.Item2, Color.White))
{
g.DrawImage(characterBitmap, new Point(character.Key.X, y));
}
}
}
return deskewedBitmap;
}
This then produces the following output. Note each character isn't on the exact same baseline due to the pre rotation bottom being taken to work it out. To improve the code using the baseline from post rotation would be needed. Also thresholding the image before doing the baseline would help.
Another improvement would be to calculate the Right of each of the rotated characters locations so when drawing the next one it doesn't overlap the previous and cut bits off. Because as you can see in the output the 2 is slightly cutting into the 5.
The output is now very similar to the manually created one in the OP.
I am currently trying new techniques for going through every pixel on a image and doing some minor process for each. Here is my active code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace ImageProcessingExmp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Timer timer = new Timer();
private void Form1_Load(object sender, EventArgs e)
{
timer.Tick += ChangeColor;
timer.Interval = 1;
timer.Start();
pictureBox1.Width = this.Width;
pictureBox1.Height = this.Height;
pictureBox1.Left = 0;
pictureBox1.Top = 0;
this.WindowState = FormWindowState.Maximized;
}
int timerinc = 0;
public void ChangeColor(object sender, EventArgs e)
{
if (timerinc>=240)
{
timer.Stop();
}
timerinc+=10;
Bitmap bmp = (Bitmap)new Bitmap(this.Width,this.Height);
Benchmark.Start();
///////////////////////////////////////////////////////////////////////////////
// Get the area to be painted
Rectangle areaToPaint = new Rectangle();
BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int stride = data.Stride;
Random rnd = new Random(DateTime.Now.Millisecond);
unsafe
{
byte* ptr = (byte*)data.Scan0;
// Check this is not a null area
// Go through the draw area and set the pixels as they should be
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
// layer.GetBitmap().SetPixel(x, y, m_colour);
byte x2 = Convert.ToByte(x > 255 ? 0 : x);
byte y2 = Convert.ToByte(y > 255 ? 0 : y);
ptr[(x * 3) + y * stride] = x2;
ptr[(x * 3) + y * stride + 1] = y2;
ptr[(x * 3) + y * stride + 2] = 255;
}
}
}
bmp.UnlockBits(data);
/////////////////////////////////////////////////////////////////////////////////
Benchmark.End();
double seconds = Benchmark.GetSeconds();
//MessageBox.Show(seconds.ToString());
richTextBox1.Text += seconds.ToString()+'\n' ;
richTextBox2.Text += DateTime.Now.Second.ToString()+'\n';
pictureBox1.Image= bmp;
String[] lines = richTextBox1.Text.Split('\n');
double avg = 0;
double sum = 0;
double num = 0;
foreach (var line in lines)
{
// MessageBox.Show(line);
double tet = 0;
Double.TryParse(line, out tet);
sum += tet;
num++;
}
avg = sum / num;
label1.Text = "fps = " + (1/avg).ToString();
}
public class Benchmark
{
private static DateTime startDate = DateTime.MinValue;
private static DateTime endDate = DateTime.MinValue;
public static TimeSpan Span { get { return endDate.Subtract(startDate); } }
public static void Start() { startDate = DateTime.Now; }
public static void End() { endDate = DateTime.Now; }
public static double GetSeconds()
{
if (endDate == DateTime.MinValue) return 0.0;
else return Span.TotalSeconds;
}
}
}
}
I need to get the FPS to around, say, 90 fps. How can I do this? Do I need to use something other than C#? The current fps is about 7.
EDIT 1
Stack overflow edited out some of my text.. weird...
Here is the real unsafe code.
unsafe
{
byte* ptr = (byte*)data.Scan0;
// Check this is not a null area
// Go through the draw area and set the pixels as they should be
for (int y = 0; y< bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
// layer.GetBitmap().SetPixel(x, y, m_colour);
byte x2 = Convert.ToByte(x > 255 ? 0 : x);
byte y2 = Convert.ToByte(y > 255 ? 0 : y);
ptr[(x * 3) + y * stride] = x2;
ptr[(x * 3) + y * stride + 1] = y2;
ptr[(x * 3) + y * stride + 2] = 255;
}
}
}
Edit 2
Wow... it did it again... I am replacing the less than symbols with (LESSTHAN)
unsafe
{
byte* ptr = (byte*)data.Scan0;
// Check this is not a null area
// Go through the draw area and set the pixels as they should be
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
// layer.GetBitmap().SetPixel(x, y, m_colour);
byte x2 = Convert.ToByte(x > 255 ? 0 : x);
byte y2 = Convert.ToByte(y > 255 ? 0 : y);
ptr[(x * 3) + y * stride] = x2;
ptr[(x * 3) + y * stride + 1] = y2;
ptr[(x * 3) + y * stride + 2] = 255;
}
}
}
Edit 3
Tried changing the format to PixelFormat.Format32bppPArgb. Didn't work. Got this:
Click to view image
Edit 4
Plenty of changes to the code. Here is the current code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace ImageProcessingExmp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Timer timer = new Timer();
private void Form1_Load(object sender, EventArgs e)
{
timer.Tick += ChangeColor;
timer.Interval = 1;
timer.Start();
pictureBox1.Width = this.Width;
pictureBox1.Height = this.Height;
pictureBox1.Left = 0;
pictureBox1.Top = 0;
this.WindowState = FormWindowState.Maximized;
bmp=(Bitmap)new Bitmap(this.Width, this.Height);
}
int timerinc = 0;
int stride;
Bitmap bmp;
BitmapData data;
public void ChangeColor(object sender, EventArgs e)
{
timerinc+=10;
Benchmark.Start();
///////////////////////////////////////////////////////////////////////////////
data= bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
stride = data.Stride;
unsafe
{
byte* ptr = (byte*)data.Scan0;
// Check this is not a null area
// Go through the draw area and set the pixels as they should be
for (int y = 0; y < bmp.Height; y++)
{
int yTimeStride = y * stride;
byte y2 = (byte)(y > 255 ? 0 : y);
for (int x = 0; x < bmp.Width; x++)
{
int addressToAssign = (x * 3) + yTimeStride;
byte x2 = (byte)(x > 255 ? 0 : x);
ptr[addressToAssign] = x2;
ptr[addressToAssign + 1] = y2;
ptr[addressToAssign + 2] = x2;
}
}
}
bmp.UnlockBits(data);
/////////////////////////////////////////////////////////////////////////////////
Benchmark.End();
double seconds = Benchmark.GetSeconds();
//MessageBox.Show(seconds.ToString());
richTextBox1.Text += seconds.ToString()+'\n' ;
richTextBox2.Text += DateTime.Now.Second.ToString()+'\n';
pictureBox1.Image= bmp;
String[] lines = richTextBox1.Text.Split('\n');
double avg = 0;
double sum = 0;
double num = 0;
foreach (var line in lines)
{
// MessageBox.Show(line);
double tet = 0;
Double.TryParse(line, out tet);
sum += tet;
num++;
}
avg = sum / num;
label1.Text = "fps = " + (1/avg).ToString();
tst1.Add(1/avg*20);
if (timerinc >= 500)
{
timer.Stop();
Bitmap bmp2 = new Bitmap(pictureBox2.Width,pictureBox2.Height);
data = bmp2.LockBits(new Rectangle(0, 0, bmp2.Width, bmp2.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
stride = data.Stride;
unsafe
{
byte* ptr = (byte*)data.Scan0;
// Check this is not a null area
// Go through the draw area and set the pixels as they should be
for (int x = 0; x < tst1.Count; x++)
{
for (int y = 0; y < (int)tst1[x]; y++)
{
//MessageBox.Show(x.ToString());
if (y<bmp2.Height)
{
ptr[(x * 3) + (int)y * stride] = 255;//b
ptr[(x * 3) + (int)y * stride + 1] =0;//g
ptr[(x * 3) + (int)y * stride + 2] = 255;//r
}
}
}
}
bmp2.UnlockBits(data);
pictureBox2.Image = bmp2;
}
}
public List<double> tst1 = new List<double>();
public class Benchmark
{
private static DateTime startDate = DateTime.MinValue;
private static DateTime endDate = DateTime.MinValue;
public static TimeSpan Span { get { return endDate.Subtract(startDate); } }
public static void Start() { startDate = DateTime.Now; }
public static void End() { endDate = DateTime.Now; }
public static double GetSeconds()
{
if (endDate == DateTime.MinValue) return 0.0;
else return Span.TotalSeconds;
}
}
}
}
Unfortunately, this only gets me 5 fps. I am starting to believe that painting on a bitmap then putting it onto the screen is not the way to quickly render games. Does anybody know another way?
Considering the code you're benchmarking:
rnd is useless (wrong copy/paste?)
you're doing way too much multiplication, consider using:
Code:
for (int y = 0; y < bmp.Height; y++)
{
int yTimeStride = y * stride;
byte y2 = Convert.ToByte(y > 255 ? 0 : y);
for (int x = 0; x < bmp.Width; x++)
{
int addressToAssign = (x * 3) + yTimeStride;
byte x2 = Convert.ToByte(x > 255 ? 0 : x);
ptr[addressToAssign] = x2;
ptr[addressToAssign + 1] = y2;
ptr[addressToAssign + 2] = 255;
}
}
This will reduce multiplication with y from Height*Width*3 to Height and reduce multiplication with x by 3.