I have a console application that manages images. Now i need something like a preview of the Images within the console application. Is there a way to display them in the console?
Here is a comparison of the current character based answers:
Input:
Output:
Though showing an image in a console is not the intended usage of the console, you can surely hack the things, as the console window is just a window, like any other windows.
Actually, once I have started to develop a text controls library for console applications with graphics support. I have never finished that, though I have a working proof-of-concept demo:
And if you obtain the console font size, you can place the image very precisely.
This is how you can do it:
static void Main(string[] args)
{
Console.WriteLine("Graphics in console window!");
Point location = new Point(10, 10);
Size imageSize = new Size(20, 10); // desired image size in characters
// draw some placeholders
Console.SetCursorPosition(location.X - 1, location.Y);
Console.Write(">");
Console.SetCursorPosition(location.X + imageSize.Width, location.Y);
Console.Write("<");
Console.SetCursorPosition(location.X - 1, location.Y + imageSize.Height - 1);
Console.Write(">");
Console.SetCursorPosition(location.X + imageSize.Width, location.Y + imageSize.Height - 1);
Console.WriteLine("<");
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures), #"Sample Pictures\tulips.jpg");
using (Graphics g = Graphics.FromHwnd(GetConsoleWindow()))
{
using (Image image = Image.FromFile(path))
{
Size fontSize = GetConsoleFontSize();
// translating the character positions to pixels
Rectangle imageRect = new Rectangle(
location.X * fontSize.Width,
location.Y * fontSize.Height,
imageSize.Width * fontSize.Width,
imageSize.Height * fontSize.Height);
g.DrawImage(image, imageRect);
}
}
}
Here is how you can obtain the current console font size:
private static Size GetConsoleFontSize()
{
// getting the console out buffer handle
IntPtr outHandle = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero,
OPEN_EXISTING,
0,
IntPtr.Zero);
int errorCode = Marshal.GetLastWin32Error();
if (outHandle.ToInt32() == INVALID_HANDLE_VALUE)
{
throw new IOException("Unable to open CONOUT$", errorCode);
}
ConsoleFontInfo cfi = new ConsoleFontInfo();
if (!GetCurrentConsoleFont(outHandle, false, cfi))
{
throw new InvalidOperationException("Unable to get font information.");
}
return new Size(cfi.dwFontSize.X, cfi.dwFontSize.Y);
}
And the required additional WinApi calls, constants and types:
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetConsoleWindow();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(
string lpFileName,
int dwDesiredAccess,
int dwShareMode,
IntPtr lpSecurityAttributes,
int dwCreationDisposition,
int dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetCurrentConsoleFont(
IntPtr hConsoleOutput,
bool bMaximumWindow,
[Out][MarshalAs(UnmanagedType.LPStruct)]ConsoleFontInfo lpConsoleCurrentFont);
[StructLayout(LayoutKind.Sequential)]
internal class ConsoleFontInfo
{
internal int nFont;
internal Coord dwFontSize;
}
[StructLayout(LayoutKind.Explicit)]
internal struct Coord
{
[FieldOffset(0)]
internal short X;
[FieldOffset(2)]
internal short Y;
}
private const int GENERIC_READ = unchecked((int)0x80000000);
private const int GENERIC_WRITE = 0x40000000;
private const int FILE_SHARE_READ = 1;
private const int FILE_SHARE_WRITE = 2;
private const int INVALID_HANDLE_VALUE = -1;
private const int OPEN_EXISTING = 3;
And the result:
[
I further played with code from #DieterMeemken. I halved vertical resolution and added dithering via ░▒▓. On the left is Dieter Meemken result, on the right my. On the bottom is original picture resized to rougly match the output.
While Malwyns conversion function is impressive, it does not use all gray colors, what is pity.
static int[] cColors = { 0x000000, 0x000080, 0x008000, 0x008080, 0x800000, 0x800080, 0x808000, 0xC0C0C0, 0x808080, 0x0000FF, 0x00FF00, 0x00FFFF, 0xFF0000, 0xFF00FF, 0xFFFF00, 0xFFFFFF };
public static void ConsoleWritePixel(Color cValue)
{
Color[] cTable = cColors.Select(x => Color.FromArgb(x)).ToArray();
char[] rList = new char[] { (char)9617, (char)9618, (char)9619, (char)9608 }; // 1/4, 2/4, 3/4, 4/4
int[] bestHit = new int[] { 0, 0, 4, int.MaxValue }; //ForeColor, BackColor, Symbol, Score
for (int rChar = rList.Length; rChar > 0; rChar--)
{
for (int cFore = 0; cFore < cTable.Length; cFore++)
{
for (int cBack = 0; cBack < cTable.Length; cBack++)
{
int R = (cTable[cFore].R * rChar + cTable[cBack].R * (rList.Length - rChar)) / rList.Length;
int G = (cTable[cFore].G * rChar + cTable[cBack].G * (rList.Length - rChar)) / rList.Length;
int B = (cTable[cFore].B * rChar + cTable[cBack].B * (rList.Length - rChar)) / rList.Length;
int iScore = (cValue.R - R) * (cValue.R - R) + (cValue.G - G) * (cValue.G - G) + (cValue.B - B) * (cValue.B - B);
if (!(rChar > 1 && rChar < 4 && iScore > 50000)) // rule out too weird combinations
{
if (iScore < bestHit[3])
{
bestHit[3] = iScore; //Score
bestHit[0] = cFore; //ForeColor
bestHit[1] = cBack; //BackColor
bestHit[2] = rChar; //Symbol
}
}
}
}
}
Console.ForegroundColor = (ConsoleColor)bestHit[0];
Console.BackgroundColor = (ConsoleColor)bestHit[1];
Console.Write(rList[bestHit[2] - 1]);
}
public static void ConsoleWriteImage(Bitmap source)
{
int sMax = 39;
decimal percent = Math.Min(decimal.Divide(sMax, source.Width), decimal.Divide(sMax, source.Height));
Size dSize = new Size((int)(source.Width * percent), (int)(source.Height * percent));
Bitmap bmpMax = new Bitmap(source, dSize.Width * 2, dSize.Height);
for (int i = 0; i < dSize.Height; i++)
{
for (int j = 0; j < dSize.Width; j++)
{
ConsoleWritePixel(bmpMax.GetPixel(j * 2, i));
ConsoleWritePixel(bmpMax.GetPixel(j * 2 + 1, i));
}
System.Console.WriteLine();
}
Console.ResetColor();
}
usage:
Bitmap bmpSrc = new Bitmap(#"HuwnC.gif", true);
ConsoleWriteImage(bmpSrc);
EDIT
Color distance is complex topic (here, here and links on those pages...). I tried to calculate distance in YUV and results were rather worse than in RGB. They could be better with Lab and DeltaE, but I did not try that. Distance in RGB seems to be good enough. In fact results are very simmilar for both euclidean and manhattan distance in RGB color space, so I suspect there are just too few colors to choose from.
The rest is just brute force compare of color against all combinations of colors and patterns (=symbols). I stated fill ratio for ░▒▓█ to be 1/4, 2/4, 3/4 and 4/4. In that case the third symbol is in fact redundant to the first. But if ratios were not such uniform (depends on font), results could change, so I left it there for future improvements. Average color of symbol is calculated as weighed average of foregroudColor and backgroundColor according to fill ratio. It assumes linear colors, what is also big simplification. So there is still room for improvement.
If you use ASCII 219 ( █ ) twice, you have something like a pixel ( ██ ).
Now you are restricted by the amount of pixels and the number of colors in your console application.
if you keep default settings you have about 39x39 pixel, if you want more you can resize your console with Console.WindowHeight = resSize.Height + 1;and Console.WindowWidth = resultSize.Width * 2;
you have to keep the image's aspect-ratio as far as possible, so you won't have 39x39 in the most cases
Malwyn posted a totally underrated method to convert System.Drawing.Color to System.ConsoleColor
so my approach would be
using System.Drawing;
public static int ToConsoleColor(System.Drawing.Color c)
{
int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0;
index |= (c.R > 64) ? 4 : 0;
index |= (c.G > 64) ? 2 : 0;
index |= (c.B > 64) ? 1 : 0;
return index;
}
public static void ConsoleWriteImage(Bitmap src)
{
int min = 39;
decimal pct = Math.Min(decimal.Divide(min, src.Width), decimal.Divide(min, src.Height));
Size res = new Size((int)(src.Width * pct), (int)(src.Height * pct));
Bitmap bmpMin = new Bitmap(src, res);
for (int i = 0; i < res.Height; i++)
{
for (int j = 0; j < res.Width; j++)
{
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMin.GetPixel(j, i));
Console.Write("██");
}
System.Console.WriteLine();
}
}
so you can
ConsoleWriteImage(new Bitmap(#"C:\image.gif"));
sample input:
sample output:
that was fun.
Thanks fubo, i tried your solution and was able to increase the resolution of the preview by 4 (2x2).
I found, that you can set the background color for each individual char. So, instead of using two ASCII 219 ( █ ) chars, i used ASCII 223 ( ▀ ) two times with different foreground and background Colors. That divides the big Pixel ( ██ ) in 4 subpixels like this ( ▀▄ ).
In this example i put both images next to each other, so you can see the difference easily:
Here is the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace ConsoleWithImage
{
class Program
{
public static void ConsoleWriteImage(Bitmap bmpSrc)
{
int sMax = 39;
decimal percent = Math.Min(decimal.Divide(sMax, bmpSrc.Width), decimal.Divide(sMax, bmpSrc.Height));
Size resSize = new Size((int)(bmpSrc.Width * percent), (int)(bmpSrc.Height * percent));
Func<System.Drawing.Color, int> ToConsoleColor = c =>
{
int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0;
index |= (c.R > 64) ? 4 : 0;
index |= (c.G > 64) ? 2 : 0;
index |= (c.B > 64) ? 1 : 0;
return index;
};
Bitmap bmpMin = new Bitmap(bmpSrc, resSize.Width, resSize.Height);
Bitmap bmpMax = new Bitmap(bmpSrc, resSize.Width * 2, resSize.Height * 2);
for (int i = 0; i < resSize.Height; i++)
{
for (int j = 0; j < resSize.Width; j++)
{
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMin.GetPixel(j, i));
Console.Write("██");
}
Console.BackgroundColor = ConsoleColor.Black;
Console.Write(" ");
for (int j = 0; j < resSize.Width; j++)
{
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2, i * 2));
Console.BackgroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2, i * 2 + 1));
Console.Write("▀");
Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2 + 1, i * 2));
Console.BackgroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2 + 1, i * 2 + 1));
Console.Write("▀");
}
System.Console.WriteLine();
}
}
static void Main(string[] args)
{
System.Console.WindowWidth = 170;
System.Console.WindowHeight = 40;
Bitmap bmpSrc = new Bitmap(#"image.bmp", true);
ConsoleWriteImage(bmpSrc);
System.Console.ReadLine();
}
}
}
To run the example, the bitmap "image.bmp" has to be in the same directory as the executable. I increased the size of the console, the size of the preview is still 39 and can be changed at int sMax = 39;.
The solution from taffer is also very cool.
You two have my upvote...
I was reading about color spaces and LAB space appears to be a good option for you (see this questions: Finding an accurate “distance” between colors and Algorithm to check similarity of colors)
Quoting Wikipedia CIELAB page, the advantages of this color space are:
Unlike the RGB and CMYK color models, Lab color is designed to approximate human vision. It aspires to perceptual uniformity, and its L component closely matches human perception of lightness. Thus, it can be used to make accurate color balance corrections by modifying output curves in the a and b components.
To measure the distance between colors you can use Delta E distance.
With this you can approximate better from Color to ConsoleColor:
Firstly, you can define an CieLab class to represent colors in this space:
public class CieLab
{
public double L { get; set; }
public double A { get; set; }
public double B { get; set; }
public static double DeltaE(CieLab l1, CieLab l2)
{
return Math.Pow(l1.L - l2.L, 2) + Math.Pow(l1.A - l2.A, 2) + Math.Pow(l1.B - l2.B, 2);
}
public static CieLab Combine(CieLab l1, CieLab l2, double amount)
{
var l = l1.L * amount + l2.L * (1 - amount);
var a = l1.A * amount + l2.A * (1 - amount);
var b = l1.B * amount + l2.B * (1 - amount);
return new CieLab { L = l, A = a, B = b };
}
}
There are two static methods, one to measure the distance using Delta E (DeltaE) and other to combine two colors specifying how much of each color (Combine).
And for transform from RGB to LAB you can use the following method (from here):
public static CieLab RGBtoLab(int red, int green, int blue)
{
var rLinear = red / 255.0;
var gLinear = green / 255.0;
var bLinear = blue / 255.0;
double r = rLinear > 0.04045 ? Math.Pow((rLinear + 0.055) / (1 + 0.055), 2.2) : (rLinear / 12.92);
double g = gLinear > 0.04045 ? Math.Pow((gLinear + 0.055) / (1 + 0.055), 2.2) : (gLinear / 12.92);
double b = bLinear > 0.04045 ? Math.Pow((bLinear + 0.055) / (1 + 0.055), 2.2) : (bLinear / 12.92);
var x = r * 0.4124 + g * 0.3576 + b * 0.1805;
var y = r * 0.2126 + g * 0.7152 + b * 0.0722;
var z = r * 0.0193 + g * 0.1192 + b * 0.9505;
Func<double, double> Fxyz = t => ((t > 0.008856) ? Math.Pow(t, (1.0 / 3.0)) : (7.787 * t + 16.0 / 116.0));
return new CieLab
{
L = 116.0 * Fxyz(y / 1.0) - 16,
A = 500.0 * (Fxyz(x / 0.9505) - Fxyz(y / 1.0)),
B = 200.0 * (Fxyz(y / 1.0) - Fxyz(z / 1.0890))
};
}
The idea is use shade characters like #AntoninLejsek do ('█', '▓', '▒', '░'), this allows you to get more than 16 colors combining the console colors (using Combine method).
Here, we can do some improvements by pre-computing the colors to use:
class ConsolePixel
{
public char Char { get; set; }
public ConsoleColor Forecolor { get; set; }
public ConsoleColor Backcolor { get; set; }
public CieLab Lab { get; set; }
}
static List<ConsolePixel> pixels;
private static void ComputeColors()
{
pixels = new List<ConsolePixel>();
char[] chars = { '█', '▓', '▒', '░' };
int[] rs = { 0, 0, 0, 0, 128, 128, 128, 192, 128, 0, 0, 0, 255, 255, 255, 255 };
int[] gs = { 0, 0, 128, 128, 0, 0, 128, 192, 128, 0, 255, 255, 0, 0, 255, 255 };
int[] bs = { 0, 128, 0, 128, 0, 128, 0, 192, 128, 255, 0, 255, 0, 255, 0, 255 };
for (int i = 0; i < 16; i++)
for (int j = i + 1; j < 16; j++)
{
var l1 = RGBtoLab(rs[i], gs[i], bs[i]);
var l2 = RGBtoLab(rs[j], gs[j], bs[j]);
for (int k = 0; k < 4; k++)
{
var l = CieLab.Combine(l1, l2, (4 - k) / 4.0);
pixels.Add(new ConsolePixel
{
Char = chars[k],
Forecolor = (ConsoleColor)i,
Backcolor = (ConsoleColor)j,
Lab = l
});
}
}
}
Another improvement could be access directly to the image data using LockBits instead of using GetPixel.
UPDATE: If the image have parts with the same color you can speed up considerably the process drawing chunk of characters having the same colors, instead of individuals chars:
public static void DrawImage(Bitmap source)
{
int width = Console.WindowWidth - 1;
int height = (int)(width * source.Height / 2.0 / source.Width);
using (var bmp = new Bitmap(source, width, height))
{
var unit = GraphicsUnit.Pixel;
using (var src = bmp.Clone(bmp.GetBounds(ref unit), PixelFormat.Format24bppRgb))
{
var bits = src.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, src.PixelFormat);
byte[] data = new byte[bits.Stride * bits.Height];
Marshal.Copy(bits.Scan0, data, 0, data.Length);
for (int j = 0; j < height; j++)
{
StringBuilder builder = new StringBuilder();
var fore = ConsoleColor.White;
var back = ConsoleColor.Black;
for (int i = 0; i < width; i++)
{
int idx = j * bits.Stride + i * 3;
var pixel = DrawPixel(data[idx + 2], data[idx + 1], data[idx + 0]);
if (pixel.Forecolor != fore || pixel.Backcolor != back)
{
Console.ForegroundColor = fore;
Console.BackgroundColor = back;
Console.Write(builder);
builder.Clear();
}
fore = pixel.Forecolor;
back = pixel.Backcolor;
builder.Append(pixel.Char);
}
Console.ForegroundColor = fore;
Console.BackgroundColor = back;
Console.WriteLine(builder);
}
Console.ResetColor();
}
}
}
private static ConsolePixel DrawPixel(int r, int g, int b)
{
var l = RGBtoLab(r, g, b);
double diff = double.MaxValue;
var pixel = pixels[0];
foreach (var item in pixels)
{
var delta = CieLab.DeltaE(l, item.Lab);
if (delta < diff)
{
diff = delta;
pixel = item;
}
}
return pixel;
}
Finally, call DrawImage like so:
static void Main(string[] args)
{
ComputeColors();
Bitmap image = new Bitmap("image.jpg", true);
DrawImage(image);
}
Result Images:
The following solutions aren't based on chars but provides full detailed images
You can draw over any window using its handler to create a Graphics object. To get the handler of a console application you can do it importing GetConsoleWindow:
[DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow", SetLastError = true)]
private static extern IntPtr GetConsoleHandle();
Then, create a graphics with the handler (using Graphics.FromHwnd) and draw the image using the methods in Graphics object, for example:
static void Main(string[] args)
{
var handler = GetConsoleHandle();
using (var graphics = Graphics.FromHwnd(handler))
using (var image = Image.FromFile("img101.png"))
graphics.DrawImage(image, 50, 50, 250, 200);
}
This looks fine but if the console is resized or scrolled, the image disappears because the windows is refreshed (maybe implementing some kind of mechanism to redraw the image is possible in your case).
Another solution is embedding a window (Form) into the console application. To do this you have to import SetParent (and MoveWindow to relocate the window inside the console):
[DllImport("user32.dll")]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
Then you just need to create a Form and set BackgroundImage property to the desired image (do it on a Thread or Task to avoid blocking the console):
static void Main(string[] args)
{
Task.Factory.StartNew(ShowImage);
Console.ReadLine();
}
static void ShowImage()
{
var form = new Form
{
BackgroundImage = Image.FromFile("img101.png"),
BackgroundImageLayout = ImageLayout.Stretch
};
var parent = GetConsoleHandle();
var child = form.Handle;
SetParent(child, parent);
MoveWindow(child, 50, 50, 250, 200, true);
Application.Run(form);
}
Of course you can set FormBorderStyle = FormBorderStyle.None to hide windows borders (right image)
In this case you can resize the console and the image/window still be there.
One benefit with this approach is that you can locate the window where you want and change the image at any time by just changing BackgroundImage property.
There's no direct way. But you may try to use an image-to-ascii-art converter like this one
Yes, you can do it, if you stretch the question a little by opening a Form from within the Console application.
Here is how you can have you console application open a form and display an image:
include these two references in your project: System.Drawing and System.Windows.Forms
include the two namespaces as well:
using System.Windows.Forms;
using System.Drawing;
See this post on how to do that!
Now all you need it to add something like this:
Form form1 = new Form();
form1.BackgroundImage = bmp;
form1.ShowDialog();
Of course you can also use a PictureBox..
And you can use form1.Show(); to keep the console alive while the preview shows..
Original post: Of course you can't properly display an image inside a 25x80 window; even if you use a larger window and block graphics it wouldn't be a preview but a mess!
Update: Looks like you can after all GDI-draw an image onto the Console Form; see taffer's answer!
Related
I got a large set of infrared images of seeds, their sizes vary slightly.
And I would like to find them (in thefastest possible way).
Below i show zoomed in details of the images i process.
After a first noise removal and blob filter this is what i have :
The bright white is just direct reflection of the IR lamp, white pixels never combine (stretch out) over multiple seeds.
To make it more clear i placed a letter on some individual seeds.
The problems i have:
A is a single seed (dirt on the seed) generates a slight dark line.
B the nearby X close to it is at its darkest intersection its still brighter as some other seeds (cannt change brightnes or remove if gray value is lower then a certain value.
C those are 3 seeds close to each other.
The smallest visible seeds above should not becomme even smaller.
I'm not making use of mathlab or openCV, as i work directly with locked image and memory data. i can acces pixel data by array or simple getpixel / putpixel commands. I wrote my own graphics library which is fast enough for live camera data, processing speed currently at around 13ms at around 25ms i enter stream processing lag
I wonder how to separate those 'cloudy' blobs better.
I'm thinking to find local maxima over a certain pixels range..but that should see A as one seed, while on B find that B and X are not connected.
So I'm not sure here, how such a local peek function or another function should like like. Although i code in C# i looked at other c++ functions as well like dilate etc but thats not it. I also wrote a function to check for slope degree (like if it was a mountain height image) but that couldnt devide areas B and C.
Ok well i made different slope detection code, now i dont look for some degree but just the tilting point over a small range, it works nice on X axis.. but essentially i think it should work on both X and Y
here's the new result :
It can resolve issue A and B !!!
However it wouldnt differentiate between seeds who are aligned in a vertical row, and and it causes small white noise (non connected lines). at places where is there is nearly nothing to detect. I'm not yet sure on how to do the same (combined) over Y axis to get the tops then erase stuff from a certain distance of the top.. (to seperate).
Using this code just showing the pixel operations of it.
for (int y = raw.Height;y>5; y--)
{slopeUP = false;
int[] peek = new int[raw.Width];
for (int x = raw.Width; x> 7; x--)
{
int a = raw.GetPixelBleu(x, y);
int b = raw.GetPixelBleu(x - 1, y);
int c = raw.GetPixelBleu(x - 2, y);
int d = raw.GetPixelBleu(x - 11, y);
int f = raw.GetPixelBleu(x - 12, y);
if ((f + d) > (a + b))slopeUP = true;
if ((f + d) < (a + b))
{
if (slopeUP)
{
slopeUP = false;
peek[x - 6] = 10;
//raw.SetPixel(x, y, Color.GreenYellow);
}
else peek[x - 6] = 0;
}
}
for (int x = raw.Width; x > 7; x--)
{ if (peek[x-1] > 5) raw.SetPixel(x, y, Color.Lavender); }
}
In this SO answer to a similar question I applied persistent homology to find peaks in an image. I took your image, scaled it down to 50%, applied an Guassian blur with radius 20 (in Gimp), and applied the methods described in the other article (click to enlarge):
I only show peaks with persistence (see the other SO answer) of at least 20. The persistence diagram is shown here:
The 20-th peak would be the little peak on the top-left corner with a persistence of about 9. By applying a stronger Gaussian filter the image gets more diffuse and the peaks will be come more prominent.
Python code can be found here.
So, as far as speed, I am only going off of the image you posted here... on which everything runs blazing fast because it is tiny. Note that I padded the image after binarizing and never un-padded, so you will want to either un-pad or shift your results accordingly. You may not even want to pad, but it allows detection of cut off seeds.
Overview of pipeline: removeSaturation>>gaussian blur>>binarize>>padd>>distanceTransform>>peaks>>clustering
That being said here is my code and results:
void drawText(Mat & image);
void onMouse(int event, int x, int y, int, void*);
Mat bruteForceLocalMax(Mat srcImage, int searchRad);
void zoomPixelImage(Mat sourceImage, int multFactor, string name, bool mouseCallback);
Mat mergeLocalPeaks(Mat srcImage, int mergeRadius);
Mat image;
bool debugDisplays = false;
int main()
{
cout << "Built with OpenCV " << CV_VERSION << endl;
TimeStamp precisionClock = TimeStamp();
image = imread("../Raw_Images/Seeds1.png",0);
if (image.empty()) { cout << "failed to load image"<<endl; }
else
{
zoomPixelImage(image, 5, "raw data", false);
precisionClock.labeledlapStamp("image read", true);
//find max value in image that is not oversaturated
int maxVal = 0;
for (int x = 0; x < image.rows; x++)
{
for (int y = 0; y < image.cols; y++)
{
int val = image.at<uchar>(x, y);
if (val >maxVal && val !=255)
{
maxVal = val;
}
}
}
//get rid of oversaturation regions (as they throw off processing)
image.setTo(maxVal, image == 255);
if (debugDisplays)
{zoomPixelImage(image, 5, "unsaturated data", false);}
precisionClock.labeledlapStamp("Unsaturate Data", true);
Mat gaussianBlurred = Mat();
GaussianBlur(image, gaussianBlurred, Size(9, 9), 10, 0);
if (debugDisplays)
{zoomPixelImage(gaussianBlurred, 5, "blurred data", false);}
precisionClock.labeledlapStamp("Gaussian", true);
Mat binarized = Mat();
threshold(gaussianBlurred, binarized, 50, 255, THRESH_BINARY);
if (debugDisplays)
{zoomPixelImage(binarized, 5, "binarized data", false);}
precisionClock.labeledlapStamp("binarized", true);
//pad edges (may or may not be neccesary depending on setup)
Mat paddedImage = Mat();
copyMakeBorder(binarized, paddedImage, 1, 1, 1, 1, BORDER_CONSTANT, 0);
if (debugDisplays)
{zoomPixelImage(paddedImage, 5, "padded data", false);}
precisionClock.labeledlapStamp("add padding", true);
Mat distTrans = Mat();
distanceTransform(paddedImage, distTrans, CV_DIST_L1,3,CV_8U);
if (debugDisplays)
{zoomPixelImage(distTrans, 5, "distanceTransform", true);}
precisionClock.labeledlapStamp("distTransform", true);
Mat peaks = Mat();
peaks = bruteForceLocalMax(distTrans,10);
if (debugDisplays)
{zoomPixelImage(peaks, 5, "peaks", false);}
precisionClock.labeledlapStamp("peaks", true);
//attempt to cluster any colocated peaks and find the best clustering count
Mat mergedPeaks = Mat();
mergedPeaks = mergeLocalPeaks(peaks, 5);
if (debugDisplays)
{zoomPixelImage(mergedPeaks, 5, "peaks final", false);}
precisionClock.labeledlapStamp("final peaks", true);
precisionClock.fullStamp(false);
waitKey(0);
}
}
void drawText(Mat & image)
{
putText(image, "Hello OpenCV",
Point(20, 50),
FONT_HERSHEY_COMPLEX, 1, // font face and scale
Scalar(255, 255, 255), // white
1, LINE_AA); // line thickness and type
}
void onMouse(int event, int x, int y, int, void*)
{
if (event != CV_EVENT_LBUTTONDOWN)
return;
Point pt = Point(x, y);
std::cout << "x=" << pt.x << "\t y=" << pt.y << "\t value=" << int(image.at<uchar>(y,x)) << "\n";
}
void zoomPixelImage(Mat sourceImage, int multFactor, string name, bool normalized)
{
Mat zoomed;// = Mat::zeros(sourceImage.rows*multFactor, sourceImage.cols*multFactor, CV_8U);
resize(sourceImage, zoomed, Size(sourceImage.cols*multFactor, sourceImage.rows*multFactor), sourceImage.cols*multFactor, sourceImage.rows*multFactor, INTER_NEAREST);
if (normalized) { normalize(zoomed, zoomed, 0, 255, NORM_MINMAX); }
namedWindow(name);
imshow(name, zoomed);
}
Mat bruteForceLocalMax(Mat srcImage, int searchRad)
{
Mat outputArray = Mat::zeros(srcImage.rows, srcImage.cols, CV_8U);
//global search top
for (int x = 0; x < srcImage.rows - 1; x++)
{
for (int y = 0; y < srcImage.cols - 1; y++)
{
bool peak = true;
float centerVal = srcImage.at<uchar>(x, y);
if (centerVal == 0) { continue; }
//local search top
for (int a = -searchRad; a <= searchRad; a++)
{
for (int b = -searchRad; b <= searchRad; b++)
{
if (x + a<0 || x + a>srcImage.rows - 1 || y + b < 0 || y + b>srcImage.cols - 1) { continue; }
if (srcImage.at<uchar>(x + a, y + b) > centerVal)
{
peak = false;
}
if (peak == false) { break; }
}
if (peak == false) { break; }
}
if (peak)
{
outputArray.at<uchar>(x, y) = 255;
}
}
}
return outputArray;
}
Mat mergeLocalPeaks(Mat srcImage, int mergeRadius)
{
Mat outputArray = Mat::zeros(srcImage.rows, srcImage.cols, CV_8U);
//global search top
for (int x = 0; x < srcImage.rows - 1; x++)
{
for (int y = 0; y < srcImage.cols - 1; y++)
{
float centerVal = srcImage.at<uchar>(x, y);
if (centerVal == 0) { continue; }
int aveX = x;
int aveY = y;
int xCenter = -1;
int yCenter = -1;
while (aveX != xCenter || aveY != yCenter)
{
xCenter = aveX;
yCenter = aveY;
aveX = 0;
aveY = 0;
int peakCount = 0;
//local search top
for (int a = -mergeRadius; a <= mergeRadius; a++)
{
for (int b = -mergeRadius; b <= mergeRadius; b++)
{
if (xCenter + a<0 || xCenter + a>srcImage.rows - 1 || yCenter + b < 0 || yCenter + b>srcImage.cols - 1) { continue; }
if (srcImage.at<uchar>(xCenter + a, yCenter + b) > 0)
{
aveX += (xCenter + a);
aveY += (yCenter + b);
peakCount += 1;
}
}
}
double dCentX = ((double)aveX / (double)peakCount);
double dCentY = ((double)aveY / (double)peakCount);
aveX = floor(dCentX);
aveY = floor(dCentY);
}
outputArray.at<uchar>(xCenter, yCenter) = 255;
}
}
return outputArray;
}
speed:
debug images:
results:
Hope this helps! Cheers!
I'm trying to scan 2 images (32bppArgb format), identify when there is a difference and store the difference block's bounds in a list of rectangles.
Suppose these are the images:
second:
I want to get the different rectangle bounds (the opened directory window in our case).
This is what I've done:
private unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
List<Rectangle> rec = new List<Rectangle>();
bmData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, 1920, 1080), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, 1920, 1080), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
IntPtr scan0 = bmData.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride = bmData.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
int minX = int.MaxValue;;
int minY = int.MaxValue;
int maxX = 0;
bool found = false;
for (int y = 0; y < nHeight; y++)
{
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
byte* p2 = (byte*)scan02.ToPointer();
p2 += y * stride2;
for (int x = 0; x < nWidth; x++)
{
if (p[0] != p2[0] || p[1] != p2[1] || p[2] != p2[2] || p[3] != p2[3]) //found differences-began to store positions.
{
found = true;
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
if (y < minY)
minY = y;
}
else
{
if (found)
{
int height = getBlockHeight(stride, scan0, maxX, minY, scan02, stride2);
found = false;
Rectangle temp = new Rectangle(minX, minY, maxX - minX, height);
rec.Add(temp);
//x += minX;
y += height;
minX = int.MaxValue;
minY = int.MaxValue;
maxX = 0;
}
}
p += 4;
p2 += 4;
}
}
return rec;
}
public unsafe int getBlockHeight(int stride, IntPtr scan, int x, int y1, IntPtr scan02, int stride2) //a function to get an existing block height.
{
int height = 0;;
for (int y = y1; y < 1080; y++) //only for example- in our case its 1080 height.
{
byte* p = (byte*)scan.ToPointer();
p += (y * stride) + (x * 4); //set the pointer to a specific potential point.
byte* p2 = (byte*)scan02.ToPointer();
p2 += (y * stride2) + (x * 4); //set the pointer to a specific potential point.
if (p[0] != p2[0] || p[1] != p2[1] || p[2] != p2[2] || p[3] != p2[3]) //still change on the height in the increasing **y** of the block.
height++;
}
return height;
}
This is actually how I call the method:
Bitmap a = Image.FromFile(#"C:\Users\itapi\Desktop\1.png") as Bitmap;//generates a 32bppRgba bitmap;
Bitmap b = Image.FromFile(#"C:\Users\itapi\Desktop\2.png") as Bitmap;//
List<Rectangle> l1 = CodeImage(a, b);
int i = 0;
foreach (Rectangle rec in l1)
{
i++;
Bitmap tmp = b.Clone(rec, a.PixelFormat);
tmp.Save(i.ToString() + ".png");
}
But I'm not getting the exact rectangle.. I'm getting only half of that and sometimes even worse. I think something in the code's logic is wrong.
Code for #nico
private unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
List<Rectangle> rec = new List<Rectangle>();
var bmData1 = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
var bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
int bytesPerPixel = 3;
IntPtr scan01 = bmData1.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride1 = bmData1.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
bool[] visited = new bool[nWidth * nHeight];
byte* base1 = (byte*)scan01.ToPointer();
byte* base2 = (byte*)scan02.ToPointer();
for (int y = 0; y < nHeight; y += 5)
{
byte* p1 = base1;
byte* p2 = base2;
for (int x = 0; x < nWidth; x += 5)
{
if (!ArePixelsEqual(p1, p2, bytesPerPixel) && !(visited[x + nWidth * y]))
{
// fill the different area
int minX = x;
int maxX = x;
int minY = y;
int maxY = y;
var pt = new Point(x, y);
Stack<Point> toBeProcessed = new Stack<Point> ();
visited[x + nWidth * y] = true;
toBeProcessed.Push(pt);
while (toBeProcessed.Count > 0)
{
var process = toBeProcessed.Pop();
var ptr1 = (byte*)scan01.ToPointer() + process.Y * stride1 + process.X * bytesPerPixel;
var ptr2 = (byte*) scan02.ToPointer() + process.Y * stride2 + process.X * bytesPerPixel;
//Check pixel equality
if (ArePixelsEqual(ptr1, ptr2, bytesPerPixel))
continue;
//This pixel is different
//Update the rectangle
if (process.X < minX) minX = process.X;
if (process.X > maxX) maxX = process.X;
if (process.Y < minY) minY = process.Y;
if (process.Y > maxY) maxY = process.Y;
Point n;
int idx;
//Put neighbors in stack
if (process.X - 1 >= 0)
{
n = new Point(process.X - 1, process.Y);
idx = n.X + nWidth * n.Y;
if (!visited[idx])
{
visited[idx] = true;
toBeProcessed.Push(n);
}
}
if (process.X + 1 < nWidth)
{
n = new Point(process.X + 1, process.Y);
idx = n.X + nWidth * n.Y;
if (!visited[idx])
{
visited[idx] = true;
toBeProcessed.Push(n);
}
}
if (process.Y - 1 >= 0)
{
n = new Point(process.X, process.Y - 1);
idx = n.X + nWidth * n.Y;
if (!visited[idx])
{
visited[idx] = true;
toBeProcessed.Push(n);
}
}
if (process.Y + 1 < nHeight)
{
n = new Point(process.X, process.Y + 1);
idx = n.X + nWidth * n.Y;
if (!visited[idx])
{
visited[idx] = true;
toBeProcessed.Push(n);
}
}
}
if (((maxX - minX + 1) > 5) & ((maxY - minY + 1) > 5))
rec.Add(new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1));
}
p1 += 5 * bytesPerPixel;
p2 += 5 * bytesPerPixel;
}
base1 += 5 * stride1;
base2 += 5 * stride2;
}
bmp.UnlockBits(bmData1);
bmp2.UnlockBits(bmData2);
return rec;
}
I see a couple of problems with your code. If I understand it correctly, you
find a pixel that's different between the two images.
then you continue to scan from there to the right, until you find a position where both images are identical again.
then you scan from the last "different" pixel to the bottom, until you find a position where both images are identical again.
then you store that rectangle and start at the next line below it
Am I right so far?
Two obvious things can go wrong here:
If two rectangles have overlapping y-ranges, you're in trouble: You'll find the first rectangle fine, then skip to the bottom Y-coordinate, ignoring all the pixels left or right of the rectangle you just found.
Even if there is only one rectangle, you assume that every pixel on the rectangle's border is different, and all the other pixels are identical. If that assumption isn't valid, you'll stop searching too early, and only find parts of rectangles.
If your images come from a scanner or digital camera, or if they contain lossy compression (jpeg) artifacts, the second assumption will almost certainly be wrong. To illustrate this, here's what I get when I mark every identical pixel the two jpg images you linked black, and every different pixel white:
What you see is not a rectangle. Instead, a lot of pixels around the rectangles you're looking for are different:
That's because of jpeg compression artifacts. But even if you used lossless source images, pixels at the borders might not form perfect rectangles, because of antialiasing or because the background just happens to have a similar color in that region.
You could try to improve your algorithm, but if you look at that border, you will find all kinds of ugly counterexamples to any geometric assumptions you'll make.
It would probably be better to implement this "the right way". Meaning:
Either implement a flood fill algorithm that erases different pixels (e.g. by setting them to identical or by storing a flag in a separate mask), then recursively checks if the 4 neighbor pixels.
Or implement a connected component labeling algorithm, that marks each different pixel with a temporary integer label, using clever data structures to keep track which temporary labels are connected. If you're only interested in a bounding box, you don't even have to merge the temporary labels, just merge the bounding boxes of adjacent labeled areas.
Connected component labeling is in general a bit faster, but is a bit trickier to get right than flood fill.
One last advice: I would rethink your "no 3rd party libraries" policy if I were you. Even if your final product will contain no 3rd party libraries, development might by a lot faster if you used well-documented, well-tested, useful building blocks from a library, then replaced them one by one with your own code. (And who knows, you might even find an open source library with a suitable license that's so much faster than your own code that you'll stick with it in the end...)
ADD: In case you want to rethink your "no libraries" position: Here's a quick and simple implementation using AForge (which has a more permissive library than emgucv):
private static void ProcessImages()
{
(* load images *)
var img1 = AForge.Imaging.Image.FromFile(#"compare1.jpg");
var img2 = AForge.Imaging.Image.FromFile(#"compare2.jpg");
(* calculate absolute difference *)
var difference = new AForge.Imaging.Filters.ThresholdedDifference(15)
{OverlayImage = img1}
.Apply(img2);
(* create and initialize the blob counter *)
var bc = new AForge.Imaging.BlobCounter();
bc.FilterBlobs = true;
bc.MinWidth = 5;
bc.MinHeight = 5;
(* find blobs *)
bc.ProcessImage(difference);
(* draw result *)
BitmapData data = img2.LockBits(
new Rectangle(0, 0, img2.Width, img2.Height),
ImageLockMode.ReadWrite, img2.PixelFormat);
foreach (var rc in bc.GetObjectsRectangles())
AForge.Imaging.Drawing.FillRectangle(data, rc, Color.FromArgb(128,Color.Red));
img2.UnlockBits(data);
img2.Save(#"compareResult.jpg");
}
The actual difference + blob detection part (without loading and result display) takes about 43ms, for the second run (this first time takes longer of course, due to JITting, cache, etc.)
Result (the rectangle is larger due to jpeg artifacts):
Here is a flood-fill based version of your code. It checks every pixel for difference. If it finds a different pixel, it runs an exploration to find the entire different area.
The code is only meant as an illustration. There are certainly some points that could be improved.
unsafe bool ArePixelsEqual(byte* p1, byte* p2, int bytesPerPixel)
{
for (int i = 0; i < bytesPerPixel; ++i)
if (p1[i] != p2[i])
return false;
return true;
}
private static unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
if (bmp.PixelFormat != bmp2.PixelFormat || bmp.Width != bmp2.Width || bmp.Height != bmp2.Height)
throw new ArgumentException();
List<Rectangle> rec = new List<Rectangle>();
var bmData1 = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
var bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
int bytesPerPixel = Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
IntPtr scan01 = bmData1.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride1 = bmData1.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
bool[] visited = new bool[nWidth * nHeight];
byte* base1 = (byte*)scan01.ToPointer();
byte* base2 = (byte*)scan02.ToPointer();
for (int y = 0; y < nHeight; y++)
{
byte* p1 = base1;
byte* p2 = base2;
for (int x = 0; x < nWidth; ++x)
{
if (!ArePixelsEqual(p1, p2, bytesPerPixel) && !(visited[x + nWidth * y]))
{
// fill the different area
int minX = x;
int maxX = x;
int minY = y;
int maxY = y;
var pt = new Point(x, y);
Stack<Point> toBeProcessed = new Stack<Point>();
visited[x + nWidth * y] = true;
toBeProcessed.Push(pt);
while (toBeProcessed.Count > 0)
{
var process = toBeProcessed.Pop();
var ptr1 = (byte*)scan01.ToPointer() + process.Y * stride1 + process.X * bytesPerPixel;
var ptr2 = (byte*)scan02.ToPointer() + process.Y * stride2 + process.X * bytesPerPixel;
//Check pixel equality
if (ArePixelsEqual(ptr1, ptr2, bytesPerPixel))
continue;
//This pixel is different
//Update the rectangle
if (process.X < minX) minX = process.X;
if (process.X > maxX) maxX = process.X;
if (process.Y < minY) minY = process.Y;
if (process.Y > maxY) maxY = process.Y;
Point n; int idx;
//Put neighbors in stack
if (process.X - 1 >= 0)
{
n = new Point(process.X - 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.X + 1 < nWidth)
{
n = new Point(process.X + 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y - 1 >= 0)
{
n = new Point(process.X, process.Y - 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y + 1 < nHeight)
{
n = new Point(process.X, process.Y + 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
}
rec.Add(new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1));
}
p1 += bytesPerPixel;
p2 += bytesPerPixel;
}
base1 += stride1;
base2 += stride2;
}
bmp.UnlockBits(bmData1);
bmp2.UnlockBits(bmData2);
return rec;
}
You can achieve this easily using a flood fill segmentation algorithm.
First an utility class to make fast bitmap access easier. This will help to encapsulate the complex pointer-logic and make the code more readable:
class BitmapWithAccess
{
public Bitmap Bitmap { get; private set; }
public System.Drawing.Imaging.BitmapData BitmapData { get; private set; }
public BitmapWithAccess(Bitmap bitmap, System.Drawing.Imaging.ImageLockMode lockMode)
{
Bitmap = bitmap;
BitmapData = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), lockMode, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
}
public Color GetPixel(int x, int y)
{
unsafe
{
byte* dataPointer = MovePointer((byte*)BitmapData.Scan0, x, y);
return Color.FromArgb(dataPointer[3], dataPointer[2], dataPointer[1], dataPointer[0]);
}
}
public void SetPixel(int x, int y, Color color)
{
unsafe
{
byte* dataPointer = MovePointer((byte*)BitmapData.Scan0, x, y);
dataPointer[3] = color.A;
dataPointer[2] = color.R;
dataPointer[1] = color.G;
dataPointer[0] = color.B;
}
}
public void Release()
{
Bitmap.UnlockBits(BitmapData);
BitmapData = null;
}
private unsafe byte* MovePointer(byte* pointer, int x, int y)
{
return pointer + x * 4 + y * BitmapData.Stride;
}
}
Then a class representing a rectangle containing different pixels, to mark them in the resulting image. In general this class can also contain a list of Point instances (or a byte[,] map) to make indicating individual pixels in the resulting image possible:
class Segment
{
public int Left { get; set; }
public int Top { get; set; }
public int Right { get; set; }
public int Bottom { get; set; }
public Bitmap Bitmap { get; set; }
public Segment()
{
Left = int.MaxValue;
Right = int.MinValue;
Top = int.MaxValue;
Bottom = int.MinValue;
}
};
Then the steps of a simple algorithm are as follows:
find different pixels
use a flood-fill algorithm to find segments on the difference image
draw bounding rectangles for the segments found
The first step is the easiest one:
static Bitmap FindDifferentPixels(Bitmap i1, Bitmap i2)
{
var result = new Bitmap(i1.Width, i2.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
var ia1 = new BitmapWithAccess(i1, System.Drawing.Imaging.ImageLockMode.ReadOnly);
var ia2 = new BitmapWithAccess(i2, System.Drawing.Imaging.ImageLockMode.ReadOnly);
var ra = new BitmapWithAccess(result, System.Drawing.Imaging.ImageLockMode.ReadWrite);
for (int x = 0; x < i1.Width; ++x)
for (int y = 0; y < i1.Height; ++y)
{
var different = ia1.GetPixel(x, y) != ia2.GetPixel(x, y);
ra.SetPixel(x, y, different ? Color.White : Color.FromArgb(0, 0, 0, 0));
}
ia1.Release();
ia2.Release();
ra.Release();
return result;
}
And the second and the third steps are covered with the following three functions:
static List<Segment> Segmentize(Bitmap blackAndWhite)
{
var bawa = new BitmapWithAccess(blackAndWhite, System.Drawing.Imaging.ImageLockMode.ReadOnly);
var result = new List<Segment>();
HashSet<Point> queue = new HashSet<Point>();
bool[,] visitedPoints = new bool[blackAndWhite.Width, blackAndWhite.Height];
for (int x = 0;x < blackAndWhite.Width;++x)
for (int y = 0;y < blackAndWhite.Height;++y)
{
if (bawa.GetPixel(x, y).A != 0
&& !visitedPoints[x, y])
{
result.Add(BuildSegment(new Point(x, y), bawa, visitedPoints));
}
}
bawa.Release();
return result;
}
static Segment BuildSegment(Point startingPoint, BitmapWithAccess bawa, bool[,] visitedPoints)
{
var result = new Segment();
List<Point> toProcess = new List<Point>();
toProcess.Add(startingPoint);
while (toProcess.Count > 0)
{
Point p = toProcess.First();
toProcess.RemoveAt(0);
ProcessPoint(result, p, bawa, toProcess, visitedPoints);
}
return result;
}
static void ProcessPoint(Segment segment, Point point, BitmapWithAccess bawa, List<Point> toProcess, bool[,] visitedPoints)
{
for (int i = -1; i <= 1; ++i)
{
for (int j = -1; j <= 1; ++j)
{
int x = point.X + i;
int y = point.Y + j;
if (x < 0 || y < 0 || x >= bawa.Bitmap.Width || y >= bawa.Bitmap.Height)
continue;
if (bawa.GetPixel(x, y).A != 0 && !visitedPoints[x, y])
{
segment.Left = Math.Min(segment.Left, x);
segment.Right = Math.Max(segment.Right, x);
segment.Top = Math.Min(segment.Top, y);
segment.Bottom = Math.Max(segment.Bottom, y);
toProcess.Add(new Point(x, y));
visitedPoints[x, y] = true;
}
}
}
}
And the following program given your two images as arguments:
static void Main(string[] args)
{
Image ai1 = Image.FromFile(args[0]);
Image ai2 = Image.FromFile(args[1]);
Bitmap i1 = new Bitmap(ai1.Width, ai1.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Bitmap i2 = new Bitmap(ai2.Width, ai2.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (var g1 = Graphics.FromImage(i1))
using (var g2 = Graphics.FromImage(i2))
{
g1.DrawImage(ai1, Point.Empty);
g2.DrawImage(ai2, Point.Empty);
}
var difference = FindDifferentPixels(i1, i2);
var segments = Segmentize(difference);
using (var g1 = Graphics.FromImage(i1))
{
foreach (var segment in segments)
{
g1.DrawRectangle(Pens.Red, new Rectangle(segment.Left, segment.Top, segment.Right - segment.Left, segment.Bottom - segment.Top));
}
}
i1.Save("result.png");
Console.WriteLine("Done.");
Console.ReadKey();
}
produces the following result:
As you can see there are more differences between the given images. You can filter the resulting segments with regard to their size for example to drop the small artefacts. Also there is of course much work to do in terms of error checking, design and performance.
One idea is to proceed as follows:
1) Rescale images to a smaller size (downsample)
2) Run the above algorithm on smaller images
3) Run the above algorithm on original images, but restricting yourself only to rectangles found in step 2)
This can be of course extended to a multi-level hierarchical approach (using more different image sizes, increasing accuracy with each step).
Ah an algorithm challenge. Like! :-)
There are other answers here using f.ex. floodfill that will work just fine. I just noticed that you wanted something fast, so let me propose a different idea. Unlike the other people, I haven't tested it; it shouldn't be too hard and should be quite fast, but I simply don't have the time at the moment to test it myself. If you do, please share the results. Also, note that it's not a standard algorithm, so there are probably some bugs here and there in my explanation (and no patents).
My idea is derived from the idea of mean adaptive thresholding but with a lot of important differences. I cannot find the link from wikipedia anymore or my code, so I'll do this from the top of my mind. Basically you create a new (64-bit) buffer for both images and fill it with:
f(x,y) = colorvalue + f(x-1, y) + f(x, y-1) - f(x-1, y-1)
f(x,0) = colorvalue + f(x-1, 0)
f(0,y) = colorvalue + f(0, y-1)
The main trick is that you can calculate the sum value of a portion of the image fast, namely by:
g(x1,y1,x2,y2) = f(x2,y2)-f(x1-1,y2)-f(x2,y1-1)+f(x1-1,y1-1)
In other words, this will give the same result as:
result = 0;
for (x=x1; x<=x2; ++x)
for (y=y1; y<=y2; ++y)
result += f(x,y)
In our case this means that with only 4 integer operations this will get you some unique number of the block in question. I'd say that's pretty awesome.
Now, in our case, we don't really care about the average value; we just care about some sort-of unique number. If the image changes, it should change - simple as that. As for colorvalue, usually some gray scale number is used for thresholding - instead, we'll be using the complete 24-bit RGB value. Because there are only so few compares, we can simply scan until we find a block that doesn't match.
The basic algorithm that I propose works as follows:
for (y=0; y<height;++y)
for (x=0; x<width; ++x)
if (src[x,y] != dst[x,y])
if (!IntersectsWith(x, y, foundBlocks))
FindBlock(foundBlocks);
Now, IntersectsWith can be something like a quad tree of if there are only a few blocks, you can simply iterate through the blocks and check if they are within the bounds of the block. You can also update the x variable accordingly (I would). You can even balance things by re-building the buffer for f(x,y) if you have too many blocks (more precise: merge found blocks back from dst into src, then rebuild the buffer).
FindBlocks is where it gets interesting. Using the formula for g that's now pretty easy:
int x1 = x-1; int y1 = y-1; int x2 = x; int y2 = y;
while (changes)
{
while (g(srcimage,x1-1,y1,x1,y2) == g(dstimage,x1-1,y1,x1,y2)) { --x1; }
while (g(srcimage,x1,y1-1,x1,y2) == g(dstimage,x1,y1-1,x1,y2)) { --y1; }
while (g(srcimage,x1,y1,x1+1,y2) == g(dstimage,x1,y1,x1+1,y2)) { ++x1; }
while (g(srcimage,x1,y1,x1,y2+1) == g(dstimage,x1,y1,x1,y2+1)) { ++y1; }
}
That's it. Note that the complexity of the FindBlocks algorithm is O(x + y), which is pretty awesome for finding a 2D block IMO. :-)
As I said, let me know how it turns out.
I want detect circles in bitmap without a library. First, i used otsu thresold for binarization. After binarization i used laplace edge detection. Now there is a circle in my bitmap. How can i detect this circle.
NOTE: I tried to draw a circle and float x and y coordinates. But this way is so slowly and this way has radius issue. Because, if my tempCircle's radius=10 and real circle's radius=9 then my algorithm can not find the circle.
BitmapProcess bmpPro = new BitmapProcess((Bitmap)(pictureBox1.Image));
bmpPro.LockBits();
int black = 0, temp = 0,fixX=0,fixY=0;
Bitmap bmp = (Bitmap)pictureBox1.Image;
Graphics g = this.pictureBox1.CreateGraphics();
Pen pen = new Pen(Color.Red, 10);
int x = 0, y = 0;
for (int a = 0; a < pictureBox1.Image.Width - 1; a += 1)
{
for (int b = 0; b < pictureBox1.Image.Height - 1; b++)
{
double radius = 10;
temp = 0;
for (double i = 0.0; i < 360.0; i += 1)
{
double angle = i * System.Math.PI / 180;
x = (int)(a + radius * System.Math.Cos(angle));
y = (int)(b + radius * System.Math.Sin(angle));
Color aa = bmpPro.GetPixel(Math.Abs(x), Math.Abs(y));
if (bmpPro.GetPixel(Math.Abs(x), Math.Abs(y))!=Color.Black) temp++;
}
if (temp > black)
{
black = temp;
fixX = a;
fixY = b;
}
}
}
g.DrawEllipse(pen,fixX,fixY,50,50);
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace LockBitBitmap
{
class BitmapProcess
{
public int b = 0;
int TotalPixelLocked;
Bitmap source = null; //kaynak bmp
IntPtr Iptr = IntPtr.Zero; //baslangıc adresi
BitmapData bitmapData = null;
public byte[] Pixels { get; set; }
public int Depth { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public BitmapProcess(Bitmap source)
{
this.source = source; //bitmapı dısardan al
}
public void LockBits()
{
//resmin en ve boyunu al
Width = source.Width;
Height = source.Height;
//kilit için rectangle olustur
Rectangle rect = new Rectangle(0,0,Width,Height);
//kaynak bitmap ın pixel formatını al
Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);
//pixel basına bit sayısını bul(Bpp)
if (Depth != 8 && Depth != 24 && Depth != 32)
{
return; //bit türü desteklenmiyor...
}
//bitmapı kilitle ve veriyi döndür...
bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite, source.PixelFormat);
//kilitlenecek pixel sayısını al
TotalPixelLocked = Math.Abs(bitmapData.Stride) * source.Height;
//pixel verilerini tutmak için byte dizisi olustur
int step = Depth / 8;
Pixels = new byte[TotalPixelLocked * step];
Iptr = bitmapData.Scan0;
//verileri pointerden diziye aktar
Marshal.Copy(Iptr, Pixels, 0, TotalPixelLocked);
}
public Color GetPixel(int x, int y)
{
Color clr = Color.Empty; //boş renk
//renkli nesne sayısını al
int cCount = Depth / 8;
//istenen pixelin baslangıc adresini bul
int i = y * bitmapData.Stride + x * cCount;
if (i > (Pixels.Length - cCount))
{
throw new IndexOutOfRangeException("index out of range ( dizi adresi geçersiz)");
}
if (Depth == 32) //r g b a
{
byte b = Pixels[i];
byte g = Pixels[i + 1];
byte r = Pixels[i + 2];
byte a = Pixels[i + 3];
clr = Color.FromArgb(a,r,g,b);
}
if (Depth == 24) //r g b
{
byte b = Pixels[i];
byte g = Pixels[i + 1];
byte r = Pixels[i + 2];
clr = Color.FromArgb(r, g, b);
}
if (Depth == 8) // r g b hepsi aynı
{
byte c = Pixels[i];
clr = Color.FromArgb(c,c,c);
}
return clr;
}
public void SetPixel(int x, int y, Color color)
{
// renkli nesne sayısı
int cCount = Depth / 8;
// baslangıc indexini bul
//int i = ((y * Width) + x) * cCount;
int i = y * bitmapData.Stride + x * cCount;
if (i > (Pixels.Length - cCount))
{
throw new IndexOutOfRangeException("index out of range ( dizi adresi geçersiz)");
}
if (Depth == 32) // r,g,b, (alpha)
{
Pixels[i] = color.B;
Pixels[i + 1] = color.G;
Pixels[i + 2] = color.R;
Pixels[i + 3] = color.A;
}
if (Depth == 24) // r,g,b
{
Pixels[i] = color.B;
Pixels[i + 1] = color.G;
Pixels[i + 2] = color.R;
b++;
}
if (Depth == 8)//r g b hepsi aynı
{
Pixels[i] = color.B;
}
}
public Bitmap giveBitmap()
{
System.Runtime.InteropServices.Marshal.Copy(Pixels, 0, Iptr, TotalPixelLocked);
source.UnlockBits(bitmapData);
return source;
}
}
}
If you have only a few circles in the image, I'd recommend using RANSAC.
Brief explanation: From your edge pixels, pick 3 points at random and find the equation of the circle that fits these points. Use this equation on all your remaining edge points to see how many of them lie on the circle i.e. how many satisfy the equation. Repeat this process N times (N can be decided based on various factors, including the number of edge points, the number of circles, etc.), and record the iteration with the highest number of inliers (points lying on the circle). The equation corresponding to this maximal iteration is your best fit circle.
If you have more than one circle in the image, repeat the above steps after deleting or masking out all points that have fit previously found circles.
Why don't you have a look at a paper written by Yonghong Xie and Qiang Ji "A NEW EFFICIENT ELLIPSE DETECTION METHOD". link
I know it has ellipse in the title but as you for sure know a cricle is just very special ellipse ;).
Hint: Next step in your procesing should be clustering your edge pixels - this way you will know which pixels are creating an object. As we know objects have some properties and you should determine what object properties makes that object a circle - and then implement these properties-based filters in your code.
I'm doing some image processing and ran in to an exception.
Let me explain the logic process;
Resize the image to to a smaller size
Turn it grayscale
Threshold the image
Save it for use later on.
When you threshold the image, the constructor can take an int that sets the intensity of the filter. The best way I've found to get this "magic number" is to use a method called GetOtsuThreshold. It uses unsafe code but works well. However, something strange happens when you call that method. After you call the otsu method, it causes the Aforge...Threshold.ApplyInPlace() method to throw a Parameter is not valid exception. If you don't call it (When the code is commented out) the whole thing runs just fine though.
Wot's the deal?
EDIT: Found the problem; You must put a new a new image into the otsu method because it disposes of the image!!
using System;
using System.Drawing;
using System.Drawing.Imaging;
using AForge.Imaging.Filters;
namespace Puma.Ocr.Tests
{
class FormatImage
{
public static Bitmap _FullImageOfCoin;
public FormatImage(string path)
{
_FullImageOfCoin = ScaleImage(new Bitmap(path), 2000, 2000);
GrayscaleImage();
ThresholdImage();
}
private void GrayscaleImage()
{
Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);
// apply the filter
_FullImageOfCoin = filter.Apply(_FullImageOfCoin);
}
private void ThresholdImage()
{
//Causes the exception
Threshold threshold = new Threshold(getOtsuThreshold(_FullImageOfCoin));
//Runs fine
//Threshold threshold = new Threshold();
threshold.ApplyInPlace(_FullImageOfCoin);
_FullImageOfCoin.Save(#"C:\users\school\desktop\thresholded.bmp");
}
public static Bitmap ScaleImage(Bitmap image, int maxWidth, int maxHeight)
{
var ratioX = (double)maxWidth / image.Width;
var ratioY = (double)maxHeight / image.Height;
var ratio = Math.Min(ratioX, ratioY);
var newWidth = (int)(image.Width * ratio);
var newHeight = (int)(image.Height * ratio);
var newImage = new Bitmap(newWidth, newHeight);
Graphics.FromImage(newImage).DrawImage(image, 0, 0, newWidth, newHeight);
return newImage;
}
public int getOtsuThreshold(Bitmap bmp)
{
byte t = 0;
float[] vet = new float[256];
int[] hist = new int[256];
vet.Initialize();
float p1, p2, p12;
int k;
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
unsafe
{
byte* p = (byte*)(void*)bmData.Scan0.ToPointer();
getHistogram(p, bmp.Width, bmp.Height, bmData.Stride, hist);
for (k = 1; k != 255; k++)
{
p1 = Px(0, k, hist);
p2 = Px(k + 1, 255, hist);
p12 = p1 * p2;
if (p12 == 0)
p12 = 1;
float diff = (Mx(0, k, hist) * p2) - (Mx(k + 1, 255, hist) * p1);
vet[k] = (float)diff * diff / p12;
}
}
bmp.UnlockBits(bmData);
t = (byte)findMax(vet, 256);
bmp.Dispose();
return t;
}
private unsafe void getHistogram(byte* p, int w, int h, int ws, int[] hist)
{
hist.Initialize();
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w * 3; j += 3)
{
int index = i * ws + j;
hist[p[index]]++;
}
}
}
private int findMax(float[] vec, int n)
{
float maxVec = 0;
int idx = 0;
int i;
for (i = 1; i <= n - 1; i++)
{
if (vec[i] > maxVec)
{
maxVec = vec[i];
idx = i;
}
}
return idx;
}
private float Px(int init, int end, int[] hist)
{
int sum = 0;
int i;
for (i = init; i <= end; i++)
sum += hist[i];
return (float)sum;
}
// function is used to compute the mean values in the equation (mu)
private float Mx(int init, int end, int[] hist)
{
int sum = 0;
int i;
for (i = init; i <= end; i++)
sum += i * hist[i];
return (float)sum;
}
}
}
A few ideas:
Use debugging and follow this method step by step to see if it is well working
Your getOtsuThreshold(Bitmap bmp) is returning an int, but the variable t returned is a byte: try to cast the value?
If the int returned by getOtsuThreshold is okay, check that the value is in the good range provided the API info (http://www.aforgenet.com/framework/docs/html/503a43b9-d98b-a19f-b74e-44767916ad65.htm):
Since the filter can be applied as to 8 bpp and to 16 bpp images, the
ThresholdValue value should be set appropriately to the pixel format.
In the case of 8 bpp images the threshold value is in the [0, 255]
range, but in the case of 16 bpp images the threshold value is in the
[0, 65535] range.
Alright, found the answer. By putting the _Fullimage of coin into the otsu method it stripped the variable of all it properties. I don't know how, but by putting a new Bitmap into the otsu method it fixed the problem.
After spending much time researching about this on Google, I could not come across an example of converting a Wbmp image to Png format in C#
I have download some Wbmp images from the internet and I am viewing them using a Binary Editor.
Does anyone have an algorithm that will assist me in doing so or any code will also help.
Things I know so far:
First byte is type* (0 for monochrome images)
Second byte is called a “fixed-header” and is 0
Third byte is the width of the image in pixels*
Fourth byte is the height of the image in pixels*
Data bytes arranged in rows – one bit per pixel:
Where the row length is not divisible by 8, the row is 0-padded to
the byte boundary
I am fully lost so any help will be appreciated
Some of the other code:
using System.Drawing;
using System.IO;
class GetPixel
{
public static void Main(string[] args)
{
foreach ( string s in args )
{
if (File.Exists(s))
{
var image = new Bitmap(s);
Color p = image.GetPixel(0, 0);
System.Console.WriteLine("R: {0} G: {1} B: {2}", p.R, p.G, p.B);
}
}
}
}
And
class ConfigChecker
{
public static void Main()
{
string drink = "Nothing";
try
{
System.Configuration.AppSettingsReader configurationAppSettings
= new System.Configuration.AppSettingsReader();
drink = ((string)(configurationAppSettings.GetValue("Drink", typeof(string))));
}
catch ( System.Exception )
{
}
System.Console.WriteLine("Drink: " + drink);
} // Main
} // class ConfigChecker
Process :
Did research on Wbmp
Open up X.wbmp to check details first
Work out how you find the width and height of the WBMP file (so that you can later write the code). Note that the simplest way to convert a collection of length bytes (once the MSB is cleared) is to treat the entity as base-128.
Look at sample code I updated.
I am trying to create an empty Bitmap object and set its width and height to what we worked out in (3)
For every bit of data, will try and do a SetPixel on the Bitmap object created.
Padded 0s when the WBMP width is not a multiple of 8.
Save Bitmap using the Save() method.
Example usage of the application. It is assumed that the application is called Wbmp2Png. In command line:
Wbmp2Png IMG_0001.wbmp IMG_0002.wbmp IMG_0003.wbmp
The application converts each of IMG_0001.wbmp, IMG_0002.wbmp, and IMG_0003.wbmp to PNG files.
This seems to get the job done.
using System.Drawing;
using System.IO;
namespace wbmp2png
{
class Program
{
static void Main(string[] args)
{
foreach (string file in args)
{
if (!File.Exists(file))
{
continue;
}
byte[] data = File.ReadAllBytes(file);
int width = 0;
int height = 0;
int i = 2;
for (; data[i] >> 7 == 1; i++)
{
width = (width << 7) | (data[i] & 0x7F);
}
width = (width << 7) | (data[i++] & 0x7F);
for (; data[i] >> 7 == 1; i++)
{
height = (height << 7) | (data[i] & 0x7F);
}
height = (height << 7) | (data[i++] & 0x7F);
int firstPixel = i;
Bitmap png = new Bitmap(width, height);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
png.SetPixel(x, y, (((data[firstPixel + (x / 8) + (y * ((width - 1) / 8 + 1))] >> (7 - (x % 8))) & 1) == 1) ? Color.White : Color.Black);
}
}
png.Save(Path.ChangeExtension(file, "png"));
}
}
}
}
Try this code, this works!
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
class WBmpConvertor
{
public void Convert(string inputFile, string outputFile, ImageFormat format)
{
byte[] datas = File.ReadAllBytes(inputFile);
byte tmp;
int width = 0, height = 0, offset = 2;
do
{
tmp = datas[offset++];
width = (width << 7) | (tmp & 0x7f);
} while ((tmp & 0x80) != 0);
do
{
tmp = datas[offset++];
height = (height << 7) | (tmp & 0x7f);
} while ((tmp & 0x80) != 0);
var bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height),
ImageLockMode.WriteOnly, bmp.PixelFormat);
int stride = (width + 7) >> 3;
var tmpdata = new byte[height * width];
for (int i = 0; i < height; i++)
{
int pos = stride * i;
byte mask = 0x80;
for (int j = 0; j < width; j++)
{
if ((datas[offset + pos] & mask) == 0)
tmpdata[i * width + j] = 0;
else
tmpdata[i * width + j] = 0xff;
mask >>= 1;
if (mask == 0)
{
mask = 0x80;
pos++;
}
}
}
Marshal.Copy(tmpdata, 0, bd.Scan0, tmpdata.Length);
bmp.UnlockBits(bd);
bmp.Save(outputFile, format);
}
}