private bool isBlackWhite(Color[,] image, CIELAB[,] imageLAB) { double nonGray = 0; double thresh = 0.001; CIELAB white = new CIELAB(100, 0, 0); CIELAB black = new CIELAB(0, 0, 0); int width = image.GetLength(0); int height = image.GetLength(1); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { Color color = image[i, j]; CIELAB lab = imageLAB[i, j];//Util.RGBtoLAB(color); bool gray = color.GetSaturation() <= 0.2 || lab.SqDist(white) <= 5 || lab.SqDist(black) <= 5; if (!gray) { nonGray++; } } } return(nonGray / (width * height) < thresh); }
public double NormalizedSaliency(CIELAB a, double minE = -4.5, double maxE = 0) { //hardcoded to XKCD //double minE = -4.5; //double maxE = 0; return((Saliency(a) - minE) / (maxE - minE)); }
public Cluster() { lab = new CIELAB(); sumlab = new CIELAB(); count = 0; id = -1; }
public void AddColor(CIELAB color, double weight = 1) { sumlab.L += color.L * weight; sumlab.A += color.A * weight; sumlab.B += color.B * weight; count += weight; }
public Bitmap Render() { //render the template (assuming possible values are between -200 and 200) //must clamp to be valid RGB values int width = template.GetLength(0); int height = template.GetLength(1); Bitmap result = new Bitmap(width, height); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { CIELAB lab = template[i, j]; //add gray to the lab value CIELAB gray = new CIELAB(53.5850, 0, 0); //convert (first clamp to reasonable LAB values?) Color rgb = Util.LABtoRGB(lab + gray); //Color rgb = Util.LABtoRGB(lab * gray); result.SetPixel(i, j, rgb); } } return(result); }
private int GetBinId(CIELAB color) { int Lbin = GetBin(color.L, minL, maxL, Lbins); int Abin = GetBin(color.A, minA, maxA, Abins); int Bbin = GetBin(color.B, minB, maxB, Bbins); return((int)(Abins * Bbins * Lbin + Abins * Bbin + Abin)); }
private CIELAB[,] AverageRecolor(CIELAB[,] image, PaletteData palette) { int width = image.GetLength(0); int height = image.GetLength(1); CIELAB[,] result = new CIELAB[width, height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { double[] scores = new double[palette.colors.Count()]; //check if the color is in the palette CIELAB color = image[i, j]; if (palette.lab.Contains(color)) { result[i, j] = color; continue; } //otherwise, find the best color for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { int x = i + dx; int y = j + dy; double weight = (dx == 0 && dy == 0)? 4:1; if (Util.InBounds(x, y, width, height)) { for (int c = 0; c < palette.colors.Count(); c++) { scores[c] += weight * image[x, y].SqDist(palette.lab[c]); } } } } //pick the color with the min score double minScore = Double.PositiveInfinity; int bestIdx = 0; for (int c = 0; c < palette.colors.Count(); c++) { if (scores[c] < minScore) { minScore = scores[c]; bestIdx = c; } } result[i, j] = palette.lab[bestIdx]; } } return(result); }
public PixelCluster() { id = -1; lab = new CIELAB(); count = 1; neighbors = new SortedSet <int>(); parentId = -1; children = new int[] { }; }
public PixelCluster(int i, CIELAB color) { id = i; lab = color; count = 1; neighbors = new SortedSet <int>(); parentId = i; children = new int[] { }; }
public ColorTemplate(Bitmap image, Segmentation seg, PaletteData palette) { slots = seg; //TODO: need to copy the segmentation? originalPalette = new PaletteData(palette); int width = image.Width; int height = image.Height; template = new CIELAB[width, height]; //initialize the template for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { template[i, j] = Util.RGBtoLAB(image.GetPixel(i, j)); } } segToColor = new int[seg.numSegments]; //map each segment to the closest palette color, based on its mean color //TODO: if two adjacent segments have the same mean color, we could merge them... for (int i = 0; i < seg.numSegments; i++) { CIELAB lab = seg.segToMeanColor[i]; int bestColor = -1; double bestDist = Double.PositiveInfinity; for (int j = 0; j < palette.lab.Count(); j++) { double dist = palette.lab[j].SqDist(lab); if (dist < bestDist) { bestDist = dist; bestColor = j; } } segToColor[i] = bestColor; } //subtract the mean color from the template for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { int coloridx = segToColor[slots.assignments[i, j]]; template[i, j] -= palette.lab[coloridx]; //template[i, j] /= palette.lab[coloridx]; } } }
public double GetCNDist(CIELAB a, CIELAB b) { int i = colorNames.GetBin(a); int j = colorNames.GetBin(b); if (CNcache[i, j] < 0) { double dist = 1 - colorNames.CosineDistance(i, j); CNcache[i, j] = dist; CNcache[j, i] = dist; } return(CNcache[i, j]); }
public static ColorMood LABtoColorMood(CIELAB lab) { //hue angle double h = Math.Atan2(lab.B, lab.A);// +Math.PI; double radians1 = 100 * (Math.PI / 180.0); double radians2 = 50 * (Math.PI / 180.0); double chroma = Math.Sqrt(lab.A * lab.A + lab.B * lab.B); double activity = -2.1 + 0.06 * Math.Sqrt(Math.Pow(lab.L - 50, 2) + Math.Pow(lab.A - 3, 2) + Math.Pow((lab.B - 17) / 1.4, 2)); double weight = -1.8 + 0.04 * (100 - lab.L) + 0.45 * Math.Cos(h - radians1); double heat = -0.5 + 0.02 * Math.Pow(chroma, 1.07) * Math.Cos(h - radians2); return(new ColorMood(activity, weight, heat)); }
private CIELAB ClosestColor(CIELAB color, List <CIELAB> palette) { int bestc = 0; Double bestDist = Double.PositiveInfinity; for (int c = 0; c < palette.Count(); c++) { double dist = color.SqDist(palette[c]); if (dist < bestDist) { bestDist = dist; bestc = c; } } return(palette[bestc]); }
public double Saliency(CIELAB a) { double H = 0; int W = terms.Count(); int i = GetBin(a); for (int w = 0; w < W; w++) { double p = T[i * W + w]; p /= ccount[i]; if (p > 0) { H += p * Math.Log(p) / Math.Log(2); } } return(H); }
public Bitmap SolidColor(PaletteData palette, int[] slotToColor) { int width = Width(); int height = Height(); Bitmap result = new Bitmap(width, height); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { CIELAB lab = palette.lab[slotToColor[slots.assignments[i, j]]]; result.SetPixel(i, j, Util.LABtoRGB(lab)); } } return(result); }
public ColorTemplate(Bitmap image, PaletteData palette) { //create the segmentation based on the palette //TODO: There's a problem that sometimes a palette color won't appear in the image (perhaps due to color blending), and so the slot will have no pixels associated with it int width = image.Width; int height = image.Height; slots = new Segmentation(palette.colors.Count(), width, height); segToColor = new int[slots.numSegments]; template = new CIELAB[width, height]; CIELAB[,] recolored = ModeRecolor(Util.Map <Color, CIELAB>(Util.BitmapToArray(image), c => Util.RGBtoLAB(c)), palette); for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { template[i, j] = new CIELAB(); int bestIdx = -1; CIELAB lab = recolored[i, j]; double bestDist = Double.PositiveInfinity; for (int c = 0; c < palette.lab.Count(); c++) { double dist = palette.lab[c].SqDist(lab); if (dist < bestDist) { bestDist = dist; bestIdx = c; if (dist < 0.00001) { break; } } } slots.assignments[i, j] = bestIdx; slots.counts[bestIdx]++; segToColor[bestIdx] = bestIdx; } } originalPalette = new PaletteData(palette); }
public static Color LABtoRGB(CIELAB lab) { double gamma = 2.2; double e = 216 / 24389.0; double k = 24389 / 27.0; double XR = 0.95047; double YR = 1.00000; double ZR = 1.08883; double fy = (lab.L + 16) / 116.0; double fx = lab.A / 500.0 + fy; double fz = fy - lab.B / 200.0; double[,] xyzTorgbMatrix = new double[3, 3] { { 3.2404542, -1.5371385, -0.4985314 }, { -0.9692660, 1.8760108, 0.0415560 }, { 0.0556434, -0.2040259, 1.0572252 } }; double xR = Math.Pow(fx, 3.0); double zR = Math.Pow(fz, 3.0); xR = (xR > e) ? xR : (116 * fx - 16) / k; double yR = (lab.L > k * e) ? Math.Pow((lab.L + 16) / 116.0, 3.0) : lab.L / k; zR = (zR > e) ? zR : (116 * fz - 16) / k; double x = xR * XR; double y = yR * YR; double z = zR * ZR; //xyz to rgb double r = xyzTorgbMatrix[0, 0] * x + xyzTorgbMatrix[0, 1] * y + xyzTorgbMatrix[0, 2] * z; double g = xyzTorgbMatrix[1, 0] * x + xyzTorgbMatrix[1, 1] * y + xyzTorgbMatrix[1, 2] * z; double b = xyzTorgbMatrix[2, 0] * x + xyzTorgbMatrix[2, 1] * y + xyzTorgbMatrix[2, 2] * z; int red = (int)Math.Round(255 * (Math.Pow(clamp(r), 1.0 / gamma))); int green = (int)Math.Round(255 * (Math.Pow(clamp(g), 1.0 / gamma))); int blue = (int)Math.Round(255 * (Math.Pow(clamp(b), 1.0 / gamma))); return(Color.FromArgb(red, green, blue)); }
public int GetBin(CIELAB x) { int L = (int)(5 * Math.Round(x.L / 5)); int A = (int)(5 * Math.Round(x.A / 5)); int B = (int)(5 * Math.Round(x.B / 5)); String s = L + "," + A + "," + B; if (map.ContainsKey(s)) { return(map[s]); } else { //look at nearby bins CIELAB[] neighbors = new CIELAB[] { new CIELAB(L, A, B + 5), new CIELAB(L, A, B - 5), new CIELAB(L, A + 5, B), new CIELAB(L, A - 5, B), new CIELAB(L - 5, A, B), new CIELAB(L + 5, A, B) }; double bestDist = Double.PositiveInfinity; String best = ""; for (int i = 0; i < neighbors.Count(); i++) { String key = neighbors[i].L + "," + neighbors[i].A + "," + neighbors[i].B; double dist = neighbors[i].SqDist(x); if (dist < bestDist && map.ContainsKey(key)) { bestDist = dist; best = key; } } if (best != "") { //return the best return(map[best]); } else { //give up return(-1); } } }
public double CosineDistance(CIELAB a, CIELAB b) { int i = GetBin(a); int j = GetBin(b); int C = colors.Count(); int W = terms.Count(); double sa = 0, sb = 0, sc = 0; int ta; int tb; for (var w = 0; w < W; w++) { ta = T[i * W + w]; tb = T[j * W + w]; sa += ta * ta; sb += tb * tb; sc += ta * tb; } return(sc / Math.Max((Math.Sqrt(sa * sb)), 1)); }
public static List <Cluster> InitializePictureSeeds(List <CIELAB> colors, int k) { //initialize k seeds, randomly choose colors in LAB space //find extents List <Cluster> seeds = new List <Cluster>(); Random random = new Random(); //sample colors in LAB bounding box double Lmin = double.PositiveInfinity; double Lmax = double.NegativeInfinity; double Amin = double.PositiveInfinity; double Amax = double.NegativeInfinity; double Bmin = double.PositiveInfinity; double Bmax = double.NegativeInfinity; for (int i = 0; i < colors.Count(); i++) { CIELAB lab = colors[i]; Lmin = Math.Min(Lmin, lab.L); Lmax = Math.Max(Lmax, lab.L); Amin = Math.Min(Amin, lab.A); Amax = Math.Max(Amax, lab.A); Bmin = Math.Min(Bmin, lab.B); Bmax = Math.Max(Bmax, lab.B); } //initialize the seeds (stratified) randomly //within the bounding box if (k <= 10) { for (int i = 0; i < k; i++) { double L = random.NextDouble() * (Lmax - Lmin) + Lmin; double A = random.NextDouble() * (Amax - Amin) + Amin; double B = random.NextDouble() * (Bmax - Bmin) + Bmin; CIELAB seed = new CIELAB(L, A, B); Cluster cluster = new Cluster(); cluster.id = i; cluster.lab = seed; seeds.Add(cluster); } } else { //stratified //find closest floor perfect square. //TODO: need to generalize this better, doesn't work for non-perfect squares int sideLength = 2; int numSamples = (int)Math.Floor(k / (double)(sideLength * sideLength * sideLength)); int i = 0; for (int l = 0; l < sideLength; l++) { double dLmax = (Lmax - Lmin) / sideLength * (l + 1) + Lmin; double dLmin = (Lmax - Lmin) / sideLength * l + Lmin; for (int a = 0; a < sideLength; a++) { double dAmax = (Amax - Amin) / sideLength * (a + 1) + Amin; double dAmin = (Amax - Amin) / sideLength * a + Amin; for (int b = 0; b < sideLength; b++) { double dBmax = (Bmax - Bmin) / sideLength * (b + 1) + Bmin; double dBmin = (Bmax - Bmin) / sideLength * b + Bmin; int dSamples = numSamples; if (b == sideLength - 1 && a == sideLength - 1 && l == sideLength - 1) { //figure out leftovers dSamples = k - numSamples * (sideLength * sideLength * sideLength - 1); } for (int s = 0; s < dSamples; s++) { double L = random.NextDouble() * (dLmax - dLmin) + dLmin; double A = random.NextDouble() * (dAmax - dAmin) + dAmin; double B = random.NextDouble() * (dBmax - dBmin) + dBmin; CIELAB seed = new CIELAB(L, A, B); Cluster cluster = new Cluster(); cluster.id = i; cluster.lab = seed; seeds.Add(cluster); i++; } } } } } return(seeds); }
public void Reset() { lab = MeanColor(); count = 0; sumlab = new CIELAB(0, 0, 0); }
/** * Compute histograms for the given category, both clipart and regular **/ private void ComputeHistograms(Category category, bool loadIfPossible = true) { Stopwatch watch = new Stopwatch(); watch.Start(); Parallel.For(0, category.members.Count(), m => { String member = category.members[m]; //Get the relevant images String[] queryLabels = { "", "_clipart" }; foreach (String label in queryLabels) { String baseName = Encode(member) + label; String outFile = Path.Combine(cacheDir, baseName + ".txt"); String memberDir = Path.Combine(imageDir, baseName); if (File.Exists(outFile) && loadIfPossible) { Console.WriteLine("Reusing histogram for " + baseName); continue; } double[, ,] histogram = new double[Lbins, Abins, Bbins]; String[] files = Directory.GetFiles(memberDir); foreach (String f in files) { //TODO: maybe process the image here, or filter it out try { Bitmap image = ResizeImage(f); Color[,] imageRGB = Util.BitmapToArray(image); CIELAB[,] imageLAB = Util.Map <Color, CIELAB>(imageRGB, Util.RGBtoLAB); //Process the image bool valid = ProcessImage(imageRGB, imageLAB); if (!valid) { continue; } double pixelCount = 0; for (int i = 0; i < image.Width; i++) { for (int j = 0; j < image.Height; j++) { if (imageRGB[i, j].A >= 1) { pixelCount++; } } } if (pixelCount > 0) { for (int i = 0; i < image.Width; i++) { for (int j = 0; j < image.Height; j++) { if (imageRGB[i, j].A <= 1) { continue; } CIELAB lab = imageLAB[i, j];//Util.RGBtoLAB(imageRGB[i,j]); //update histogram int L = (int)Math.Floor((lab.L / (double)binSize) + 0.5); int A = (int)Math.Floor(((100 + lab.A) / (double)binSize) + 0.5); int B = (int)Math.Floor(((100 + lab.B) / (double)binSize) + 0.5); L = clamp(L, 0, Lbins - 1); A = clamp(A, 0, Abins - 1); B = clamp(B, 0, Bbins - 1); histogram[L, A, B] += 1.0 / pixelCount; } } } //cleanup image.Dispose(); } catch (Exception) { Console.WriteLine("Could not process image " + f); } } //save the histogram to a file List <String> lines = new List <String>(); for (int i = 0; i < Lbins; i++) { for (int j = 0; j < Abins; j++) { for (int k = 0; k < Bbins; k++) { lines.Add(histogram[i, j, k].ToString()); } } } File.WriteAllLines(outFile, lines.ToArray <String>()); } }); watch.Stop(); Console.WriteLine("Done with histograms Time: " + watch.ElapsedMilliseconds / 1000.0); }
private double[] GetProbabilities(String query, Func <CIELAB, CIELAB, double> distFunc, Kernel kernel, double whiteThresh = 20) { CIELAB white = new CIELAB(100, 0, 0); //load the histogram double[, ,] hist = new double[Lbins, Abins, Bbins]; String histFile = Path.Combine(cacheDir, query) + ".txt"; String[] hlines = File.ReadAllLines(histFile); for (int i = 0; i < hlines.Count(); i++) { int L = i / (Abins * Bbins); int plane = (i % (Abins * Bbins)); int A = plane / Bbins; int B = plane % Bbins; hist[L, A, B] = Double.Parse(hlines[i]); } int ncolors = paletteLAB.Count(); double[] freq = new double[20]; Parallel.For(0, ncolors, l => { double count = 0; for (int i = 0; i < Lbins; i++) { for (int j = 0; j < Abins; j++) { for (int k = 0; k < Bbins; k++) { double val = hist[i, j, k]; System.Diagnostics.Debug.Assert(!double.IsNaN(val)); CIELAB lab = new CIELAB(i * binSize, j * binSize - 100, k * binSize - 100); if (white.SqDist(lab) < whiteThresh * whiteThresh) { continue; } if (val <= 0) { continue; } count += val; freq[l] += val * kernel.Eval(distFunc(paletteLAB[l], lab)); } } } if (count > 0) { freq[l] /= count; } }); //now renormalize double totalFreq = 0; for (int i = 0; i < freq.Count(); i++) { totalFreq += freq[i]; } //this should only happen if the histogram is empty or has no valid bins if (totalFreq == 0) { totalFreq = 1; } for (int i = 0; i < freq.Count(); i++) { freq[i] /= totalFreq; } return(freq); }
public static double CMeansPicture(List <CIELAB> colors, List <Cluster> seeds, int m = 2) { //cluster colors given seeds //return score List <double> weights = new List <double>(); double[,] memberships = new double[colors.Count(), seeds.Count()]; //pixel to cluster int numSeeds = seeds.Count(); int numColors = colors.Count(); int maxIters = 50;//100; double epsilon = 0.0001; double J = Double.PositiveInfinity; int changes = 0; for (int t = 0; t < maxIters; t++) { changes = 0; for (int i = 0; i < colors.Count(); i++) { //calculate the memberships double[] dists = new double[numSeeds]; double factor = 0; for (int k = 0; k < numSeeds; k++) { dists[k] = Math.Max(epsilon, Math.Pow(Math.Sqrt(colors[i].SqDist(seeds[k].lab)), 2.0 / (m - 1))); factor += (1.0 / dists[k]); } for (int k = 0; k < numSeeds; k++) { double oldval = memberships[i, k]; memberships[i, k] = 1.0 / (dists[k] * factor); if (oldval != memberships[i, k]) { changes++; } } } //update the centers for (int k = 0; k < numSeeds; k++) { CIELAB center = new CIELAB(); double total = 0; for (int i = 0; i < numColors; i++) { double u = Math.Pow(memberships[i, k], m); center += colors[i] * u; total += u; } center = center / total; seeds[k].lab = center; } //find J double thisJ = 0; for (int i = 0; i < numColors; i++) { for (int k = 0; k < numSeeds; k++) { double u = memberships[i, k]; thisJ += Math.Pow(u, m) * Math.Max(epsilon, seeds[k].lab.SqDist(colors[i])); } } if (thisJ >= J) { break; } J = thisJ; if (changes == 0) { break; } } return(J); }
private void RemoveBackground(Color[,] image, CIELAB[,] imageLAB) { //check perimeter to see if it's mostly black or white //RGB to LAB CIELAB black = new CIELAB(0, 0, 0); CIELAB white = new CIELAB(100, 0, 0); int width = image.GetLength(0); int height = image.GetLength(1); CIELAB[,] labs = imageLAB;//Util.Map<Color, CIELAB>(image, (c) => Util.RGBtoLAB(c)); int numBlack = 0; int numWhite = 0; int thresh = 3 * 3; List <Point> perimeterIdx = new List <Point>(); double totalPerimeter = 4 * width + 4 * height; double bgThresh = totalPerimeter * 0.75; for (int i = 0; i < width; i++) { //top for (int j = 0; j < 2; j++) { if (black.SqDist(labs[i, j]) < thresh) { numBlack++; } if (white.SqDist(labs[i, j]) < thresh) { numWhite++; } perimeterIdx.Add(new Point(i, j)); } //bottom for (int j = height - 2; j < height; j++) { perimeterIdx.Add(new Point(i, j)); if (black.SqDist(labs[i, j]) < thresh) { numBlack++; } if (white.SqDist(labs[i, j]) < thresh) { numWhite++; } } } for (int j = 0; j < height; j++) { //left for (int i = 0; i < 2; i++) { perimeterIdx.Add(new Point(i, j)); if (black.SqDist(labs[i, j]) < thresh) { numBlack++; } if (white.SqDist(labs[i, j]) < thresh) { numWhite++; } } //right for (int i = width - 2; i < width; i++) { perimeterIdx.Add(new Point(i, j)); if (black.SqDist(labs[i, j]) < thresh) { numBlack++; } if (white.SqDist(labs[i, j]) < thresh) { numWhite++; } } } if (numBlack >= bgThresh || numWhite >= bgThresh) { //connected components UnionFind <CIELAB> uf = new UnionFind <CIELAB>((a, b) => a.SqDist(b) < thresh); int[,] cc = uf.ConnectedComponents(labs); SortedSet <int> ids = new SortedSet <int>(); //go around the perimeter to collect the right ids foreach (Point p in perimeterIdx) { if (numWhite > numBlack) { if (labs[p.X, p.Y].SqDist(white) < thresh) { ids.Add(cc[p.X, p.Y]); } } else { if (labs[p.X, p.Y].SqDist(black) < thresh) { ids.Add(cc[p.X, p.Y]); } } } //fill the bitmap with transparency for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { if (ids.Contains(cc[i, j])) { image[i, j] = Color.FromArgb(0, 0, 0, 0); } } } } }
private double[,] ComputeAffinities(Category category, double clipartPrior = 0.7, double saturationThresh = 0.1, double sigma = 0.2, double whiteThresh = 20) { //get the color probabilities for regular and clipart queries double[,] pcw_regular = new double[paletteHex.Count(), category.members.Count()]; double[,] pcw_clipart = new double[paletteHex.Count(), category.members.Count()]; double[,] pcw = new double[paletteHex.Count(), category.members.Count()]; Parallel.For(0, category.members.Count(), w => { String query = Encode(category.members[w]); double[] pc_regular = GetProbabilities(query, GetCNDist, new GaussianKernel(sigma), whiteThresh); double[] pc_clipart = GetProbabilities(query + "_clipart", GetCNDist, new GaussianKernel(sigma), whiteThresh); for (int c = 0; c < paletteLAB.Count(); c++) { pcw_regular[c, w] = pc_regular[c]; pcw_clipart[c, w] = pc_clipart[c]; } }); Console.WriteLine("Done computing probabilities"); //compute combined probability for (int w = 0; w < category.members.Count(); w++) { double rentropy = 0; double centropy = 0; for (int c = 0; c < paletteHex.Count(); c++) { if (pcw_clipart[c, w] > 0) { centropy += pcw_clipart[c, w] * Math.Log(pcw_clipart[c, w]); } if (pcw_regular[c, w] > 0) { rentropy += pcw_regular[c, w] * Math.Log(pcw_regular[c, w]); } } centropy *= -1; rentropy *= -1; //avoid divide by zero centropy = Math.Max(centropy, epsilon); rentropy = Math.Max(rentropy, epsilon); double cw = clipartPrior / centropy; double rw = (1 - clipartPrior) / rentropy; for (int c = 0; c < paletteHex.Count(); c++) { CIELAB lab = paletteLAB[c]; double chroma = Math.Sqrt(lab.A * lab.A + lab.B * lab.B); double saturation = chroma / Math.Max(Math.Sqrt(chroma * chroma + lab.L * lab.L), epsilon); pcw[c, w] = Math.Max(saturation, saturationThresh) * (cw * pcw_clipart[c, w] + rw * pcw_regular[c, w]); } } //renormalize double[] memberSums = new double[category.members.Count()]; for (int c = 0; c < paletteHex.Count(); c++) { for (int w = 0; w < category.members.Count(); w++) { memberSums[w] += pcw[c, w]; } } for (int c = 0; c < paletteHex.Count(); c++) { for (int w = 0; w < category.members.Count(); w++) { pcw[c, w] /= memberSums[w]; } } //now compute affinities from the combined probabilities double[,] affinities = new double[paletteHex.Count(), category.members.Count()]; double[,] pwc = new double[category.members.Count(), paletteHex.Count()]; //compute p(w|c) //p(w|c) = p(c|w)*p(w)/p(c) double colorZ = 0; double[] colorSums = new double[paletteHex.Count()]; for (int c = 0; c < paletteHex.Count(); c++) { for (int w = 0; w < category.members.Count(); w++) { colorSums[c] += pcw[c, w]; colorZ += pcw[c, w]; } } for (int c = 0; c < paletteHex.Count(); c++) { for (int w = 0; w < category.members.Count(); w++) { pwc[w, c] = pcw[c, w] * (1.0 / category.members.Count()) / (Math.Max(colorSums[c], epsilon) / colorZ); } } double minScore = Double.PositiveInfinity; double maxScore = Double.NegativeInfinity; //balance with entropy H(w|c) for (int c = 0; c < paletteHex.Count(); c++) { double Hwc = 0; for (int w = 0; w < category.members.Count(); w++) { if (pwc[w, c] > 0) { Hwc += pwc[w, c] * Math.Log(pwc[w, c]); } } Hwc *= -1; System.Diagnostics.Debug.Assert(!double.IsNaN(Hwc)); for (int w = 0; w < category.members.Count(); w++) { affinities[c, w] = pcw[c, w] / Math.Max(Hwc, epsilon); minScore = Math.Min(minScore, affinities[c, w]); maxScore = Math.Max(maxScore, affinities[c, w]); } } System.Diagnostics.Debug.Assert(maxScore != minScore); //scale the affinities between 0 and 1s (easier to visualize) for (int c = 0; c < paletteHex.Count(); c++) { for (int w = 0; w < category.members.Count(); w++) { affinities[c, w] = (affinities[c, w] - minScore) / (maxScore - minScore); if (Double.IsNaN(affinities[c, w])) { throw new FormatException("Affinity is NaN! Affinity " + category.members[w] + " " + paletteHex[c]); } } } Console.WriteLine("Done computing affinities"); return(affinities); }
private CIELAB[,] EdgeUnmixRecolor(CIELAB[,] image, PaletteData palette) { int width = image.GetLength(0); int height = image.GetLength(1); CIELAB[,] result = new CIELAB[width, height]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { double[] scores = new double[palette.colors.Count()]; //check if the color is in the palette CIELAB color = image[i, j]; if (palette.lab.Contains(color)) { result[i, j] = color; continue; } //otherwise, find the best color //see if this is an edge //if it is, assign the color to be one of the edge color candidates HashSet <CIELAB> tbCandidates = new HashSet <CIELAB>(); HashSet <CIELAB> lrCandidates = new HashSet <CIELAB>(); List <HashSet <CIELAB> > unmixCandidates = new List <HashSet <CIELAB> > { tbCandidates, lrCandidates }; //check edge types //horizontal and vertical HashSet <CIELAB> oneSide = new HashSet <CIELAB>(); HashSet <CIELAB> otherSide = new HashSet <CIELAB>(); Point[] top = new Point[] { new Point(i - 1, j - 1), new Point(i, j - 1), new Point(i + 1, j - 1) }; Point[] bottom = new Point[] { new Point(i - 1, j + 1), new Point(i - 1, j), new Point(i - 1, j + 1) }; Point[] left = new Point[] { new Point(i - 1, j - 1), new Point(i - 1, j), new Point(i - 1, j + 1) }; Point[] right = new Point[] { new Point(i + 1, j - 1), new Point(i + 1, j), new Point(i + 1, j + 1) }; List <Point[]> oneCompare = new List <Point[]> { top, left }; List <Point[]> otherCompare = new List <Point[]> { bottom, right }; bool edge = false; for (int c = 0; c < oneCompare.Count(); c++) { oneSide.Clear(); otherSide.Clear(); foreach (Point p in oneCompare[c]) { if (Util.InBounds(p.X, p.Y, width, height)) { CIELAB rc = ClosestColor(image[p.X, p.Y], palette.lab); //check if in the set if (oneSide.Contains(rc)) { //yes, we found a majority unmixCandidates[c].Add(image[p.X, p.Y]); break; } else { oneSide.Add(rc); } } } foreach (Point p in otherCompare[c]) { if (Util.InBounds(p.X, p.Y, width, height)) { CIELAB rc = ClosestColor(image[p.X, p.Y], palette.lab); //check if in the set if (otherSide.Contains(rc)) { //yes, we found a majority unmixCandidates[c].Add(rc); break; } else { otherSide.Add(rc); } } } //is it an edge? if (unmixCandidates[c].Count() >= 2) { result[i, j] = ClosestColor(image[i, j], unmixCandidates[c].ToList <CIELAB>()); edge = true; break; } } //TODO: //45 degrees //45 degrees-flipped if (!edge) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { int x = i + dx; int y = j + dy; double weight = (dx == 0 && dy == 0) ? 4 : 1; if (Util.InBounds(x, y, width, height)) { int bestc = ClosestColorIndex(image[x, y], palette.lab); scores[bestc] -= weight; } } } //pick the color with the min score double minScore = Double.PositiveInfinity; int bestIdx = 0; for (int c = 0; c < palette.colors.Count(); c++) { if (scores[c] < minScore) { minScore = scores[c]; bestIdx = c; } } result[i, j] = palette.lab[bestIdx]; } } } return(result); }