Ejemplo n.º 1
0
        /// <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);
        }
Ejemplo n.º 2
0
        /// <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);
        }
Ejemplo n.º 3
0
        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));
            }
        }
Ejemplo n.º 4
0
        /// <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
        }