// Return whether the image is mostly dark. This is done by counting the number of pixels that are // below a certain tolerance (i.e., mostly black), and seeing if the % of mostly dark pixels are // at or higher than the given darkPercent. // We also check to see if the image is predominantly color. We do this by checking to see if pixels are grey scale // (where r=g=b, with a bit of slop added) and then check that against a threshold. public static bool IsDark(BitmapSource bmap, int darkPixelThreshold, double darkPixelRatio, out double darkPixelRatioFound, out bool isColor) { PixelBitmap image1 = new PixelBitmap(bmap); int dark_counted = 0; int total_counted = 0; int color_slop = 40; // A grey scale pixel has r = g = b. But we will allow some slop in here just in case a bit of color creeps in int grey_scale_counted = 0; double greyScalePixelRatio = .9; // A greyscale image (given the above slop) will typically have about 90% of its pixels as grey scale const int SKIPPIXELS = 20; for (int i = 0; i < image1.Pixels.Length; i += SKIPPIXELS) // Check only every 20 pixels, as otherwise its a very expensive operation { byte b = (byte)image1.Pixels[i].Blue; byte g = (byte)image1.Pixels[i].Green; byte r = (byte)image1.Pixels[i].Red; total_counted++; // The numbers below convert a particular color to its greyscale equivalent // Colors are not weighted equally. Since pure green is lighter than pure red and pure blue, it has a higher weight. // Pure blue is the darkest of the three, so it receives the least weight. byte brightness = (byte)Math.Round((0.299 * r + +0.5876 * g + 0.114 * b)); if (brightness <= darkPixelThreshold) dark_counted++; // Check if the pixel is a grey scale vs. color pixel. Note the heuristic. // Normally we should check r = g = b, but we allow a bit of slop as some cameras actually // have a bit of color in their dark shots (don't ask me why, it just happens). // i.e. if the total delta is less than the color slop, then we consider it a grey level. if (GetRgbDelta(r, g, b) <= color_slop) grey_scale_counted++; } // Check if its a grey scale image, i.e., at least 90% of the pixels in this image (given this slop) are grey scale. // If not, its a color image so judge it as not dark double greyScalePixelRatioFound = (1d * grey_scale_counted / total_counted); if (greyScalePixelRatioFound < greyScalePixelRatio) { darkPixelRatioFound = 1 - greyScalePixelRatioFound; isColor = true; return false; } darkPixelRatioFound = (1d * dark_counted / total_counted); isColor = false; return (darkPixelRatioFound >= darkPixelRatio); }
// TODO: This needs to be fixed. public void ViewDifferencesCombined() { // If we are in any state other than the unaltered state, go to the unaltered state, otherwise the combined diff state if (whichImageState == (int)whichImage.NextDiff || whichImageState == (int)whichImage.PreviousDiff || whichImageState == (int) whichImage.CombinedDiff) { whichImageState = (int)whichImage.Unaltered; } else { whichImageState = (int)whichImage.CombinedDiff; } // If we are on the unaltered image if (whichImageState == (int)whichImage.Unaltered) { this.markableCanvas.imgToDisplay.Source = this.cachedImages[whichImageState]; this.markableCanvas.imgToMagnify.Source = this.cachedImages[whichImageState]; StatusBarUpdate.ClearMessage(this.statusBar); return; } // If we are on the first image, or the last image, then don't do anything if (this.dbData.CurrentRow == 0 || this.dbData.CurrentRow == dbData.ImageCount - 1) { whichImageState = (int) whichImage.Unaltered; StatusBarUpdate.Message(this.statusBar, "Can't show combined differences without three good images"); return; } // If any of the images are corrupted, then don't do anything if (!dbData.RowIsImageDisplayable() || !dbData.RowIsImageDisplayable(dbData.CurrentRow + 1) || !dbData.RowIsImageDisplayable(dbData.CurrentRow - 1)) { whichImageState = (int) whichImage.Unaltered; StatusBarUpdate.Message(this.statusBar, "Can't show combined differences without three good images"); return; } if (null == this.cachedImages[whichImageState]) { // We need three valid images: the current one, the previous one, and the next one. // The current image is always in the cache. Create a PixeBitmap from it PixelBitmap currImage = new PixelBitmap((BitmapSource)cachedImages[(int)whichImage.Unaltered]); // Get the previous and next image int idx = dbData.CurrentRow - 1; string path = System.IO.Path.Combine(this.FolderPath, dbData.RowGetValueFromType((string)this.dbData.DataLabelFromType[Constants.FILE], idx)); if (!File.Exists(path)) { StatusBarUpdate.Message(this.statusBar, "Can't show combined differences without three good images"); return; } BitmapImage prevImage = new BitmapImage(new Uri(path)); idx = dbData.CurrentRow + 1; ; path = System.IO.Path.Combine(this.FolderPath, dbData.RowGetValueFromType((string)this.dbData.DataLabelFromType[Constants.FILE], idx)); if (!File.Exists(path)) { StatusBarUpdate.Message(this.statusBar, "Can't show combined differences without three good images"); return; } BitmapImage nextImage = new BitmapImage(new Uri(path)); // Generate the differenced image and dislay it PixelBitmap differencedImage = PixelBitmap.Difference(cachedImages[(int)whichImage.Unaltered], prevImage, nextImage, differenceThreshold); this.cachedImages[whichImageState] = differencedImage.ToBitmap(); } whichImageState = (int)whichImage.CombinedDiff; this.markableCanvas.imgToDisplay.Source = this.cachedImages[whichImageState]; StatusBarUpdate.Message(this.statusBar, "Viewing surrounding differences"); }
// Given three images, return an image that highlights the differences in common betwen the main image and the first image, // and the main image and a second image. //public static PixelBitmap Difference(PixelBitmap mainImg, PixelBitmap img1, PixelBitmap img2, byte threshold) public static PixelBitmap Difference(BitmapSource mainImgBM, BitmapSource img1BM, BitmapSource img2BM, byte threshold) { PixelBitmap mainImg = new PixelBitmap((BitmapSource)mainImgBM); PixelBitmap img1 = new PixelBitmap((BitmapSource)img1BM); PixelBitmap img2 = new PixelBitmap((BitmapSource)img2BM); // if images are not same size , use the smaller of their dimensions int width = Math.Min(mainImg.Width, img1.Width); width = Math.Min(width, img2.Width); int height = Math.Min(mainImg.Height, img1.Height); height = Math.Min(height, img2.Height); PixelColor[] pixelColor = new PixelColor[width * height]; //CHECK THIS - BOUNDS MAY NOT BE RIGHT for (int i = 0; i < pixelColor.Length; i++) { byte b1 = (byte)Math.Abs(mainImg.Pixels[i].Blue - img1.Pixels[i].Blue); byte b2 = (byte)Math.Abs(mainImg.Pixels[i].Blue - img2.Pixels[i].Blue); byte b = PixelBitmap.TempCalc(threshold, b1, b2); byte g1 = (byte)Math.Abs(mainImg.Pixels[i].Green - img1.Pixels[i].Green); byte g2 = (byte)Math.Abs(mainImg.Pixels[i].Green - img2.Pixels[i].Green); byte g = PixelBitmap.TempCalc(threshold, g1, g2); byte r1 = (byte)Math.Abs(mainImg.Pixels[i].Red - img1.Pixels[i].Red); byte r2 = (byte)Math.Abs(mainImg.Pixels[i].Red - img2.Pixels[i].Red); byte r = PixelBitmap.TempCalc(threshold, r1, r2); byte a = byte.MaxValue; // opaque var diff = (byte)(b / 3 + g / 3 + r / 3); var pixel = new PixelColor() { Alpha = a, Red = diff, Blue = diff, Green = diff }; pixelColor[i] = pixel; } return new PixelBitmap() { Width = width, Height = height, Pixels = pixelColor }; }
// Cycle through the image enhancements in the order current, then previous and next differenced images. // Create the differenced image if needed // For display efficiency, cache the differenced image. private void ViewDifferencesCycleThrough() { // Note: No matter what image we are viewing, the source image will have already been cached before entering this function // Go to the next image in the cycle we want to show. NextInCycle(); // If we are supposed to display the unaltered image, do it and get out of here. // The unaltered image will always be cached at this point, so there is no need to check. if (whichImageState == (int) whichImage.Unaltered) { this.markableCanvas.imgToMagnify.Source = cachedImages[(int) whichImage.Unaltered]; this.markableCanvas.imgToDisplay.Source = cachedImages[(int) whichImage.Unaltered]; // Check if its a corrupted image if (!dbData.RowIsImageDisplayable()) //TO DO AS WE MAY HAVE TO GET THE INDEX OF THE NEXT IN CYCLE IMAGE??? { StatusBarUpdate.Message(this.statusBar, "Image is corrupted"); } else { StatusBarUpdate.ClearMessage(this.statusBar); } return; } // If we don't have the cached difference image, generate and cache it. if (cachedImages[whichImageState] == null) { // Decide which comparison image to use for differencing. int idx; if ( whichImageState == (int)whichImage.PreviousDiff ) { idx = this.dbData.CurrentRow - 1; // Find the previous image (unless we are already at the beginning) if (idx < 0) idx = this.dbData.CurrentRow; } else { idx = this.dbData.CurrentRow + 1; if (idx >= dbData.ImageCount) idx = this.dbData.CurrentRow; } // Generate the differenced image. string fullFileName = System.IO.Path.Combine(this.FolderPath, this.dbData.RowGetValueFromDataLabel((string) this.dbData.DataLabelFromType [Constants.FILE], idx)); // Check if that file actually exists if (!File.Exists (fullFileName)) { StatusBarUpdate.Message(this.statusBar, "Difference Image is missing"); return; } var otherImage = new BitmapImage(new Uri(fullFileName)); var image1 = new PixelBitmap((BitmapSource)cachedImages[(int)whichImage.Unaltered]); var image2 = new PixelBitmap((BitmapSource)otherImage); var difference = image1 - image2; BitmapSource img = difference.ToBitmap(); // and now cache the differenced image cachedImages[(int)whichImageState] = (BitmapSource)img; } // display the differenced image this.markableCanvas.imgToDisplay.Source = cachedImages[whichImageState]; StatusBarUpdate.Message(this.statusBar, "Viewing " + ( (whichImageState == (int) whichImage.PreviousDiff) ? "previous" : "next") + " differenced image"); }