/// <summary> /// Gets the bar width distribution and calculates narrow bar width over the specified /// range of the histogramResult. A histogramResult could have multiple ranges, separated /// by quiet zones. /// </summary> /// <param name="hist">histogramResult data</param> /// <param name="iStart">start coordinate to be considered</param> /// <param name="iEnd">end coordinate + 1</param> private static void GetBarWidthDistribution(ref histogramResult hist, int iStart, int iEnd) { HybridDictionary hdLightBars = new HybridDictionary(); HybridDictionary hdDarkBars = new HybridDictionary(); bool bDarkBar = (hist.histogram[iStart] <= hist.threshold); int iBarStart = 0; for (int i = iStart + 1; i < iEnd; i++) { bool bDark = (hist.histogram[i] <= hist.threshold); if (bDark != bDarkBar) { int iBarWidth = i - iBarStart; if (bDarkBar) { if (!hdDarkBars.Contains(iBarWidth)) hdDarkBars.Add(iBarWidth, 1); else hdDarkBars[iBarWidth] = (int)hdDarkBars[iBarWidth] + 1; } else { if (!hdLightBars.Contains(iBarWidth)) hdLightBars.Add(iBarWidth, 1); else hdLightBars[iBarWidth] = (int)hdLightBars[iBarWidth] + 1; } bDarkBar = bDark; iBarStart = i; } } // Now get the most common bar widths CalcNarrowBarWidth(hdLightBars, out hist.lightnarrowbarwidth, out hist.lightwiderbarwidth); CalcNarrowBarWidth(hdDarkBars, out hist.darknarrowbarwidth, out hist.darkwiderbarwidth); }
/// <summary> /// FindBarcodeZones looks for barcode zones in the current band. /// We look for white space that is more than GAPFACTOR * narrowbarwidth /// separating two zones. For narrowbarwidth we take the maximum of the /// dark and light narrow bar width. /// </summary> /// <param name="hist">Data for current image band</param> private static void FindBarcodeZones(ref histogramResult hist) { if (!ValidBars(ref hist)) hist.zones = null; else if (!UseBarcodeZones) { hist.zones = new BarcodeZone[1]; hist.zones[0].Start = 0; hist.zones[0].End = hist.histogram.Length; } else { ArrayList alBarcodeZones = new ArrayList(); bool bDarkBar = (hist.histogram[0] <= hist.threshold); int iBarStart = 0; int iZoneStart = -1; int iZoneEnd = -1; float fQuietZoneWidth = GAPFACTOR * (hist.darknarrowbarwidth + hist.lightnarrowbarwidth) / 2; float fMinZoneWidth = fQuietZoneWidth; for (int i = 1; i < hist.histogram.Length; i++) { bool bDark = (hist.histogram[i] <= hist.threshold); if (bDark != bDarkBar) { int iBarWidth = i - iBarStart; if (!bDarkBar) // This ends a light area { if ((iZoneStart == -1) || (iBarWidth > fQuietZoneWidth)) { // the light area can be seen as a quiet zone iZoneEnd = i - (iBarWidth >> 1); // Check if the active zone is big enough to contain a barcode if ((iZoneStart >= 0) && (iZoneEnd > iZoneStart + fMinZoneWidth)) { // record the barcode zone that ended in the detected quiet zone ... BarcodeZone bz = new BarcodeZone(); bz.Start = iZoneStart; bz.End = iZoneEnd; alBarcodeZones.Add(bz); // .. and start a new barcode zone iZoneStart = iZoneEnd; } if (iZoneStart == -1) iZoneStart = iZoneEnd; // first zone starts here } } bDarkBar = bDark; iBarStart = i; } } if (iZoneStart >= 0) { BarcodeZone bz = new BarcodeZone(); bz.Start = iZoneStart; bz.End = hist.histogram.Length; alBarcodeZones.Add(bz); } if (alBarcodeZones.Count > 0) hist.zones = (BarcodeZone[])alBarcodeZones.ToArray(typeof(BarcodeZone)); else hist.zones = null; } }
/// <summary> /// Scans for patterns of bars and returns them encoded as strings in the passed /// string builder parameters. /// </summary> /// <param name="hist">Input data containing picture information for the scan line</param> /// <param name="sbCode39Pattern">Returns string containing "w" for wide bars and "n" for narrow bars</param> /// <param name="sbEANPattern">Returns string with numbers designating relative bar widths compared to /// narrowest bar: "1" to "4" are valid widths that can be present in an EAN barcode</param> /// <remarks>In both output strings, "|"-characters will be inserted to indicate gaps /// in the input data.</remarks> private static void GetBarPatterns(ref histogramResult hist, out StringBuilder sbCode39Pattern, out StringBuilder sbEANPattern) { // Initialize return data sbCode39Pattern = new StringBuilder(); sbEANPattern = new StringBuilder(); if (hist.zones != null) // if barcode zones were found along the scan line { for (int iZone = 0; iZone < hist.zones.Length; iZone++) { // Recalculate bar width distribution if more than one zone is present, it could differ per zone if (hist.zones.Length > 1) GetBarWidthDistribution(ref hist, hist.zones[iZone].Start, hist.zones[iZone].End); // Check the calculated narrow bar widths. If they are very different, the pattern is // unlikely to be a bar code if (ValidBars(ref hist)) { // add gap separator to output patterns sbCode39Pattern.Append("|"); sbEANPattern.Append("|"); // Variables needed to check for int iBarStart = 0; bool bDarkBar = (hist.histogram[0] <= hist.threshold); // Find the narrow and wide bars for (int i = 1; i < hist.histogram.Length; ++i) { bool bDark = (hist.histogram[i] <= hist.threshold); if (bDark != bDarkBar) { int iBarWidth = i - iBarStart; float fNarrowBarWidth = bDarkBar ? hist.darknarrowbarwidth : hist.lightnarrowbarwidth; float fWiderBarWidth = bDarkBar ? hist.darkwiderbarwidth : hist.lightwiderbarwidth; if (IsWideBar(iBarWidth, fNarrowBarWidth, fWiderBarWidth)) { // The bar was wider than the narrow bar width, it's a wide bar or a gap if (iBarWidth > GAPFACTOR * fNarrowBarWidth) { sbCode39Pattern.Append("|"); sbEANPattern.Append("|"); } else { sbCode39Pattern.Append("w"); AppendEAN(sbEANPattern, iBarWidth, fNarrowBarWidth); } } else { // The bar is a narrow bar sbCode39Pattern.Append("n"); AppendEAN(sbEANPattern, iBarWidth, fNarrowBarWidth); } bDarkBar = bDark; iBarStart = i; } } } } } }
/// <summary> /// Checks if dark and light narrow bar widths are in agreement. /// </summary> /// <param name="hist">barcode data</param> /// <returns>true if barcode data is valid</returns> private static bool ValidBars(ref histogramResult hist) { float fCompNarrowBarWidths = hist.lightnarrowbarwidth / hist.darknarrowbarwidth; float fCompWiderBarWidths = hist.lightwiderbarwidth / hist.darkwiderbarwidth; return ((fCompNarrowBarWidths >= 0.5) && (fCompNarrowBarWidths <= 2.0) && (fCompWiderBarWidths >= 0.5) && (fCompWiderBarWidths <= 2.0) && (hist.darkwiderbarwidth / hist.darknarrowbarwidth >= 1.5) && (hist.lightwiderbarwidth / hist.lightnarrowbarwidth >= 1.5)); }
/// <summary> /// Vertical histogram of an image /// </summary> /// <param name="bmp">Bitmap</param> /// <param name="start">Start coordinate of band to be scanned</param> /// <param name="end">End coordinate of band to be scanned</param> /// <param name="direction"> /// ScanDirection.Vertical: start and end denote y-coordinates. /// ScanDirection.Horizontal: start and end denote x-coordinates. /// </param> /// <returns>histogramResult, containing average brightness values across the scan line</returns> private static histogramResult verticalHistogram(Bitmap bmp, int start, int end, ScanDirection direction) { // convert the pixel format of the bitmap to something that we can handle PixelFormat pf = CheckSupportedPixelFormat(bmp.PixelFormat); BitmapData bmData; int xMax, yMax; if (direction == ScanDirection.Horizontal) { bmData = bmp.LockBits(new Rectangle(start, 0, end - start, bmp.Height), ImageLockMode.ReadOnly, pf); xMax = bmData.Height; yMax = end - start; } else { bmData = bmp.LockBits(new Rectangle(0, start, bmp.Width, end - start), ImageLockMode.ReadOnly, pf); xMax = bmp.Width; yMax = bmData.Height; } // Create the return value byte[] histResult = new byte[xMax + 2]; // add 2 to simulate light-colored background pixels at sart and end of scanline ushort[] vertSum = new ushort[xMax]; unsafe { byte* p = (byte*)(void*)bmData.Scan0; int stride = bmData.Stride; // stride is offset between horizontal lines in p for (int y = 0; y < yMax; ++y) { // Add up all the pixel values vertically for (int x = 0; x < xMax; ++x) { if (direction == ScanDirection.Horizontal) vertSum[x] += getpixelbrightness(p, pf, stride, y, x); else vertSum[x] += getpixelbrightness(p, pf, stride, x, y); } } } bmp.UnlockBits(bmData); // Now get the average of the row by dividing the pixel by num pixels int iDivider = end - start; if (pf != PixelFormat.Format1bppIndexed) iDivider *= 3; byte maxValue = byte.MinValue; // Start the max value at zero byte minValue = byte.MaxValue; // Start the min value at the absolute maximum for (int i = 1; i <= xMax; i++) // note: intentionally skips first pixel in histResult { histResult[i] = (byte)(vertSum[i - 1] / iDivider); //Save the max value for later if (histResult[i] > maxValue) maxValue = histResult[i]; // Save the min value for later if (histResult[i] < minValue) minValue = histResult[i]; } // Set first and last pixel to "white", i.e., maximum intensity histResult[0] = maxValue; histResult[xMax + 1] = maxValue; histogramResult retVal = new histogramResult(); retVal.histogram = histResult; retVal.max = maxValue; retVal.min = minValue; // Now we have the brightness distribution along the scan band, try to find the distribution of bar widths. retVal.threshold = (byte)(minValue + ((maxValue - minValue) >> 1)); GetBarWidthDistribution(ref retVal, 0, retVal.histogram.Length); // Now that we know the narrow bar width, lets look for barcode zones. // The image could have more than one barcode in the same band, with // different bar widths. FindBarcodeZones(ref retVal); return retVal; }
/// <summary> /// Vertical histogram of an image /// </summary> /// <param name="bmp">the bitmap to be processed</param> /// <param name="showDiagnostics">if true, draws an overlay on the image</param> /// <param name="diagnosticsColor">the color of the overlay</param> /// <returns>a histogramResult representing the vertical histogram</returns> private static histogramResult verticalHistogram(Bitmap bmp, bool showDiagnostics, Color diagnosticsColor) { // Create the return value float[] histResult = new float[bmp.Width]; float[] vertSum = new float[bmp.Width]; // Start the max value at zero float maxValue = 0; // Start the min value at the absolute maximum float minValue = 255; // GDI+ still lies to us - the return format is BGR, NOT RGB. BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0; unsafe { byte* p = (byte*)(void*)Scan0; int nOffset = stride - bmp.Width * 3; int nWidth = bmp.Width * 3; for (int y = 0; y < bmp.Height; ++y) { for (int x = 0; x < bmp.Width; ++x) { // Add up all the pixel values vertically (average the R,G,B channels) vertSum[x] += ((p[0] + (p + 1)[0] + (p + 2)[0]) / 3); p += 3; } p += nOffset; } } bmp.UnlockBits(bmData); // Now get the average of the row by dividing the pixel by num pixels for (int i = 0; i < bmp.Width; i++) { histResult[i] = (vertSum[i] / (bmp.Height)); //Save the max value for later if (histResult[i] > maxValue) maxValue = histResult[i]; // Save the min value for later if (histResult[i] < minValue) minValue = histResult[i]; } // Use GDI+ to draw a lines up the image showing each lines average if (showDiagnostics) { // Set the alpha of the overlay to half transparency int alpha = (int)255 / 2; Pen p = new Pen(Color.FromArgb(alpha, diagnosticsColor)); //Get the graphics to draw on Graphics g; g = Graphics.FromImage(bmp); for (int i = 0; i < bmp.Width; ++i) { // Normalize the drawn histogram to the height of the image int drawLength = (int)(histResult[i] * (bmp.Height / maxValue)); g.DrawLine(p, i, bmp.Height, i, (bmp.Height - (int)drawLength)); } p.Dispose(); g.Dispose(); } histogramResult retVal = new histogramResult(); retVal.histogram = histResult; retVal.max = maxValue; retVal.min = minValue; return retVal; }
public static string ReadCode39(Bitmap bmp, bool showDiagnostics, Color diagnosticsColor) { // To find a horizontal barcode, find the vertical histogram to find individual barcodes, // then get the vertical histogram to decode each histogramResult vertHist = verticalHistogram(bmp, showDiagnostics, diagnosticsColor); // Set the threshold for determining dark/light bars to half way between the histograms min/max float threshold = vertHist.min + ((vertHist.max - vertHist.min) / 2); // Variables needed to check for string patternString = null; int nBarStart = -1; int nNarrowBarWidth = -1; bool bDarkBar = false; // Find the narrow and wide bars for (int i = 0; i < vertHist.histogram.Length; ++i) { // First find the narrow bar width if (nNarrowBarWidth < 0) { if (nBarStart < 0) { // The code doesn't start until we see a dark bar if (vertHist.histogram[i] <= threshold) { // We detected a dark bar, save it's start position nBarStart = i; } } else { if (vertHist.histogram[i] > threshold) { // We detected the end of first the dark bar, save the narrow bar width and // start the rest of the barcode nNarrowBarWidth = i - nBarStart + 1; patternString += "n"; nBarStart = i; bDarkBar = false; } } } else { if (bDarkBar) { // We're on a dark bar, detect when the bar becomes light again if (vertHist.histogram[i] > threshold) { if ((i - nBarStart) > (nNarrowBarWidth)) { // The light bar was wider than the narrow bar width, it's a wide bar patternString += "w"; nBarStart = i; } else { // The light bar is a narrow bar patternString += "n"; nBarStart = i; } bDarkBar = false; } } else { // We're on a light bar, detect when the bar becomes dark if (vertHist.histogram[i] <= threshold) { if ((i - nBarStart) > (nNarrowBarWidth)) { // The dark bar was wider than the narrow bar width, it's a wide bar patternString += "w"; nBarStart = i; } else { // The dark bar is a narrow bar patternString += "n"; nBarStart = i; } bDarkBar = true; } } } } // We now have a barcode in terms of narrow & wide bars... Parse it! string dataString = ""; // Each pattern within code 39 is nine bars with one white bar between each pattern for (int i = 0; i < patternString.Length - 1; i += 10) { // Create an array of charachters to hold the pattern to be tested char[] pattern = new char[9]; // Stuff the pattern with data from the pattern string patternString.CopyTo(i, pattern, 0, 9); dataString += parsePattern(new string(pattern)); } return(dataString); }
/// <summary> /// Vertical histogram of an image /// </summary> /// <param name="bmp">the bitmap to be processed</param> /// <param name="showDiagnostics">if true, draws an overlay on the image</param> /// <param name="diagnosticsColor">the color of the overlay</param> /// <returns>a histogramResult representing the vertical histogram</returns> private static histogramResult verticalHistogram(Bitmap bmp, bool showDiagnostics, Color diagnosticsColor) { // Create the return value float[] histResult = new float[bmp.Width]; float[] vertSum = new float[bmp.Width]; // Start the max value at zero float maxValue = 0; // Start the min value at the absolute maximum float minValue = 255; // GDI+ still lies to us - the return format is BGR, NOT RGB. BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0; unsafe { byte *p = (byte *)(void *)Scan0; int nOffset = stride - bmp.Width * 3; int nWidth = bmp.Width * 3; for (int y = 0; y < bmp.Height; ++y) { for (int x = 0; x < bmp.Width; ++x) { // Add up all the pixel values vertically (average the R,G,B channels) vertSum[x] += ((p[0] + (p + 1)[0] + (p + 2)[0]) / 3); p += 3; } p += nOffset; } } bmp.UnlockBits(bmData); // Now get the average of the row by dividing the pixel by num pixels for (int i = 0; i < bmp.Width; i++) { histResult[i] = (vertSum[i] / (bmp.Height)); //Save the max value for later if (histResult[i] > maxValue) { maxValue = histResult[i]; } // Save the min value for later if (histResult[i] < minValue) { minValue = histResult[i]; } } // Use GDI+ to draw a lines up the image showing each lines average if (showDiagnostics) { // Set the alpha of the overlay to half transparency int alpha = (int)255 / 2; Pen p = new Pen(Color.FromArgb(alpha, diagnosticsColor)); //Get the graphics to draw on Graphics g; g = Graphics.FromImage(bmp); for (int i = 0; i < bmp.Width; ++i) { // Normalize the drawn histogram to the height of the image int drawLength = (int)(histResult[i] * (bmp.Height / maxValue)); g.DrawLine(p, i, bmp.Height, i, (bmp.Height - (int)drawLength)); } p.Dispose(); g.Dispose(); } histogramResult retVal = new histogramResult(); retVal.histogram = histResult; retVal.max = maxValue; retVal.min = minValue; return(retVal); }
/// <summary> /// Vertical histogram of an image /// </summary> /// <param name="bmp">the bitmap to be processed</param> /// <param name="showDiagnostics">if true, draws an overlay on the image</param> /// <param name="diagnosticsColor">the color of the overlay</param> /// <returns>a histogramResult representing the vertical histogram</returns> private static histogramResult verticalHistogram(Bitmap bmp, int startheight, int endheight) { // Create the return value float[] histResult = new float[bmp.Width]; float[] vertSum = new float[bmp.Width]; // Start the max value at zero float maxValue = 0; // Start the min value at the absolute maximum float minValue = 255; // GDI+ still lies to us - the return format is BGR, NOT RGB. BitmapData bmData = bmp.LockBits(new Rectangle(0, startheight, bmp.Width, endheight - startheight), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0; unsafe { byte *p = (byte *)(void *)Scan0; int nOffset = stride - bmp.Width * 3; int nWidth = bmp.Width * 3; for (int y = startheight; y < endheight; ++y) { for (int x = 0; x < bmp.Width; ++x) { // Add up all the pixel values vertically (average the R,G,B channels) vertSum[x] += ((p[0] + (p + 1)[0] + (p + 2)[0]) / 3); p += 3; } p += nOffset; } } bmp.UnlockBits(bmData); // Now get the average of the row by dividing the pixel by num pixels for (int i = 0; i < bmp.Width; i++) { histResult[i] = (vertSum[i] / (endheight - startheight)); //Save the max value for later if (histResult[i] > maxValue) { maxValue = histResult[i]; } // Save the min value for later if (histResult[i] < minValue) { minValue = histResult[i]; } } histogramResult retVal = new histogramResult(); retVal.histogram = histResult; retVal.max = maxValue; retVal.min = minValue; return(retVal); }
/// <summary> /// Vertical histogram of an image /// </summary> /// <param name="bmp">the bitmap to be processed</param> /// <param name="showDiagnostics">if true, draws an overlay on the image</param> /// <param name="diagnosticsColor">the color of the overlay</param> /// <returns>a histogramResult representing the vertical histogram</returns> private static histogramResult verticalHistogram(Bitmap bmp, int startheight, int endheight) { // Create the return value float[] histResult = new float[bmp.Width]; float[] vertSum = new float[bmp.Width]; // Start the max value at zero float maxValue = 0; // Start the min value at the absolute maximum float minValue = 255; // GDI+ still lies to us - the return format is BGR, NOT RGB. BitmapData bmData = bmp.LockBits(new Rectangle(0, startheight, bmp.Width, endheight - startheight), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); int stride = bmData.Stride; System.IntPtr Scan0 = bmData.Scan0; unsafe { byte * p = (byte *)(void *)Scan0; int nOffset = stride - bmp.Width*3; int nWidth = bmp.Width * 3; for(int y=startheight;y<endheight;++y) { for(int x=0; x < bmp.Width; ++x ) { // Add up all the pixel values vertically (average the R,G,B channels) vertSum[x] += ((p[0] + (p+1)[0] + (p+2)[0])/3); p+=3; } p += nOffset; } } bmp.UnlockBits(bmData); // Now get the average of the row by dividing the pixel by num pixels for(int i=0; i<bmp.Width; i++) { histResult[i] = (vertSum[i] / (endheight - startheight)); //Save the max value for later if (histResult[i] > maxValue) maxValue = histResult[i]; // Save the min value for later if (histResult[i] < minValue) minValue = histResult[i]; } histogramResult retVal = new histogramResult(); retVal.histogram = histResult; retVal.max = maxValue; retVal.min = minValue; return retVal; }