distance/similarity between 2 bitmap images in C# - c#

I came across the following code taken from here:
using System;
using System.Drawing;
class Program
{
static void Main()
{
Bitmap img1 = new Bitmap("Lenna50.jpg");
Bitmap img2 = new Bitmap("Lenna100.jpg");
if (img1.Size != img2.Size)
{
Console.Error.WriteLine("Images are of different sizes");
return;
}
float diff = 0;
for (int y = 0; y < img1.Height; y++)
{
for (int x = 0; x < img1.Width; x++)
{
diff += (float)Math.Abs(img1.GetPixel(x, y).R - img2.GetPixel(x, y).R) / 255;
diff += (float)Math.Abs(img1.GetPixel(x, y).G - img2.GetPixel(x, y).G) / 255;
diff += (float)Math.Abs(img1.GetPixel(x, y).B - img2.GetPixel(x, y).B) / 255;
}
}
Console.WriteLine("diff: {0} %", 100 * diff / (img1.Width * img1.Height * 3));
}
}
Unfortunately, this is really slow. Is anyone aware of a faster way of calculating the distance between 2 images? Thanks!
To provide some more context as well. I am working on something like this:
https://rogerjohansson.blog/2008/12/07/genetic-programming-evolution-of-mona-lisa/
I evolve SVGs which are then translated into a Bitmap and compared to the target image.
Just came across the aforgenet library - see for example:
enter link description here
PS:
I start to rewrite the above using LockBits. The code below is my current attempt but I am a bit stuck. Please note, that bmp1 is the 'target picture' and does not really change - hence the copying can be factored out/only needs to be done once. The Bitmap bmp2 is passed in and to be compared with bmp1 (there are 100s of bmp2s). Ultimately, I would like to determine the similarity between bmp1 and bmp2 using some distance (e.g. Euclidean distance of bytes?). Any pointers regarding this and how to speed the code up would be very much appreciated. Thanks!
public double Compare(Bitmap bmp1, Bitmap bmp2)
{
BitmapData bitmapData1 = bmp1.LockBits(new Rectangle(0, 0, bmp1.Width, bmp1.Height), ImageLockMode.ReadWrite, bmp1.PixelFormat);
BitmapData bitmapData2 = bmp2.LockBits(new Rectangle(0, 0, bmp2.Width, bmp2.Height), ImageLockMode.ReadWrite, bmp2.PixelFormat);
IntPtr ptr1 = bitmapData1.Scan0;
int bytes1 = bitmapData1.Stride * bmp1.Height;
byte[] rgbValues1 = new byte[bytes1];
byte[] r1 = new byte[bytes1 / 3];
byte[] g1 = new byte[bytes1 / 3];
byte[] b1 = new byte[bytes1 / 3];
Marshal.Copy(ptr1, rgbValues1, 0, bytes1);
bmp1.UnlockBits(bitmapData1);
IntPtr ptr2 = bitmapData2.Scan0;
int bytes2 = bitmapData2.Stride * bmp2.Height;
byte[] rgbValues2 = new byte[bytes2];
byte[] r2 = new byte[bytes2 / 3];
byte[] g2 = new byte[bytes2 / 3];
byte[] b2 = new byte[bytes2 / 3];
Marshal.Copy(ptr2, rgbValues2, 0, bytes2);
bmp2.UnlockBits(bitmapData2);
int stride = bitmapData1.Stride;
for (int column = 0; column < bitmapData1.Height; column++)
{
for (int row = 0; row < bitmapData1.Width; row++)
{
//????
}
}
return 0;
}
PPS:
I (think I) made some more progress. The following code seems to work:
using System.Drawing;
using System.Drawing.Imaging;
using Color = System.Drawing.Color;
namespace ClassLibrary1
{
public unsafe class BitmapComparer : IBitmapComparer
{
private readonly Color[] _targetBitmapColors;
private readonly int _width;
private readonly int _height;
private readonly int _maxPointerLength;
private readonly PixelFormat _pixelFormat;
public BitmapComparer(Bitmap targetBitmap)
{
_width = targetBitmap.Width;
_height = targetBitmap.Height;
_maxPointerLength = _width * _height;
_pixelFormat = targetBitmap.PixelFormat;
_targetBitmapColors = new Color[_maxPointerLength];
var bData = targetBitmap.LockBits(new Rectangle(0, 0, _width, _height), ImageLockMode.ReadWrite, _pixelFormat);
var scan0 = (byte*) bData.Scan0.ToPointer();
for (var i = 0; i < _maxPointerLength; i += 4)
{
_targetBitmapColors[i] = Color.FromArgb(scan0[i + 2], scan0[i + 1], scan0[i + 0]);
}
targetBitmap.UnlockBits(bData);
}
// https://rogerjohansson.blog/2008/12/09/genetic-programming-mona-lisa-faq/
public double Compare(Bitmap bitmapToCompareWith)
{
var bData = bitmapToCompareWith.LockBits(new Rectangle(0, 0, _width, _height), ImageLockMode.ReadWrite, _pixelFormat);
var scan0 = (byte*) bData.Scan0.ToPointer();
double distance = 0;
for (var i = 0; i < _maxPointerLength; i += 4)
{
distance +=
( ((_targetBitmapColors[i].R - scan0[i + 2]) * (_targetBitmapColors[i].R - scan0[i + 2]))
+ ((_targetBitmapColors[i].G - scan0[i + 1]) * (_targetBitmapColors[i].G - scan0[i + 1]))
+ ((_targetBitmapColors[i].B - scan0[i + 0]) * (_targetBitmapColors[i].B - scan0[i + 0])));
}
bitmapToCompareWith.UnlockBits(bData);
return distance;
}
}
}

Using all pixels always will be time-consuming. What if you use a randomly selected pixels sample of the images. Also, you can apply hierarchical image granularity. In this way, you will get more information about the details presented in the images.
I am also working on a similar project. It is available in GitHub under the name Ellipses-Image-Approximator.
Something like this:
package eu.veldsoft.ellipses.image.approximator;
import java.awt.image.BufferedImage;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
/**
* Compare to raster images by using Euclidean distance between the pixels but
* in probabilistic sampling on hierarchical image detailization.
*
* #author Todor Balabanov
*/
class HierarchicalProbabilisticImageComparator implements ImageComparator {
/** A pseudo-random number generator instance. */
private static final Random PRNG = new Random();
/**
* Euclidean distance color comparator instance.
*/
private static final ColorComparator EUCLIDEAN = new EuclideanColorComparator();
/** Recursive descent depth level. */
private int depthLevel = 1;
/**
* Size of the sample in percentages from the size of the population (from
* 0.0 to 1.0).
*/
private double samplePercent = 0.1;
/** A supportive array for the first image pixels. */
private int aPixels[] = null;
/** A supportive array for the second image pixels. */
private int bPixels[] = null;
/**
* Constructor without parameters for default members' values.
*/
public HierarchicalProbabilisticImageComparator() {
this(1, 0.1);
}
/**
* Constructor with all parameters.
*
* #param depthLevel
* Recursive descent depth level.
* #param samplePercent
* Size of the sample in percentages from the size of the
* population (from 0.0 to 1.0).
*/
public HierarchicalProbabilisticImageComparator(int depthLevel,
double samplePercent) {
super();
this.depthLevel = depthLevel;
this.samplePercent = samplePercent;
}
private double distance(int width, int level, int minX, int minY, int maxX,
int maxY) {
/*
* At the bottom of the recursive descent, distance is zero, and
* descending is canceled.
*/
if (level > depthLevel) {
return 0;
}
/* Rectangle's boundaries should be observed. */
if (maxX <= minX || maxY <= minY) {
return 0;
}
/*
* Sample size calculated according formula.
*
* https://www.surveymonkey.com/mp/sample-size-calculator/
*/
int sampleSize = (int) ((maxX - minX) * (maxY - minY) * samplePercent);
/* Generate unique indices of pixels with the size of the sample. */
Set<Integer> indices = new HashSet<Integer>();
while (indices.size() < sampleSize) {
int x = minX + PRNG.nextInt(maxX - minX + 1);
int y = minY + PRNG.nextInt(maxY - minY + 1);
indices.add(y * width + x);
}
/* The Euclidean distance of the randomly selected pixels. */
double sum = 0;
for (int index : indices) {
sum += EUCLIDEAN.distance(aPixels[index], bPixels[index]);
}
/* Do a recursive descent. */
return (sum / sampleSize) * level
+ distance(width, level + 1, minX, minY,
maxX - (maxX - minX) / 2, maxY - (maxY - minY) / 2)
+ distance(width, level + 1, maxX - (maxX - minX) / 2, minY,
maxX, maxY - (maxY - minY) / 2)
+ distance(width, level + 1, minX, maxY - (maxY - minY) / 2,
maxX - (maxX - minX) / 2, maxY)
+ distance(width, level + 1, maxX - (maxX - minX) / 2,
maxY - (maxY - minY) / 2, maxX, maxY);
}
/**
* {#inheritDoc}
*/
#Override
public double distance(BufferedImage a, BufferedImage b) {
if (a.getWidth() != b.getWidth()) {
throw new RuntimeException("Images width should be identical!");
}
if (a.getHeight() != b.getHeight()) {
throw new RuntimeException("Images height should be identical!");
}
aPixels = a.getRGB(0, 0, a.getWidth(), a.getHeight(), null, 0,
a.getWidth());
bPixels = b.getRGB(0, 0, b.getWidth(), b.getHeight(), null, 0,
b.getWidth());
/* Do a recursive calculation. */
return distance(Math.min(a.getWidth(), b.getWidth()), 1, 0, 0,
Math.min(a.getWidth() - 1, b.getWidth() - 1),
Math.min(a.getHeight() - 1, b.getHeight() - 1));
}
}

As others have pointed out, you can use BitMap.LockBits and use pointers instead of GetPixel. The following runs about 200 times faster than the original approach:
static float CalculateDifference(Bitmap bitmap1, Bitmap bitmap2)
{
if (bitmap1.Size != bitmap2.Size)
{
return -1;
}
var rectangle = new Rectangle(0, 0, bitmap1.Width, bitmap1.Height);
BitmapData bitmapData1 = bitmap1.LockBits(rectangle, ImageLockMode.ReadOnly, bitmap1.PixelFormat);
BitmapData bitmapData2 = bitmap2.LockBits(rectangle, ImageLockMode.ReadOnly, bitmap2.PixelFormat);
float diff = 0;
var byteCount = rectangle.Width * rectangle.Height * 3;
unsafe
{
// scan to first byte in bitmaps
byte* pointer1 = (byte*)bitmapData1.Scan0.ToPointer();
byte* pointer2 = (byte*)bitmapData2.Scan0.ToPointer();
for (int x = 0; x < byteCount; x++)
{
diff += (float)Math.Abs(*pointer1 - *pointer2) / 255;
pointer1++;
pointer2++;
}
}
bitmap1.UnlockBits(bitmapData1);
bitmap2.UnlockBits(bitmapData2);
return 100 * diff / byteCount;
}

Related

How to scan two images for differences?

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.

Cudafy.NET - CUDA.NET exception ErrorInvalidValue

I am using Cudafy.NET to perform image processing on the GPU. I have set up my class along with the first function (PerformBarrelCorrection running on the CPU) to set up the multiple threads to perform the logic calculation for each pixel in the image.
However every time I launch the function on the GPU it throws an exception:
An exception of type 'Cudafy.Host.CudafyHostException' occurred in
Cudafy.NET.dll but was not handled in user code
Additional information: CUDA.NET exception: ErrorInvalidValue.
Here is the class in its entirety commented to show the line on which the exception is thrown.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Cudafy;
using Cudafy.Host;
using Cudafy.Translator;
using System.Drawing;
using System.IO;
namespace AForgeTrial
{
class GPUProcessing
{
public CudafyModule km;
public GPGPU gpu;
public GPUProcessing() {
km = CudafyTranslator.Cudafy();
gpu = CudafyHost.GetDevice(CudafyModes.Target, CudafyModes.DeviceId);
gpu.LoadModule(km);
}
public Bitmap PerformBarrelCorrection(Bitmap source, double strength, float zoom, int imageWidth, int imageHeight)
{
byte[] bmp = ImageToByteArray2(source);
int halfWidth = imageWidth / 2;
int halfHeight = imageHeight / 2;
double correctionRadius = Math.Sqrt(((imageWidth * imageWidth) + (imageHeight * imageHeight)) / strength);
byte[] dev_src_bmp = gpu.CopyToDevice(bmp);
byte[] dst_bmp = new byte[bmp.Length];
byte[] dev_dst_bmp = gpu.Allocate<byte>(dst_bmp);
double[] correctPass = new double[1];
correctPass[0] = correctionRadius;
double[] dev_correctionRadius = gpu.CopyToDevice<double>(correctPass);
float[] zoomPass = new float[1];
zoomPass[0] = zoom;
float[] dev_zoom = gpu.CopyToDevice<float>(zoomPass);
int[] halfWidthPass = new int[1];
halfWidthPass[0] = halfWidth;
int[] dev_halfWidth = gpu.CopyToDevice<int>(halfWidthPass);
int[] halfHeightPass = new int[1];
halfHeightPass[0] = imageHeight;
int[] dev_halfHeight = gpu.CopyToDevice<int>(halfHeightPass);
//int blksize = ((bmp.Length / 3) / 128) + 1;
// EXCEPTION HAPPENS ON THE LINE BELOW
gpu.Launch((bmp.Length / 3), 1).BarrelCorrectionSingleOperation(dev_src_bmp, dev_dst_bmp, dev_correctionRadius, dev_zoom, dev_halfWidth, dev_halfHeight);
gpu.CopyFromDevice(dev_dst_bmp, dst_bmp);
// Convert dst_bmp to Bitmap and return it
Bitmap result;
using (MemoryStream ms = new MemoryStream(dst_bmp))
{
result = new Bitmap(ms);
}
return result;
}
[Cudafy]
public static void BarrelCorrectionSingleOperation(GThread thread, byte[] src_bmp, byte[] dst_bmp, double[] correctionRadius, float[] zoom, int[] halfWidth, int[] halfHeight)
{
// Move a single byte from source to destination or fill if required
}
public static byte[] ImageToByteArray(Bitmap img)
{
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(img, typeof(byte[]));
}
public static byte[] ImageToByteArray2(Bitmap img)
{
byte[] byteArray = new byte[0];
using (MemoryStream stream = new MemoryStream())
{
img.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
stream.Close();
byteArray = stream.ToArray();
}
return byteArray;
}
public void Close() {
gpu.FreeAll();
}
}
}
Anyone know anything about this?? Thanks in advance.
Solved my own question for anyone interested although its very niche, my code was full of holes.. This successfully performs real-time lens correction using Cudafy.
public void PerformBarrelCorrection(double strength, float zoom)
{
CheckIsSet();
int halfWidth = Width / 2;
int halfHeight = Height / 2;
double correctionRadius = Math.Sqrt(((Width * Width) + (Height * Height)) / strength);
_gpu.Launch(Width, Height).BarrelCorrectionSingleOperation(_gdata.SourceImage, _gdata.ResultImage, correctionRadius, zoom, halfWidth, halfHeight);
}
[Cudafy]
public static void BarrelCorrectionSingleOperation(GThread thread, byte[] src_bmp, byte[] dst_bmp, double correctionRadius, float zoom, int halfWidth, int halfHeight)
{
// Move a single byte from source to destination or fill if required
int x = thread.blockIdx.x;
int y = thread.threadIdx.x;
int newX = x - halfWidth;
int newY = y - halfHeight;
double distance = Math.Sqrt((newX * newX) + (newY * newY));
double r = distance / correctionRadius;
double theta;
if (r == 0)
{
theta = 1;
}
else
{
theta = Math.Atan(r) / r;
}
int sourceX = (int)(halfWidth + theta * newX * zoom);
int sourceY = (int)(halfHeight + theta * newY * zoom);
dst_bmp[(y * ((halfWidth * 2) * 4)) + (x * 4)] = src_bmp[(sourceY * ((halfWidth * 2) * 4)) + (sourceX * 4)];
dst_bmp[(y * (((halfWidth * 2) * 4)) + (x * 4)) + 1] = src_bmp[((sourceY * ((halfWidth * 2) * 4)) + (sourceX * 4)) + 1];
dst_bmp[(y * (((halfWidth * 2) * 4)) + (x * 4)) + 2] = src_bmp[((sourceY * ((halfWidth * 2) * 4)) + (sourceX * 4)) + 2];
dst_bmp[(y * (((halfWidth * 2) * 4)) + (x * 4)) + 3] = src_bmp[((sourceY * ((halfWidth * 2) * 4)) + (sourceX * 4)) + 3];
}

How to add noise to image or convert it to 24bpp?

Noob needs help!
I have an image and I need to add noise on it. I tried using AForge libs to do it but this method works only with 24bpp bitmaps and I get something different after resizing. The question is how to convert a bitmap to 24bpp or how to add noise on it? Maybe there are some libs for making this easier.
Resizing:
private Image Fit(Image image)
{
Image img = image;
if (filepath != null)
{
if (img.Width > pictureBox1.Width)
{
double op = ((pictureBox1.Width - (pictureBox1.Width % 100)) % 100) + (pictureBox1.Width % 100) * 0.01;
double percent = img.Width / (pictureBox1.Width * 0.01);
double temp = ((percent - percent % 100 + 100) - percent) * pictureBox1.Height * 0.01;
double height = pictureBox1.Height * 0.01 * ((percent - percent % 100 + 100) - percent);
System.Drawing.Size sz = new Size(pictureBox1.Width, (int)height);
img = resizeImage(img, sz);
}
if (img.Height > pictureBox1.Height)
{
double percent = img.Height / (pictureBox1.Height * 0.01);
double temp = ((percent - percent % 100 + 100) - percent) * pictureBox1.Width * 0.01;
double width = pictureBox1.Width * 0.01 * ((percent - percent % 100 + 100) - percent);
System.Drawing.Size sz = new Size((int)width, pictureBox1.Height);
img = resizeImage(img, sz);
}
}
return img;
}
P.S.> I have a type of bug - system totally refuses to divide 1 by 100 so I had to multiply 1 by 0.01 or I get 0.
Resizing the image to maintain aspect ratio is pretty easy. You compute the horizontal and vertical scaling factors, and then select the smallest of the two.
double vscale = 1.0;
double hscale = 1.0;
if (img.Width > pictureBox1.Width)
{
hscale = (double)pictureBox1.Width/img.Width;
}
if (img.Height > pictureBox1.Height)
{
vscale = (double)pictureBox1.Height/img.Height;
}
double scale = Math.Min(hscale, vscale);
double width = scale * img.Width;
double height = scale * img.Height;
Size sz = new Size((int)width, (int)height);
img = resizeImage(img, sz)
Note that this only scales if the image is larger than the box. It won't zoom the image to make it fit the box if the image is smaller than the box.
Haven't found anything good. That's how I've solved it:
public void GenerateNoise(Image img, int intense)
{
Bitmap finalBmp = img as Bitmap;
Random r = new Random();
int width = img.Width;
int height = img.Height;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
int def = r.Next(0, 100);
if (def < intense)
{
int op = r.Next(0, 1);
if (op == 0)
{
int num = r.Next(0, intense);
Color clr = finalBmp.GetPixel(x, y);
int R = (clr.R + clr.R + num)/2;
if (R > 255) R = 255;
int G = (clr.G + clr.G + num) / 2;
if (G > 255) G = 255;
int B = (clr.B + clr.B + num) / 2;
if (B > 255) B = 255;
Color result = Color.FromArgb(255, R, G, B);
finalBmp.SetPixel(x, y, result);
}
else
{
int num = r.Next(0, intense);
Color clr = finalBmp.GetPixel(x, y);
Color result = Color.FromArgb(255, (clr.R + clr.R - num) / 2, (clr.G + clr.G - num) / 2,
(clr.B + clr.B - num) / 2);
finalBmp.SetPixel(x, y, result);
}
}
}
}
}

How to speed up my color thresholding in C#

I'm working on tracking objects based on color and I was using EmguCV library to threshold my color image to binary black and white image. Thresholding itself was quite fast, 50 ms on 320x240 image. I'm using RG Chromaticity color space, so there are some necessarily calculations.
Now I'm trying to speed it up using pointers, but the result is very similar with what I did with emguCV (around 50 ms per image).
I want to ask, if there is some expert who can help me, what I am doing wrong. Here is my short code snippet of my color thresholding implementation. It's based on this one: https://web.archive.org/web/20140906075741/http://bobpowell.net/onebit.aspx.
public static Bitmap ThresholdRGChroma(Bitmap original, double angleMin,
double angleMax, double satMin, double satMax)
{
Bitmap bimg = new Bitmap(original.Width, original.Height, PixelFormat.Format1bppIndexed);
BitmapData imgData = original.LockBits(new Rectangle(0, 0, original.Width, original.Height), ImageLockMode.ReadOnly, original.PixelFormat);
BitmapData bimgData = bimg.LockBits(new Rectangle(0, 0, bimg.Width, bimg.Height), ImageLockMode.ReadWrite, bimg.PixelFormat);
int pixelSize = 3;
double r, g, angle, sat;
unsafe
{
byte* R, G, B;
byte* row;
int RGBSum;
for (int y = original.Height - 1; y >= 0; y--)
{
row = (byte*)imgData.Scan0 + (y * imgData.Stride);
for (int x = original.Width - 1; x >= 0; x--)
{
// get rgb values
B = &row[x * pixelSize];
G = &row[x * pixelSize + 1];
R = &row[x * pixelSize + 2];
RGBSum = *R + *G + *B;
if (RGBSum == 0)
{
SetIndexedPixel(x, y, bimgData, false);
continue;
}
//calculate r ang g for rg chroma color space
r = (double)*R / RGBSum;
g = (double)*G / RGBSum;
//and angle and saturation
angle = GetAngleRad(r, g) * (180.0 / Math.PI);
sat = Math.Sqrt(Math.Pow(g, 2) + Math.Pow(r, 2));
//conditions to set pixel black or white
if ((angle >= angleMin && angle <= angleMax) && (sat >= satMin && sat <= satMax))
SetIndexedPixel(x, y, bimgData, true);
else
SetIndexedPixel(x, y, bimgData, false);
}
}
}
bimg.UnlockBits(bimgData);
original.UnlockBits(imgData);
return bimg;
}
private unsafe static void SetIndexedPixel(int x, int y, BitmapData bmd, bool pixel)
{
int index = y * bmd.Stride + (x >> 3);
byte* p = (byte*)bmd.Scan0.ToPointer();
byte mask = (byte)(0x80 >> (x & 0x7));
if (pixel)
p[index] |= mask;
else
p[index] &= (byte)(mask ^ 0xff);
}
private static double GetAngleRad(double x, double y)
{
if (x - _rgChromaOriginX == 0)
return 0.0;
double angle = Math.Atan((y - _rgChromaOriginY) / (x - _rgChromaOriginX)); // 10ms
if (x < _rgChromaOriginX && y > _rgChromaOriginY)
angle = angle + Math.PI;
else if (x < _rgChromaOriginX && y < _rgChromaOriginY)
angle = angle + Math.PI;
else if (x > _rgChromaOriginX && y < _rgChromaOriginY)
angle = angle + 2 * Math.PI;
return angle;
}
You're doing a lot of unnecessary math for each pixel, calculating exact values only to check to see if they're inside some limits. You can simplify the comparisons by precomputing some adjustments to the limits.
The easiest substitution is for the saturation. You're doing a square root which you can avoid by squaring the limits instead.
double satMin2 = satMin*satMin;
double satMax2 = satMax*satMax;
// ...
sat2 = g*g + r*r;
//conditions to set pixel black or white
if ((angle >= angleMin && angle <= angleMax) && (sat2 >= satMin2 && sat <= satMax2))
A similar trick can be used with the angle. Rather than calculating the angle with Math.Atan, figure out what those limits equate to in your r and g ranges.
For completeness, here is modified version of thresholding image in RG Chromaticity color space it's more than 2 times faster than the version in my Question.
public static Bitmap ThresholdRGChroma(Bitmap original, Rectangle roi, double angle,
double width, double satMin, double satMax)
{
Bitmap bimg = new Bitmap(original.Width, original.Height, PixelFormat.Format1bppIndexed);
BitmapData imgData = original.LockBits(new Rectangle(0, 0, original.Width, original.Height), ImageLockMode.ReadOnly, original.PixelFormat);
BitmapData bimgData = bimg.LockBits(new Rectangle(0, 0, bimg.Width, bimg.Height), ImageLockMode.ReadWrite, bimg.PixelFormat);
int pixelSize = 3;
double r, g, sat, m;
double satMin2 = satMin * satMin;
double satMax2 = satMax * satMax;
double cr = Math.Sin((2 * Math.PI * angle) / 360.0);
double cg = Math.Cos((2 * Math.PI * angle) / 360.0);
// Instead of (Math.Cos(2 * width / 180.0) + 1) / 2.0 I'm using pre-calculated <1; 0> values.
double w2 = -width;
unsafe
{
byte* R, G, B;
byte* row;
int RGBSum;
for (int y = original.Height - 1; y >= 0; y--)
{
row = (byte*)imgData.Scan0 + (y * imgData.Stride);
for (int x = original.Width - 1; x >= 0; x--)
{
B = &row[x * pixelSize];
G = &row[x * pixelSize + 1];
R = &row[x * pixelSize + 2];
RGBSum = *R + *G + *B;
if (RGBSum == 0)
{
SetIndexedPixel(x, y, bimgData, false);
continue;
}
r = (double)*R / RGBSum - _rgChromaOriginX;
g = (double)*G / RGBSum - _rgChromaOriginY;
m = cr * r + cg * g;
sat = r * r + g * g;
if (m > 0 && m * m > w2 * w2 * sat && sat >= satMin2 && sat <= satMax2)
SetIndexedPixel(x, y, bimgData, true);
else
SetIndexedPixel(x, y, bimgData, false);
}
}
}
bimg.UnlockBits(bimgData);
original.UnlockBits(imgData);
return bimg;
}

Open source C# code to present wave form?

Is there any open source C# code or library to present a graphical waveform given a byte array?
This is as open source as it gets:
public static void DrawNormalizedAudio(ref float[] data, PictureBox pb,
Color color)
{
Bitmap bmp;
if (pb.Image == null)
{
bmp = new Bitmap(pb.Width, pb.Height);
}
else
{
bmp = (Bitmap)pb.Image;
}
int BORDER_WIDTH = 5;
int width = bmp.Width - (2 * BORDER_WIDTH);
int height = bmp.Height - (2 * BORDER_WIDTH);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Black);
Pen pen = new Pen(color);
int size = data.Length;
for (int iPixel = 0; iPixel < width; iPixel++)
{
// determine start and end points within WAV
int start = (int)((float)iPixel * ((float)size / (float)width));
int end = (int)((float)(iPixel + 1) * ((float)size / (float)width));
float min = float.MaxValue;
float max = float.MinValue;
for (int i = start; i < end; i++)
{
float val = data[i];
min = val < min ? val : min;
max = val > max ? val : max;
}
int yMax = BORDER_WIDTH + height - (int)((max + 1) * .5 * height);
int yMin = BORDER_WIDTH + height - (int)((min + 1) * .5 * height);
g.DrawLine(pen, iPixel + BORDER_WIDTH, yMax,
iPixel + BORDER_WIDTH, yMin);
}
}
pb.Image = bmp;
}
This function will produce something like this:
This takes an array of samples in floating-point format (where all sample values range from -1 to +1). If your original data is actually in the form of a byte[] array, you'll have to do a little bit of work to convert it to float[]. Let me know if you need that, too.
Update: since the question technically asked for something to render a byte array, here are a couple of helper methods:
public float[] FloatArrayFromStream(System.IO.MemoryStream stream)
{
return FloatArrayFromByteArray(stream.GetBuffer());
}
public float[] FloatArrayFromByteArray(byte[] input)
{
float[] output = new float[input.Length / 4];
for (int i = 0; i < output.Length; i++)
{
output[i] = BitConverter.ToSingle(input, i * 4);
}
return output;
}
Update 2: I forgot there's a better way to do this:
public float[] FloatArrayFromByteArray(byte[] input)
{
float[] output = new float[input.Length / 4];
Buffer.BlockCopy(input, 0, output, 0, input.Length);
return output;
}
I'm just so in love with for loops, I guess.
I modified MusiGenesis's solution a little bit.
This gave me a much better result, especially with house music :)
public static Bitmap DrawNormalizedAudio(List<float> data, Color foreColor, Color backColor, Size imageSize)
{
Bitmap bmp = new Bitmap(imageSize.Width, imageSize.Height);
int BORDER_WIDTH = 0;
float width = bmp.Width - (2 * BORDER_WIDTH);
float height = bmp.Height - (2 * BORDER_WIDTH);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(backColor);
Pen pen = new Pen(foreColor);
float size = data.Count;
for (float iPixel = 0; iPixel < width; iPixel += 1)
{
// determine start and end points within WAV
int start = (int)(iPixel * (size / width));
int end = (int)((iPixel + 1) * (size / width));
if (end > data.Count)
end = data.Count;
float posAvg, negAvg;
averages(data, start, end, out posAvg, out negAvg);
float yMax = BORDER_WIDTH + height - ((posAvg + 1) * .5f * height);
float yMin = BORDER_WIDTH + height - ((negAvg + 1) * .5f * height);
g.DrawLine(pen, iPixel + BORDER_WIDTH, yMax, iPixel + BORDER_WIDTH, yMin);
}
}
return bmp;
}
private static void averages(List<float> data, int startIndex, int endIndex, out float posAvg, out float negAvg)
{
posAvg = 0.0f;
negAvg = 0.0f;
int posCount = 0, negCount = 0;
for (int i = startIndex; i < endIndex; i++)
{
if (data[i] > 0)
{
posCount++;
posAvg += data[i];
}
else
{
negCount++;
negAvg += data[i];
}
}
posAvg /= posCount;
negAvg /= negCount;
}
with adapted code from robby and using Graphics.Fill/DrawClosedCurve with antialiasing, I get a pretty good looking result.
here's the code:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace Soundfingerprinting.Audio.Services
{
public static class AudioVisualizationService
{
public class WaveVisualizationConfiguration
{
public Nullable<Color> AreaColor { get; set; }
public Nullable<Color> EdgeColor { get; set; }
public int EdgeSize { get; set; }
public Nullable<Rectangle> Bounds { get; set; }
public double Overlap { get; set; }
public int Step { get; set; }
}
public static void DrawWave(float[] data, Bitmap bitmap, WaveVisualizationConfiguration config = null)
{
Color areaColor = Color.FromArgb(0x7F87CEFA);// Color.LightSkyBlue; semi transparent
Color edgeColor = Color.DarkSlateBlue;
int edgeSize = 2;
int step = 2;
double overlap = 0.10f; // would better use a windowing function
Rectangle bounds = Rectangle.FromLTRB(0, 0, bitmap.Width, bitmap.Height);
if (config != null)
{
edgeSize = config.EdgeSize;
if (config.AreaColor.HasValue)
areaColor = config.AreaColor.GetValueOrDefault();
if (config.EdgeColor.HasValue)
edgeColor = config.EdgeColor.GetValueOrDefault();
if (config.Bounds.HasValue)
bounds = config.Bounds.GetValueOrDefault();
step = Math.Max(1, config.Step);
overlap = config.Overlap;
}
float width = bounds.Width;
float height = bounds.Height;
using (Graphics g = Graphics.FromImage(bitmap))
{
Pen edgePen = new Pen(edgeColor);
edgePen.LineJoin = LineJoin.Round;
edgePen.Width = edgeSize;
Brush areaBrush = new SolidBrush(areaColor);
float size = data.Length;
PointF[] topCurve = new PointF[(int)width / step];
PointF[] bottomCurve = new PointF[(int)width / step];
int idx = 0;
for (float iPixel = 0; iPixel < width; iPixel += step)
{
// determine start and end points within WAV
int start = (int)(iPixel * (size / width));
int end = (int)((iPixel + step) * (size / width));
int window = end - start;
start -= (int)(overlap * window);
end += (int)(overlap * window);
if (start < 0)
start = 0;
if (end > data.Length)
end = data.Length;
float posAvg, negAvg;
averages(data, start, end, out posAvg, out negAvg);
float yMax = height - ((posAvg + 1) * .5f * height);
float yMin = height - ((negAvg + 1) * .5f * height);
float xPos = iPixel + bounds.Left;
if (idx >= topCurve.Length)
idx = topCurve.Length - 1;
topCurve[idx] = new PointF(xPos, yMax);
bottomCurve[bottomCurve.Length - idx - 1] = new PointF(xPos, yMin);
idx++;
}
PointF[] curve = new PointF[topCurve.Length * 2];
Array.Copy(topCurve, curve, topCurve.Length);
Array.Copy(bottomCurve, 0, curve, topCurve.Length, bottomCurve.Length);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillClosedCurve(areaBrush, curve, FillMode.Winding, 0.15f);
if (edgeSize > 0)
g.DrawClosedCurve(edgePen, curve, 0.15f, FillMode.Winding);
}
}
private static void averages(float[] data, int startIndex, int endIndex, out float posAvg, out float negAvg)
{
posAvg = 0.0f;
negAvg = 0.0f;
int posCount = 0, negCount = 0;
for (int i = startIndex; i < endIndex; i++)
{
if (data[i] > 0)
{
posCount++;
posAvg += data[i];
}
else
{
negCount++;
negAvg += data[i];
}
}
if (posCount > 0)
posAvg /= posCount;
if (negCount > 0)
negAvg /= negCount;
}
}
}
In NAudio, there is code to draw audio waveforms in both WinForms and WPF. Have a look at the demo projects for examples of how to use it.
I've been a fan of ZedGraph for many years and have used it to display all kinds of data in various projects.
The following sample code graphs an array of doubles varying between -1 and 1:
void DisplayWaveGraph(ZedGraphControl graphControl, double[] waveData)
{
var pane = graphControl.GraphPane;
pane.Chart.Border.IsVisible = false;
pane.Chart.Fill.IsVisible = false;
pane.Fill.Color = Color.Black;
pane.Margin.All = 0;
pane.Title.IsVisible = false;
pane.XAxis.IsVisible = false;
pane.XAxis.Scale.Max = waveData.Length - 1;
pane.XAxis.Scale.Min = 0;
pane.YAxis.IsVisible = false;
pane.YAxis.Scale.Max = 1;
pane.YAxis.Scale.Min = -1;
var timeData = Enumerable.Range(0, waveData.Length)
.Select(i => (double) i)
.ToArray();
pane.AddCurve(null, timeData, waveData, Color.Lime, SymbolType.None);
graphControl.AxisChange();
}
The above sample mimics the style of an audio editor by suppressing the axes and changing the colors to produce the following:

Categories

Resources