I am making a program where I want to take an image and reduce its color palette to a preset palette of 60 colors, and then add a dithering effect. This seems to involve two things:
A color distance algorithm that goes through each pixel, gets its color, and then changes it to the color closest to it in the palette so that that image doesn't have colors that are not contained in the palette.
A dithering algorithm that goes through the color of each pixel and diffuses the difference between the original color and the new palette color chosen across the surrounding pixels.
After reading about color difference, I figured I would use either the CIE94 or CIEDE2000 algorithm for finding the closest color from my list. I also decided to use the fairly common Floyd–Steinberg dithering algorithm for the dithering effect.
Over the past 2 days I have written my own versions of these algorithms, pulled other versions of them from examples on the internet, tried them both first in Java and now C#, and pretty much every single time the output image has the same issue. Some parts of it look perfectly fine, have the correct colors, and are dithered properly, but then other parts (and sometimes the entire image) end up way too bright, are completely white, or all blur together. Usually darker images or darker parts of images turn out fine, but any part that is bright or has lighter colors at all gets turned up way brighter. Here is an example of an input and output image with these issues:
Input:
]3
Output:
I do have one idea for what may be causing this. When a pixel is sent through the "nearest color" function, I have it output its RGB values and it seems like some of them have their R value (and potentially other values??) pushed way higher than they should be, and even sometimes over 255 as shown in the screenshot. This does NOT happen for the earliest pixels in the image, only for ones that are multiple pixels in already and are already somewhat bright. This leads me to believe it is the dithering/error algorithm doing this, and not the color conversion or color difference algorithms. If that is the issue, then how would I go about fixing that?
Here's the relevant code and functions I'm using. At this point it's a mix of stuff I wrote and stuff I've found in libraries or other StackOverflow posts. I believe the main dithering algorithm and C3 class are copied basically directly from this Github page (and changed to work with C#, obviously)
class Program
{
public static C3[] palette = new C3[]{
new C3(196, 76, 86),
new C3(186, 11, 39),
new C3(113, 0, 32),
new C3(120, 41, 56),
new C3(203, 125, 84),
new C3(205, 90, 40),
new C3(175, 50, 33),
new C3(121, 61, 54),
// etc... palette is 60 colors total
// each object contains an r, g, and b value
};
static void Main(string[] args)
{
// paths for original image and output path for dithered image
string path = #"C:\Users\BillehBawb\Desktop\";
string imgPath = path + "amy.jpg";
string createPath = path + "amydithered.jpg";
// pulls the original image, runs the dithering function, then saves the new image
Bitmap img = new Bitmap(imgPath);
Bitmap dithered = floydSteinbergDithering(img);
dithered.Save(createPath, ImageFormat.Jpeg);
}
// loops through every pixel in the image, populates a 2d array with the pixel colors, then loops through again to change the color to one in the palette and do the dithering algorithm
private static Bitmap floydSteinbergDithering(Bitmap img)
{
int w = img.Width;
int h = img.Height;
C3[,] d = new C3[h, w];
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
d[y, x] = new C3(img.GetPixel(x, y).ToArgb());
}
}
for (int y = 0; y < img.Height; y++)
{
for (int x = 0; x < img.Width; x++)
{
C3 oldColor = d[y, x];
C3 newColor = findClosestPaletteColor(oldColor, palette);
img.SetPixel(x, y, newColor.toColor());
C3 err = oldColor.sub(newColor);
if (x + 1 < w)
{
d[y, x + 1] = d[y, x + 1].add(err.mul(7.0 / 16));
}
if (x - 1 >= 0 && y + 1 < h)
{
d[y + 1, x - 1] = d[y + 1, x - 1].add(err.mul(3.0 / 16));
}
if (y + 1 < h)
{
d[y + 1, x] = d[y + 1, x].add(err.mul(5.0 / 16));
}
if (x + 1 < w && y + 1 < h)
{
d[y + 1, x + 1] = d[y + 1, x + 1].add(err.mul(1.0 / 16));
}
}
}
return img;
}
// loops through the palette, converts the input pixel and palette colors to the LAB format, finds the difference between all of them, and selects the palette color with the lowest difference
private static C3 findClosestPaletteColor(C3 c, C3[] palette)
{
double[] pixelLab = rgbToLab(c.toColor().R, c.toColor().G, c.toColor().B);
double minDist = Double.MaxValue;
int colorIndex = 0;
for (int i = 0; i < palette.Length; i++)
{
double[] colors = rgbToLab(palette[i].toColor().R, palette[i].toColor().G, palette[i].toColor().B);
double dist = labDist(pixelLab[0], pixelLab[1], pixelLab[2], colors[0], colors[1], colors[2]);
if (dist < minDist)
{
colorIndex = i;
minDist = dist;
}
}
return palette[colorIndex];
}
// finds the deltaE/difference between two sets of LAB colors with the CIE94 algorithm
public static double labDist(double l1, double a1, double b1, double l2, double a2, double b2)
{
var deltaL = l1 - l2;
var deltaA = a1 - a2;
var deltaB = b1 - b2;
var c1 = Math.Sqrt(Math.Pow(a1, 2) + Math.Pow(b1, 2));
var c2 = Math.Sqrt(Math.Pow(a2, 2) + Math.Pow(b2, 2));
var deltaC = c1 - c2;
var deltaH = Math.Pow(deltaA, 2) + Math.Pow(deltaB, 2) - Math.Pow(deltaC, 2);
deltaH = deltaH < 0 ? 0 : Math.Sqrt(deltaH);
double sl = 1.0;
double kc = 1.0;
double kh = 1.0;
double Kl = 1.0;
double K1 = .045;
double K2 = .015;
var sc = 1.0 + K1 * c1;
var sh = 1.0 + K2 * c1;
var i = Math.Pow(deltaL / (Kl * sl), 2) +
Math.Pow(deltaC / (kc * sc), 2) +
Math.Pow(deltaH / (kh * sh), 2);
var finalResult = i < 0 ? 0 : Math.Sqrt(i);
return finalResult;
}
// converts RGB colors to the XYZ and then LAB format so the color difference algorithm can be done
public static double[] rgbToLab(int R, int G, int B)
{
float[] xyz = new float[3];
float[] lab = new float[3];
float[] rgb = new float[3];
rgb[0] = R / 255.0f;
rgb[1] = G / 255.0f;
rgb[2] = B / 255.0f;
if (rgb[0] > .04045f)
{
rgb[0] = (float)Math.Pow((rgb[0] + .055) / 1.055, 2.4);
}
else
{
rgb[0] = rgb[0] / 12.92f;
}
if (rgb[1] > .04045f)
{
rgb[1] = (float)Math.Pow((rgb[1] + .055) / 1.055, 2.4);
}
else
{
rgb[1] = rgb[1] / 12.92f;
}
if (rgb[2] > .04045f)
{
rgb[2] = (float)Math.Pow((rgb[2] + .055) / 1.055, 2.4);
}
else
{
rgb[2] = rgb[2] / 12.92f;
}
rgb[0] = rgb[0] * 100.0f;
rgb[1] = rgb[1] * 100.0f;
rgb[2] = rgb[2] * 100.0f;
xyz[0] = ((rgb[0] * .412453f) + (rgb[1] * .357580f) + (rgb[2] * .180423f));
xyz[1] = ((rgb[0] * .212671f) + (rgb[1] * .715160f) + (rgb[2] * .072169f));
xyz[2] = ((rgb[0] * .019334f) + (rgb[1] * .119193f) + (rgb[2] * .950227f));
xyz[0] = xyz[0] / 95.047f;
xyz[1] = xyz[1] / 100.0f;
xyz[2] = xyz[2] / 108.883f;
if (xyz[0] > .008856f)
{
xyz[0] = (float)Math.Pow(xyz[0], (1.0 / 3.0));
}
else
{
xyz[0] = (xyz[0] * 7.787f) + (16.0f / 116.0f);
}
if (xyz[1] > .008856f)
{
xyz[1] = (float)Math.Pow(xyz[1], 1.0 / 3.0);
}
else
{
xyz[1] = (xyz[1] * 7.787f) + (16.0f / 116.0f);
}
if (xyz[2] > .008856f)
{
xyz[2] = (float)Math.Pow(xyz[2], 1.0 / 3.0);
}
else
{
xyz[2] = (xyz[2] * 7.787f) + (16.0f / 116.0f);
}
lab[0] = (116.0f * xyz[1]) - 16.0f;
lab[1] = 500.0f * (xyz[0] - xyz[1]);
lab[2] = 200.0f * (xyz[1] - xyz[2]);
return new double[] { lab[0], lab[1], lab[2] };
}
}
And here is the C3 class, its basically just the Color class with some math functions to make it cleaner to do the dithering on
class C3
{
int r, g, b;
public C3(int c)
{
Color color = Color.FromArgb(c);
r = color.R;
g = color.G;
b = color.B;
}
public C3(int r, int g, int b)
{
this.r = r;
this.g = g;
this.b = b;
}
public C3 add(C3 o)
{
return new C3(r + o.r, g + o.g, b + o.b);
}
public int clamp(int c)
{
return Math.Max(0, Math.Min(255, c));
}
public int diff(C3 o)
{
int Rdiff = o.r - r;
int Gdiff = o.g - g;
int Bdiff = o.b - b;
int distanceSquared = Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff;
return distanceSquared;
}
public C3 mul(double d)
{
return new C3((int)(d * r), (int)(d * g), (int)(d * b));
}
public C3 sub(C3 o)
{
return new C3(r - o.r, g - o.g, b - o.b);
}
public Color toColor()
{
return Color.FromArgb(clamp(r), clamp(g), clamp(b));
}
public int toRGB()
{
return toColor().ToArgb();
}
}
Sorry for the massive code dump, the functions are just pretty large and I want to provide everything I can. If anyone has any suggestions on a simpler or different way to do this, or if you know how to fix the issue I'm running into, please let me know. I have tried quite a few different algorithms for my desired result but I haven't been able to get any of them to do what I want them to. Any help or ideas are greatly appreciated, thank you!
It appears that when you shift the error to the neighbors in floydSteinbergDithering() the r,g,b values never get clamped until you cast them back to Color.
Since you're using int and not byte there is no prevention of overflows to negative or large values greater than 255 for r, g, and b.
You should consider implementing r,g, and b as properties that clamp to 0-255 when they're set.
This will ensure their values will never be outside your expected range (0 - 255).
class C3
{
private int r;
public int R
{
get => r;
set
{
r = clamp(value);
}
}
private int g;
public int G
{
get => g;
set
{
g = clamp(value);
}
}
private int b;
public int B
{
get => b;
set
{
b = clamp(value);
}
}
// rest of class
}
Related
Project: intelligence scissors.
The first part of the project is to load an image (in the form of RGBPixel 2d array) then to construct a graph of weights to use it later to determine the shortest path between 2 points on the image (the 2 points will be determined by an anchor and a free point..In short i will have the source and the destination points).
I have a function that open the image and return a RGBPixel 2d array already made.Now image is loaded i want to construct the graph to do the i should use a function called CalculatePixelEnergies here is the code
public static Vector2D CalculatePixelEnergies(int x, int y, RGBPixel[,] ImageMatrix)
{
if (ImageMatrix == null) throw new Exception("image is not set!");
Vector2D gradient = CalculateGradientAtPixel(x, y, ImageMatrix);
double gradientMagnitude = Math.Sqrt(gradient.X * gradient.X + gradient.Y * gradient.Y);
double edgeAngle = Math.Atan2(gradient.Y, gradient.X);
double rotatedEdgeAngle = edgeAngle + Math.PI / 2.0;
Vector2D energy = new Vector2D();
energy.X = Math.Abs(gradientMagnitude * Math.Cos(rotatedEdgeAngle));
energy.Y = Math.Abs(gradientMagnitude * Math.Sin(rotatedEdgeAngle));
return energy;
}
This function use CalculateGradientAtPixel, Here is the code in case you want it.
private static Vector2D CalculateGradientAtPixel(int x, int y, RGBPixel[,] ImageMatrix)
{
Vector2D gradient = new Vector2D();
RGBPixel mainPixel = ImageMatrix[y, x];
double pixelGrayVal = 0.21 * mainPixel.red + 0.72 * mainPixel.green + 0.07 * mainPixel.blue;
if (y == GetHeight(ImageMatrix) - 1)
{
//boundary pixel.
for (int i = 0; i < 3; i++)
{
gradient.Y = 0;
}
}
else
{
RGBPixel downPixel = ImageMatrix[y + 1, x];
double downPixelGrayVal = 0.21 * downPixel.red + 0.72 * downPixel.green + 0.07 * downPixel.blue;
gradient.Y = pixelGrayVal - downPixelGrayVal;
}
if (x == GetWidth(ImageMatrix) - 1)
{
//boundary pixel.
gradient.X = 0;
}
else
{
RGBPixel rightPixel = ImageMatrix[y, x + 1];
double rightPixelGrayVal = 0.21 * rightPixel.red + 0.72 * rightPixel.green + 0.07 * rightPixel.blue;
gradient.X = pixelGrayVal - rightPixelGrayVal;
}
return gradient;
}
In my code of graph construction i decided to make a 2d double array to hold the weights, here what i do but it seems to be a wrong construction
public static double [,] calculateWeights(RGBPixel[,] ImageMatrix)
{
double[,] weights = new double[1000, 1000];
int height = ImageOperations.GetHeight(ImageMatrix);
int width = ImageOperations.GetWidth(ImageMatrix);
for (int y = 0; y < height - 1; y++)
{
for (int x = 0; x < width - 1; x++)
{
Vector2D e;
e = ImageOperations.CalculatePixelEnergies(x, y, ImageMatrix);
weights[y + 1, x] = 1 / e.X;
weights[y, x + 1] = 1 / e.Y;
}
}
return weights;
}
an example for an image
an other example for an image
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
For my assignment I need to make to make a color picker that looks like this:
I've got the wheel part but can't figure out how to draw the triangle.
using System.Drawing;
using System.Drawing.Imaging;
void Main()
{
double hue = 3.3;
double sat = 0.4;
double val = 0.9;
var wheel = new ColorPicker(400);
var img = wheel.DrawImage(hue, sat, val);
using (var g = Graphics.FromImage(img))
{
var pen = val < 0.5 ? Pens.White : Pens.Black;
var wheelPosition = wheel.GetWheelPosition(hue);
g.DrawEllipse(pen, (float)wheelPosition.X - 5, (float)wheelPosition.Y - 5, 10, 10);
var trianglePosition = wheel.GetTrianglePosition(sat, val);
g.DrawEllipse(pen, (float)trianglePosition.X - 5, (float)trianglePosition.Y - 5, 10, 10);
}
img.Dump(); // LINQPad extension method
}
public class ColorPicker
{
public int Size { get; }
public int CenterX => Size / 2;
public int CenterY => Size / 2;
public int InnerRadius => Size * 5 / 12;
public int OuterRadius => Size / 2;
public ColorPicker(int size = 400)
{
Size = size;
}
public enum Area
{
Outside,
Wheel,
Triangle
}
public struct PickResult
{
public Area Area { get; set; }
public double? Hue { get; set; }
public double? Sat { get; set; }
public double? Val { get; set; }
}
public PickResult Pick(double x, double y)
{
var distanceFromCenter = Math.Sqrt((x - CenterX) * (x - CenterX) + (y - CenterY) * (y - CenterY));
var sqrt3 = Math.Sqrt(3);
if (distanceFromCenter > OuterRadius)
{
// Outside
return new PickResult { Area = Area.Outside };
}
else if (distanceFromCenter > InnerRadius)
{
// Wheel
var angle = Math.Atan2(y - CenterY, x - CenterX) + Math.PI / 2;
if (angle < 0) angle += 2 * Math.PI;
var hue = angle;
return new PickResult { Area = Area.Wheel, Hue = hue };
}
else
{
// Inside
var x1 = (x - CenterX) * 1.0 / InnerRadius;
var y1 = (y - CenterY) * 1.0 / InnerRadius;
if (0 * x1 + 2 * y1 > 1) return new PickResult { Area = Area.Outside };
else if (sqrt3 * x1 + (-1) * y1 > 1) return new PickResult { Area = Area.Outside };
else if (-sqrt3 * x1 + (-1) * y1 > 1) return new PickResult { Area = Area.Outside };
else
{
// Triangle
var sat = (1 - 2 * y1) / (sqrt3 * x1 - y1 + 2);
var val = (sqrt3 * x1 - y1 + 2) / 3;
return new PickResult { Area = Area.Triangle, Sat = sat, Val = val };
}
}
}
public Image DrawImage(double hue = 0.0, double sat = 1.0, double val = 1.0)
{
var img = new Bitmap(Size, Size, PixelFormat.Format32bppArgb);
for (int y = 0; y < Size; y++)
{
for (int x = 0; x < Size; x++)
{
Color color;
var result = Pick(x, y);
if (result.Area == Area.Outside)
{
// Outside
color = Color.Transparent;
}
else if (result.Area == Area.Wheel)
{
// Wheel
color = HSV(result.Hue.Value, sat, val, 1);
}
else
{
// Triangle
color = HSV(hue, result.Sat.Value, result.Val.Value, 1);
}
img.SetPixel(x, y, color);
}
}
return img;
}
private Color HSV(double hue, double sat, double val, double alpha)
{
var chroma = val * sat;
var step = Math.PI / 3;
var interm = chroma * (1 - Math.Abs((hue / step) % 2.0 - 1));
var shift = val - chroma;
if (hue < 1 * step) return RGB(shift + chroma, shift + interm, shift + 0, alpha);
if (hue < 2 * step) return RGB(shift + interm, shift + chroma, shift + 0, alpha);
if (hue < 3 * step) return RGB(shift + 0, shift + chroma, shift + interm, alpha);
if (hue < 4 * step) return RGB(shift + 0, shift + interm, shift + chroma, alpha);
if (hue < 5 * step) return RGB(shift + interm, shift + 0, shift + chroma, alpha);
return RGB(shift + chroma, shift + 0, shift + interm, alpha);
}
private Color RGB(double red, double green, double blue, double alpha)
{
return Color.FromArgb(
Math.Min(255, (int)(alpha * 256)),
Math.Min(255, (int)(red * 256)),
Math.Min(255, (int)(green * 256)),
Math.Min(255, (int)(blue * 256)));
}
public PointD GetWheelPosition(double hue)
{
double middleRadius = (InnerRadius + OuterRadius) / 2;
return new PointD
{
X = CenterX + middleRadius * Math.Sin(hue),
Y = CenterY - middleRadius * Math.Cos(hue)
};
}
public PointD GetTrianglePosition(double sat, double val)
{
var sqrt3 = Math.Sqrt(3);
return new PointD
{
X = CenterX + InnerRadius * (2 * val - sat * val - 1) * sqrt3 / 2,
Y = CenterY + InnerRadius * (1 - 3 * sat * val) / 2
};
}
}
public class PointD
{
public double X { get; set; }
public double Y { get; set; }
}
Result:
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 got a XYZ to RGB converter from here and wrote it into C# but, I've been having some problems.
public static XYZColor RGBtoXYZ(RGBColor rgb)
{
XYZColor xyz = new XYZColor();
float r, g, b;
r = rgb.R;
g = rgb.G;
b = rgb.B;
if (r > 0.04045f) r = (float)Math.Pow((r + 0.055)/1.055, 2.4);
else r = r / 12.92f;
if (g > 0.04045) g =(float)Math.Pow((g + 0.055f)/1.055f , 2.4f);
else g = g/12.92f;
if (b > 0.04045f) b = (float)Math.Pow((b + 0.055f)/1.055f , 2.4f);
else b = b/12.92f;
r *= 100;
g *= 100;
b *= 100;
xyz.X = r*0.4124f + g*0.3576f + b*0.1805f;
xyz.Y = r*0.2126f + g*0.7152f + b*0.0722f;
xyz.Z = r*0.0193f + g*0.1192f + b*0.9505f;
xyz.A = rgb.A;
return xyz;
}
public static RGBColor XYZtoRGB(XYZColor xyz)
{
RGBColor rgb = new RGBColor();
float x, y, z;
x = xyz.X;
y = xyz.Y;
z = xyz.Z;
x = x/100; //X from 0 to 95.047 (Observer = 2°, Illuminant = D65)
y = y/100; //Y from 0 to 100.000
z = z/100; //Z from 0 to 108.883
rgb.R = x*3.2406f + y*-1.5372f + z*-0.4986f;
rgb.G = x*-0.9689f + y*1.8758f + z*0.0415f;
rgb.B = x*0.0557f + y*-0.2040f + z*1.0570f;
if (rgb.R > 0.0031308f) rgb.R = 1.055f*(float)Math.Pow(rgb.R, (1/2.4f)) - 0.055f;
else rgb.R = 12.92f*rgb.R;
if (rgb.G > 0.0031308f) rgb.G = 1.055f*(float)Math.Pow(rgb.G ,(1/2.4f)) - 0.055f;
else rgb.G = 12.92f*rgb.G;
if (rgb.B > 0.0031308f) rgb.B = 1.055f*(float)Math.Pow(rgb.B, (1/2.4f)) - 0.055f;
else rgb.B = 12.92f*rgb.B;
rgb.A = xyz.A;
return rgb;
}
On my testing application, I make a tilemap, each with it's own color, then give it a hue through HSV and cycle them so it shows the full spectrum. Then, every update it gets the hue added to so it moves through the spectrum.
However, when I convert the color to and from XYZ it shows up like this.
Here's the code in case you were interested. The HSVtoRGB method works as expected.
Hue += HUEINCREASE;
RGBColor c = ColorMath.HSVtoRGB(Hue, 1, 1, 1);
//Convert to XYZ and back
XYZColor xyz = ColorMath.RGBtoXYZ(c);
c = ColorMath.XYZtoRGB(xyz);
Render.Color = c;
Both XYZColor and RGBColor are structs. RGBColor holds floats, and if it's value is greater than 1, it wraps the color around. So if it had a red value of 1.1f, it would wrap it to .1f;
You can use ColorHelper library for this:
RGB to XYZ:
using ColorHelper;
RGB rgb = new RGB(100, 100, 100);
XYZ xyz = ColorConverter.RgbToXyz(rgb);
XYZ to RGB:
using ColorHelper;
XYZ xyz = new XYZ(0, 0, 0);
RGB rgb = ColorConverter.XyzToRgb(xyz);
Links:
Github
Nuget
I'm trying to write a function that will let me red-shift or blue-shift a bitmap while preserving the overall brightness of the image. Basically, a fully red-shifted bitmap would have the same brightness as the original but be thoroughly red-tinted (i.e. the G and B values would be equal for all pixels). Same for blue-tinting (but with R and G equal). The degree of spectrum shifting needs to vary from 0 to 1.
Thanks in advance.
Here is the effect I was looking for (crappy JPEG, sorry):
alt text http://www.freeimagehosting.net/uploads/d15ff241ca.jpg
The image in the middle is the original, and the side images are fully red-shifted, partially red-shifted, partially blue-shifted and fully blue-shifted, respectively.
And here is the function that produces this effect:
public void RedBlueShift(Bitmap bmp, double factor)
{
byte R = 0;
byte G = 0;
byte B = 0;
byte Rmax = 0;
byte Gmax = 0;
byte Bmax = 0;
double avg = 0;
double normal = 0;
if (factor > 1)
{
factor = 1;
}
else if (factor < -1)
{
factor = -1;
}
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
Color color = bmp.GetPixel(x, y);
R = color.R;
G = color.G;
B = color.B;
avg = (double)(R + G + B) / 3;
normal = avg / 255.0; // to preserve overall intensity
if (factor < 0) // red-tinted:
{
if (normal < .5)
{
Rmax = (byte)((normal / .5) * 255);
Gmax = 0;
Bmax = 0;
}
else
{
Rmax = 255;
Gmax = (byte)(((normal - .5) * 2) * 255);
Bmax = Gmax;
}
R = (byte)((double)R - ((double)(R - Rmax) * -factor));
G = (byte)((double)G - ((double)(G - Gmax) * -factor));
B = (byte)((double)B - ((double)(B - Bmax) * -factor));
}
else if (factor > 0) // blue-tinted:
{
if (normal < .5)
{
Rmax = 0;
Gmax = 0;
Bmax = (byte)((normal / .5) * 255);
}
else
{
Rmax = (byte)(((normal - .5) * 2) * 255);
Gmax = Rmax;
Bmax = 255;
}
R = (byte)((double)R - ((double)(R - Rmax) * factor));
G = (byte)((double)G - ((double)(G - Gmax) * factor));
B = (byte)((double)B - ((double)(B - Bmax) * factor));
}
color = Color.FromArgb(R, G, B);
bmp.SetPixel(x, y, color);
}
}
}
You'd use the ColorMatrix class for this. There's a good tutorial available in this project.