【问题标题】:Need C# function to convert grayscale TIFF to black & white (monochrome/1BPP) TIFF需要 C# 函数将灰度 TIFF 转换为黑白(单色/1BPP)TIFF
【发布时间】:2010-11-13 11:28:48
【问题描述】:

我需要一个 C# 函数,该函数将获取 8 位灰度 TIFF 的 Byte[],并返回 1 位(黑白)TIFF 的 Byte[]。

我对使用 TIFF 比较陌生,但总体思路是我们需要将它们从灰度或彩色转换为黑白/单色/二进制图像格式。

我们通过 WCF 作为 Byte[] 接收图像,然后我们需要将此转换为黑白,以便将它们发送到进行进一步处理的组件。我们目前不打算将它们保存为文件。

作为参考,在我们的测试客户端中,这是我们创建 Byte[] 的方式:

        FileStream fs = new FileStream("test1.tif", FileMode.Open, FileAccess.Read);
        this.image = new byte[fs.Length];
        fs.Read(this.image, 0, System.Convert.ToInt32(fs.Length));
        fs.Close();

--------更新---------

我认为这里可能有不止 1 个好的答案,但我们最终使用了 CodeProject 站点中的代码,并添加了以下方法来重载转换函数以接受 Byte[] 以及位图:

public static Byte[] ConvertToBitonal(Byte[] original)
    {
        Bitmap bm = new Bitmap(new System.IO.MemoryStream(original, false));
        bm = ConvertToBitonal(bm);
        System.IO.MemoryStream s = new System.IO.MemoryStream();
        bm.Save(s, System.Drawing.Imaging.ImageFormat.Tiff);
        return s.ToArray();
    }

【问题讨论】:

  • 这不是您想要的方式。要完成这项工作,您需要解析整个 TIFF。 TIFF 是一种重要的文件格式。使用可以读写 TIFF 文件的工具包。
  • 我正在寻找一个可以解析整个 TIFF 的 c# 函数,正如您所提到的。到目前为止,我已经在 VB 中看到了一些,目前正在审查 c# 中的一个。我们想要源而不是密封组件。

标签: c# graphics image image-processing tiff


【解决方案1】:

可能想查看“Craigs Utility Library”,我相信他具备该功能。 Craig's Utility Library

【讨论】:

  • 这是一个很酷的库,但是,它似乎专注于位图而不是 tif。
【解决方案2】:

首先,您需要知道 X、Y 像素位置如何映射到数组中的索引值。 这取决于你的 Byte[] 是如何构造的。 您需要了解图片格式的详细信息 - 例如,stride 是什么?

我在PixelFormat enumeration. 中没有看到 8 位灰度 TIFF,如果它在那里,它会告诉你你需要知道什么。

然后,遍历每个像素并查看其颜色值。 您需要确定一个阈值 - 如果像素的颜色高于阈值,则将新颜色设为白色;否则,将其设为黑色。

如果您想使用 1BPP 模拟灰度阴影,您可以查看更高级的技术,例如抖动。

【讨论】:

    【解决方案3】:

    我公司的产品dotImage 会这样做。

    给定图像,您可以使用多种方法从多位转换为单位,包括简单阈值、全局阈值、局部阈值、自适应阈值、抖动(有序和 Floyd Steinberg)和动态阈值。正确的选择取决于输入图像的类型(文档、图像、图表)。

    典型的代码如下所示:

    AtalaImage image = new AtalaImage("path-to-tiff", null);
    ImageCommand threshold = SomeFactoryToConstructAThresholdCommand();
    AtalaImage finalImage = threshold.Apply(image).Image;
    

    SomeFactoryToConstructAThresholdCommand() 是一种返回处理图像的新命令的方法。可以很简单

    return new DynamicThresholdCommand();
    

    return new GlobalThresholdCommand();
    

    一般来说,如果您希望将整个多页 tiff 转换为黑白,您会执行以下操作:

    // open a sequence of images
    FileSystemImageSource source = new FileSystemImageSource("path-to-tiff", true);
    
    using (FileStream outstm = new FileStream("outputpath", FileMode.Create)) {
        // make an encoder and a threshold command
        TiffEncoder encoder = new TiffEncoder(TiffCompression.Auto, true);
        // dynamic is good for documents -- needs the DocumentImaging SDK
        ImageCommand threshold = new DynamicThreshold();
    
        while (source.HasMoreImages()) {
            // get next image
            AtalaImage image = source.AcquireNext();
            AtalaImage final = threshold.Apply(image).Image;
            try {
                encoder.Save(outstm, final, null);
            }
            finally {
                // free memory from current image
                final.Dispose();
                // release the source image back to the image source
                source.Release(image);
            }
        }
    }
    

    【讨论】:

    • 我不能对此表示赞同,但我要给你一个虚拟的 +1 作为代码皮条客。
    【解决方案4】:

    这样的东西可能会起作用,我还没有测试过。 (应该很容易 C# 吧。)

        Dim bmpGrayscale As Bitmap = Bitmap.FromFile("Grayscale.tif")
        Dim bmpMonochrome As New Bitmap(bmpGrayscale.Width, bmpgrayscale.Height, Imaging.PixelFormat.Format1bppIndexed)
        Using gfxMonochrome As Graphics = Graphics.FromImage(bmpMonochrome)
            gfxMonochrome.Clear(Color.White)
        End Using
        For y As Integer = 0 To bmpGrayscale.Height - 1
            For x As Integer = 0 To bmpGrayscale.Width - 1
                If bmpGrayscale.GetPixel(x, y) <> Color.White Then
                    bmpMonochrome.SetPixel(x, y, Color.Black)
                End If
            Next
        Next
        bmpMonochrome.Save("Monochrome.tif")
    

    这可能是更好的方法:

    Using bmpGrayscale As Bitmap = Bitmap.FromFile("Grayscale.tif")
        Using bmpMonochrome As New Bitmap(bmpGrayscale.Width, bmpgrayscale.Height, Imaging.PixelFormat.Format1bppIndexed)
            Using gfxMonochrome As Graphics = Graphics.FromImage(bmpMonochrome)
                gfxMonochrome.CompositingQuality = Drawing2D.CompositingQuality.HighQuality
                gfxMonochrome.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
                gfxMonochrome.DrawImage(bmpGrayscale, new Rectangle(0, 0, bmpMonochrome.Width, bmpMonochrome.Height)
            End Using
            bmpMonochrome.Save("Monochrome.tif")
        End Using
    End Using
    

    我相信您正在寻找的术语是“重采样”。

    【讨论】:

    • 这会将任何非白色像素变为黑色,因此所有灰色阴影都将变为黑色 - 可能不是他想要的。
    • @plinth - 是的。但是您必须承认,应用阈值非常容易。 :)
    【解决方案5】:

    CodeProject here 上有一篇文章描述了您的需求。

    【讨论】:

    • 这个解决方案似乎奏效了!我添加了一个函数,使其能够接受 Byte[] 以及位图。
    【解决方案6】:

    @neodymium 有一个很好的答案,但 GetPixel/SetPixel 会影响性能。 Bob Powell has a great method.

    C#:

        private Bitmap convertTo1bpp(Bitmap img)
        {
            BitmapData bmdo = img.LockBits(new Rectangle(0, 0, img.Width, img.Height),
                                           ImageLockMode.ReadOnly, 
                                           img.PixelFormat);
    
            // and the new 1bpp bitmap
            Bitmap bm = new Bitmap(img.Width, img.Height, PixelFormat.Format1bppIndexed);
            BitmapData bmdn = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height),
                                          ImageLockMode.ReadWrite, 
                                          PixelFormat.Format1bppIndexed);
    
            // scan through the pixels Y by X
            for(int y = 0; y < img.Height; y++)
            {
                for(int x = 0; x < img.Width; x++)
                {
                    // generate the address of the colour pixel
                    int index = y * bmdo.Stride + x * 4;
    
                    // check its brightness
                    if(Color.FromArgb(Marshal.ReadByte(bmdo.Scan0, index + 2), 
                                      Marshal.ReadByte(bmdo.Scan0, index + 1), 
                                      Marshal.ReadByte(bmdo.Scan0, index)).GetBrightness() > 0.5F)
                    {
                        setIndexedPixel(x, y, bmdn, true); // set it if its bright.
                    }
                 }
            }
    
            // tidy up
            bm.UnlockBits(bmdn);
            img.UnlockBits(bmdo);
            return bm;
        }
    
        private void setIndexedPixel(int x, int y, BitmapData bmd, bool pixel)
        {
            int index = y * bmd.Stride + (x >> 3);
            byte p = Marshal.ReadByte(bmd.Scan0, index);
            byte mask = (byte)(0x80 >> (x & 0x7));
    
            if (pixel)
            {
                p |= mask;
            }
            else
            {
                p &= (byte)(mask ^ 0xFF);
            }
    
            Marshal.WriteByte(bmd.Scan0, index, p);
        }
    

    【讨论】:

    • 此方法使用简单的阈值进行转换。
    • 好的,看起来很有趣。如何将我的 Byte[] 放入 Bitmap 对象以发送到此函数?
    • @NagaMensch - 我不确定你能做到。根据msdn.microsoft.com/en-us/library/…,不支持 8 位灰度的 PixelFormat。
    • @NagaMensch 如果您已经有一个 Byte[],您甚至不需要通过 BitmapData 实例读取源位图。只需使用 Byte 数组 - 只需确保您知道步幅是多少。
    【解决方案7】:

    逐像素操作极其缓慢。比 System.DrawImage 慢 40 倍。 System.Draw 图像是一半的解决方案,损坏图片(300dpi-->96dpi)并以 300dpi 生成 200-400kb 的大结果文件。

            public static Image GetBlackAndWhiteImage(Image SourceImage)
        {
    
            Bitmap bmp = new Bitmap(SourceImage.Width, SourceImage.Height);
    
            using (Graphics gr = Graphics.FromImage(bmp)) // SourceImage is a Bitmap object
            {
                var gray_matrix = new float[][] {
                new float[] { 0.299f, 0.299f, 0.299f, 0, 0 },
                new float[] { 0.587f, 0.587f, 0.587f, 0, 0 },
                new float[] { 0.114f, 0.114f, 0.114f, 0, 0 },
                new float[] { 0,      0,      0,      1, 0 },
                new float[] { 0,      0,      0,      0, 1 }
            };
    
                var ia = new System.Drawing.Imaging.ImageAttributes();
                ia.SetColorMatrix(new System.Drawing.Imaging.ColorMatrix(gray_matrix));
                ia.SetThreshold(float.Parse(Settings.Default["Threshold"].ToString())); // Change this threshold as needed
                var rc = new Rectangle(0, 0, SourceImage.Width, SourceImage.Height);
                gr.DrawImage(SourceImage, rc, 0, 0, SourceImage.Width, SourceImage.Height, GraphicsUnit.Pixel, ia);
            }
            return bmp;
        }
    

    完美的方法是简单地转换为 CCITT 解码的 tif,它只包含 BW。使用 30-50kb 结果文件更有效的方法,300dpi 也保持正确:

            public void toCCITT(string tifURL)
        {
            byte[] imgBits = File.ReadAllBytes(tifURL);
    
            using (MemoryStream ms = new MemoryStream(imgBits))
            {
                using (Image i = Image.FromStream(ms))
                {
                    EncoderParameters parms = new EncoderParameters(1);
                    ImageCodecInfo codec = ImageCodecInfo.GetImageDecoders()
                                                         .FirstOrDefault(decoder => decoder.FormatID == ImageFormat.Tiff.Guid);
    
                    parms.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionCCITT4);
    
                    i.Save(@"c:\test\result.tif", codec, parms);
                }
            }
        }
    

    祝你好运,兄弟,

    【讨论】:

    • 这似乎不适用于 macOS 上的 dotnet core。 macOS 是否按照此处所述实现此编解码器和压缩?
    【解决方案8】:

    我已经测试了这段代码,对我来说效果很好:

    //You should use System.Linq for this to work
    public static ImageCodecInfo TiffCodecInfo => ImageCodecInfo.GetImageDecoders().
        FirstOrDefault(decoder => decoder.FormatID == ImageFormat.Tiff.Guid);
    
    //Encapsulate this in a try catch block for possible exceptions
    public static Bitmap ConvertToBitonal(Bitmap original)
    {
        EncoderParameters encoderParameters;
        MemoryStream ms = new MemoryStream();
        Bitmap result;
    
        encoderParameters = new EncoderParameters(1);
        encoderParameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 1L);
        original.Save(ms, TiffCodecInfo, encoderParameters);
        result = new Bitmap(Image.FromStream(ms));
        ms.Dispose();
        return result;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-23
      • 2013-04-21
      • 1970-01-01
      相关资源
      最近更新 更多