/// <summary> /// Suppresses the lines on the <see cref="Image"/>. /// </summary> /// <returns> /// A new <see cref="Image"/> with lines suppressed. /// </returns> /// <exception cref="NotSupportedException"> /// The <see cref="Image{T}.BitsPerPixel"/> is not 1 or 8. /// </exception> public Image SuppressLines() { if (this.BitsPerPixel != 1 && this.BitsPerPixel != 8) { throw new NotSupportedException( string.Format(CultureInfo.InvariantCulture, Properties.Resources.E_UnsupportedDepth, this.BitsPerPixel)); } // IPP does not support 1bpp images - convert to 8bpp Image src; bool convert1bpp = false; if (this.BitsPerPixel == 1) { src = this.Convert1To8(null); convert1bpp = true; } else { src = this; } Image dst = src.Clone(false); IPP.Execute(() => { return(NativeMethods.supresslines( src.Width, src.Height, src.Stride, src.Bits, dst.Bits)); }); // convert back to 1bpp if (convert1bpp) { dst.Convert8To1(dst, 1); } return(dst); }
/// <summary> /// Converts this <see cref="Image"/> from gray scale to black-and-white using Otsu algorithm and parametrized background normalization. /// </summary> /// <param name="dst">The destination <see cref="Image"/>. Can be <b>null</b>.</param> /// <param name="sx">The tile width, in pixels. If 0, the method uses default value 16.</param> /// <param name="sy">The tile height, in pixels. If 0, the method uses default value 32.</param> /// <param name="smoothx">The half-width of tile convolution kernel width.</param> /// <param name="smoothy">The half-width of tile convolution kernel height.</param> /// <param name="adaptiveThreshold">The threshold for determining foreground. If 0, the method uses default value 100.</param> /// <param name="mincount">The minimum number of foreground pixels in tile. If 0, the method uses default value (<paramref name="sx"/> * <paramref name="sy"/>) / 4.</param> /// <returns> /// The destination <see cref="Image"/>. /// </returns> /// <exception cref="ArgumentException"> /// <para>The depth of this <see cref="Image"/> is not 8 bits per pixel.</para> /// </exception> /// <remarks> /// <para>If <paramref name="dst"/> is <b>null</b> the method creates new destination <see cref="Image"/> with dimensions of this <see cref="Image"/>.</para> /// <para>If <paramref name="dst"/> equals this <see cref="Image"/>, the operation is performed in-place.</para> /// <para>Conversely, the <paramref name="dst"/> is reallocated to the dimensions of this <see cref="Image"/>.</para> /// </remarks> public Image OtsuNormalizeBackground(Image dst, int sx, int sy, int smoothx, int smoothy, byte adaptiveThreshold, int mincount) { if (this.BitsPerPixel != 8) { throw new ArgumentException(Properties.Resources.E_UnsupportedDepth_8bpp); } int width = this.Width; int height = this.Height; // initialize default variables if (sx == 0) { sx = 16; } if (sy == 0) { sy = 32; } sx = Math.Max(((sx + 7) / 8) * 8, 16); // horizontal tile size must be rounded to 8 pixels sy = Math.Max(sy, 16); if (mincount == 0) { mincount = (sx * sy) / 4; } // Calculate tile size int nx = Math.Max(1, width / sx); int ny = Math.Max(1, height / sy); // Allocate threshold map byte[] map = new byte[nx * ny]; // Create destination image bool inplace = dst == this; dst = this.CreateTemplate(dst, 1); ////this.DeconvolutionFFT(this, 3, new float[9] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }); ////this.FilterWiener(this, new Drawing.Size(5, 5), new Drawing.Point(2, 2)); ////Image edges = this.Canny(null, 50.0f, 150.0f, BorderType.BorderRepl, 0); ////Image maskedges = this & edges; // calculate adaptive threshold if (adaptiveThreshold == 0) { // calculate the threshold adaptiveThreshold = this.Otsu(0, 0, width, height); adaptiveThreshold = (byte)Math.Max(3 * adaptiveThreshold / 4, 45); } // generate foreground mask // use destination as a temporary buffer this.Convert8To1(dst, adaptiveThreshold); dst.Dilate3x3(dst, BorderType.BorderConst, 0); // create mask that has all foreground pixels set to zero Image maskg = dst.Convert1To8(null); maskg.And(maskg, this); // calculate adaptive threshold map // dst currently holds background mask CalculateAdaptiveThresholds(dst, maskg); // apply thresholds // use gray mask as a temporary buffer for normalized image ApplyAdaptiveThresholds(this, maskg); #if false //// !!!! TEMP maskg.Sub(maskg, maskg.MorphBlackHat(null, StructuringElement.Square(7), 1, BorderType.BorderRepl, 0), 0); //// !!!! TEMP #endif // apply single otsu threshold to entire normalized image byte otsuThreshold = maskg.Otsu(0, 0, width, height); // IPP thresholding functions use more / less than, or equal thresholding method // while our functions do more than, or equal / less. // So, we increment computed Otsu threshold by one maskg.Convert8To1(dst, (byte)Math.Min(byte.MaxValue, otsuThreshold + 1)); ////edges.Convert8To1(edges, 128); ////edges.Not(edges); ////edges.MorphClose(edges, StructuringElement.Brick(3, 3), 1, BorderType.BorderConst, 0); ////dst.FloodFill(dst, 8, edges); #if false //// !!!! TEMP sx *= 2; sy *= 2; nx = Math.Max(1, width / sx); ny = Math.Max(1, height / sy); map = new byte[nx * ny]; // calculate adaptive threshold map Image maskg2 = maskg & dst.Convert1To8(null); CalculateAdaptiveThresholds(dst, maskg2); // Apply the threshold Image temp1 = this.CreateTemplate(null, 1); unsafe { fixed(ulong *bitsnorm = maskg.Bits, bitstemp1 = temp1.Bits) { for (int iy = 0, ty = 0, mapoff = 0; iy < ny; iy++, ty += sy, mapoff += nx) { int th = iy + 1 == ny ? height - ty : sy; for (int ix = 0, tx = 0; ix < nx; ix++, tx += sx) { int tw = ix + 1 == nx ? width - tx : sx; byte mapThreshold = map[mapoff + ix]; byte threshold; if (mapThreshold <= otsuThreshold) { threshold = otsuThreshold; } else { threshold = (byte)((int)otsuThreshold + (2 * ((int)mapThreshold - (int)otsuThreshold) / 3)); } NativeMethods._convert8to1(tx, ty, tw, th, (byte *)bitsnorm, maskg.Stride8, (byte *)bitstemp1, temp1.Stride8, threshold); } } } } Image temp2 = dst.Dilate(null, StructuringElement.Square(7), 1, BorderType.BorderConst, 0); temp2.And(temp2, temp1); dst.FloodFill(dst, 8, temp2); //// !!!! TEMP #endif void CalculateAdaptiveThresholds(Image bgmask, Image fgmask) { for (int iy = 0, ty = 0, mapoff = 0; iy < ny; iy++, ty += sy, mapoff += nx) { int th = iy + 1 == ny ? height - ty : sy; for (int ix = 0, tx = 0; ix < nx; ix++, tx += sx) { int tw = ix + 1 == nx ? width - tx : sx; int count = (tw * th) - (int)bgmask.Power(tx, ty, tw, th); if (count >= mincount) { int sum = (int)fgmask.Power(tx, ty, tw, th); map[mapoff + ix] = (byte)(sum / count); } else { map[mapoff + ix] = 0; } } } // fill holes in map // these are tiles that did not have enough foreground pixels to make an estimate FillHoles(); // Optionally smooth the threshold map Image.SmoothMap(nx, ny, map, smoothx, smoothy); void FillHoles() { bool needBackwardPass = false; for (int iy = 0, mapoff = 0; iy < ny; iy++, mapoff += nx) { for (int ix = 0, prevoff = mapoff - nx; ix < nx; ix++) { if (map[mapoff + ix] == 0) { int sum = 0; int div = 0; // left if (ix > 0) { byte val = map[mapoff + ix - 1]; if (val != 0) { sum += val; div++; // right for (int iix = ix + 1; iix < nx; iix++) { if (map[mapoff + iix] != 0) { int coeff = iix - ix; sum += (coeff - 1) * val; sum += map[mapoff + iix]; div += coeff; break; } } } } // top if (iy > 0) { byte val = map[prevoff + ix]; if (val != 0) { sum += val; div++; // bottom for (int iiy = iy + 1, nextoff = mapoff + nx + ix; iiy < ny; iiy++, nextoff += nx) { if (map[nextoff] != 0) { int coeff = iiy - iy; sum += (coeff - 1) * val; sum += map[nextoff]; div += coeff; break; } } } } if (div == 0) { needBackwardPass = true; } else { map[mapoff + ix] = (byte)(sum / div); } } } } if (needBackwardPass) { for (int iy = ny - 1, mapoff = iy * nx; iy >= 0; iy--, mapoff -= nx) { for (int ix = nx - 1, prevoff = mapoff + nx; ix >= 0; ix--) { if (map[mapoff + ix] == 0) { int sum = 0; int div = 0; if (iy + 1 < ny) { byte val = map[prevoff + ix]; if (val != 0) { sum += map[prevoff + ix]; div++; } } if (ix + 1 < nx) { byte val = map[mapoff + ix + 1]; if (val != 0) { sum += map[mapoff + ix + 1]; div++; } } if (div != 0) { map[mapoff + ix] = (byte)(sum / div); } } } } } } } void ApplyAdaptiveThresholds(Image source, Image destination) { for (int iy = 0, ty = 0, mapoff = 0; iy < ny; iy++, ty += sy, mapoff += nx) { int th = iy + 1 == ny ? height - ty : sy; for (int ix = 0, tx = 0; ix < nx; ix++, tx += sx) { int tw = ix + 1 == nx ? width - tx : sx; destination.DivC(tx, ty, tw, th, source, map[mapoff + ix], -8); } } } if (inplace) { this.Attach(dst); return(this); } return(dst); }
public Image Affine(Image dst, System.Windows.Media.Matrix matrix, BorderType borderType, uint borderValue) { const float Eps = 1e-8f; if (matrix.IsIdentity) { return(this.Copy(dst, true)); } // IPP does not support 1bpp images - convert to 8bpp Image src; bool convert1bpp = false; if (this.BitsPerPixel == 1) { src = this.Convert1To8(null); borderValue = borderValue != 0 ? 0u : 255u; convert1bpp = true; } else { src = this; } // calculate new image size and position PointD tr = TransformPoint(src.Width, 0); PointD br = TransformPoint(src.Width, src.Height); PointD bl = TransformPoint(0, src.Height); double x1dst = Core.MinMax.Min(bl.X, tr.X, br.X, 0.0); double x2dst = Core.MinMax.Max(bl.X, tr.X, br.X, 0.0); double y1dst = Core.MinMax.Min(bl.Y, tr.Y, br.Y, 0.0); double y2dst = Core.MinMax.Max(bl.Y, tr.Y, br.Y, 0.0); // translate matrix so the transformed image fits into new frame matrix.OffsetX = -Core.MinMax.Min(x1dst, x2dst); matrix.OffsetY = -Core.MinMax.Min(y1dst, y2dst); // note: add epsilon to avoid rounding problems int widthdst = (int)Math.Floor(x2dst - x1dst + Eps); int heightdst = (int)Math.Floor(y2dst - y1dst + Eps); bool inplace = dst == this; dst = src.CreateTemplate(dst, widthdst, heightdst, src.BitsPerPixel); IPP.Execute(() => { return(NativeMethods.affine( src.BitsPerPixel, src.Width, src.Height, src.Stride, src.Bits, dst.Width, dst.Height, dst.Stride, dst.Bits, matrix.M11, matrix.M12, matrix.OffsetX, matrix.M21, matrix.M22, matrix.OffsetY, (int)borderType, borderValue)); }); dst.AppendTransform(new MatrixTransform(matrix)); // convert back to 1bpp if (convert1bpp) { dst.Convert8To1(dst, 1); /*using (Pix pixs = transformedImage.CreatePix()) * { * using (Pix pixd = pixs.pixOtsu(false)) * { * if (pixd != null) * { * return pixd.CreateImage(transformedImage.HorizontalResolution, transformedImage.VerticalResolution); * } * } * }*/ } if (inplace) { this.Attach(dst); return(this); } return(dst); PointD TransformPoint(int ptx, int pty) { return(new PointD( (matrix.M11 * ptx) + (matrix.M12 * pty) + matrix.OffsetX, (matrix.M21 * ptx) + (matrix.M22 * pty) + matrix.OffsetY)); } }
/// <summary> /// Scales the <see cref="Image"/> vertically and horizontally without changing its resolution. /// </summary> /// <param name="dst">The destination <see cref="Image"/>. Can be <b>null</b>.</param> /// <param name="width">The desired width of the image, in pixels.</param> /// <param name="height">The desired height of the image, in pixels.</param> /// <param name="options">The scaling options.</param> /// <returns> /// The destination <see cref="Image"/>. /// </returns> public Image ScaleToSize(Image dst, int width, int height, ScalingOptions options) { if (width == this.Width && height == this.Height) { return(this.Copy(dst, true)); } if (width <= 0) { throw new ArgumentException(Properties.Resources.E_InvalidWidth, nameof(width)); } if (height <= 0) { throw new ArgumentException(Properties.Resources.E_InvalidHeight, nameof(height)); } System.Windows.Media.Matrix matrix = System.Windows.Media.Matrix.Identity; matrix.Scale((double)width / this.Width, (double)height / this.Height); #if false dst = this.Affine(dst, matrix, BorderType.BorderConst, this.WhiteColor); Debug.Assert(width == dst.Width && height == dst.Height, "Image dimensions are wrong."); return(dst); #else // IPP does not support 1bpp images - convert to 8bpp Image src; bool convert1bpp = false; if (this.BitsPerPixel == 1) { src = this.Convert1To8(null); convert1bpp = true; } else { src = this; } bool inplace = dst == this; dst = src.CreateTemplate(dst, width, height, src.BitsPerPixel); IPP.Execute(() => { return(NativeMethods.resize( src.BitsPerPixel, src.Width, src.Height, src.Bits, src.Stride8, dst.Width, dst.Height, dst.Bits, dst.Stride8, options.InterpolationType, options.Antialiasing, options.ValueB, options.ValueC, options.Lobes, BorderType.BorderConst, src.WhiteColor)); }); dst.AppendTransform(new MatrixTransform(matrix)); // convert back to 1bpp if (convert1bpp) { dst.Convert8To1(dst, 1); } if (inplace) { this.Attach(dst); return(this); } return(dst); #endif }