如何确定C#或VB.NET中的图像是否为灰度?

Ele*_*ios 3 .net c# vb.net image-processing grayscale

首先


首先,请注意,给出的答复这个问题将不会对所有的灰度影像,同时也注意到,在接受答案这个其他问题并不能解释所有如何确定是否图像灰度,但反正它不符合我的需求,因为它似乎只涵盖JPEG和TIFF图像,并假设它们将具有EXIF元数据及其中的必需字段.(我无法理解为什么人们确定我所链接的第一个问题是我所链接的第二个问题的"重复"......)

最后,最后接受的答案缺乏一个工作和演示的代码示例,但无论如何都无济于事,因为作者引用了使用Bitmap.GetPixel()函数的缓慢和弃用的方法,但我们应该使用Bitmap.LockBits()函数来获得更高的性能优势.

情景


我有一些GIF,JPG,BMP和PNG图像,我需要确定它们是灰度图像还是不是灰度图像.对于GIF文件,我只关心分析第一帧.

我对图像的数据结构,像素颜色位和那些东西没有多少经验/意识,我只知道非常基础.所以,如果我错过了重要的信息,我应该提供我将要测试的图像的任何信息,那么请问我,但无论如何考虑到我想为"所有"类型的图像创建一个通用的解决方案,好吧,不是全部,但至少这些格式:BMP,JPG,GIF和PNG.

在我提到的那些图像格式中,我的最高优先级是GIF图像,这意味着如果能够确定GIF图像是否为灰度的方法不能用于分析其他类型图像的方法,那么我将接受仅涵盖GIF图像像素处理的答案.


我认为我的需求很明确:如何确定图像是否为灰度?

如果它根本不清楚,为了避免我可以做到你可以浪费你的时间:

  • 该解决方案必须至少适用于GIF图像.(记住,我只关心GIF中的第一帧),但如果提供的解决方案对BMP,JPG和PNG也有效,那么它当然总是更好.

  • 该解决方案必须关注PixelFormat.Format32bppRgb灰度图像.

  • 解决方案一定不能使用Bitmap.GetPixel()功能,必须使用它Bitmap.LockBits().

  • 我不是要求解释,伪代码也没有关于图像结构/格式/像素等文档的外部链接,我要求一个有效的代码示例(当然,如果作者覆盖图像结构/像素技术,总是更好提供除代码之外的基本解释).

  • 在C#或VB.NET中,选择无关紧要.

研究


这就是我到目前为止所做的.我试图理解确定图像是否为灰度的点,我也不确定我的bytesPerPixel变量条件是否合适以及我的RGB值分配是否正确,因为我从一开始就说过我不是图像处理专家所以我可能错过了重要的事情......

VB.NET

Public Shared Function IsImageGrayScale(ByVal img As Image) As Boolean

    Select Case img.PixelFormat

        Case PixelFormat.Format16bppGrayScale
            Return True

        Case Else
            Dim pixelCount As Integer = (img.Width * img.Height)
            Dim bytesPerPixel As Integer = (Image.GetPixelFormatSize(img.PixelFormat) \ 8)

            If (bytesPerPixel <> 3) AndAlso (bytesPerPixel <> 4) Then
                Throw New NotImplementedException(message:="Only pixel formats that has 3 or 4 bytes-per-pixel are supported.")

            Else
                Dim result As Boolean

                ' Lock the bitmap's bits.
                Dim bmp As Bitmap = DirectCast(img, Bitmap)
                Dim rect As New Rectangle(Point.Empty, bmp.Size)
                Dim data As BitmapData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat)

                ' Get the address of the first line.
                Dim ptr As IntPtr = data.Scan0

                ' Declare an array to hold the bytes of the bitmap. 
                Dim numBytes As Integer = (data.Stride * bmp.Height)
                Dim rgbValues As Byte() = New Byte(numBytes - 1) {}

                ' Copy the RGB values into the array.
                Marshal.Copy(ptr, rgbValues, 0, numBytes)

                ' Unlock the bitmap's bits.
                bmp.UnlockBits(data)

                ' Iterate the pixels.
                For i As Integer = 0 To (rgbValues.Length - bytesPerPixel) Step bytesPerPixel

                    Dim c As Color =
                        Color.FromArgb(red:=rgbValues(i + 2),
                                       green:=rgbValues(i + 1),
                                       blue:=rgbValues(i))

                    ' I don't know what kind of comparison I need to do with the pixels, 
                    ' so I don't know how to proceed here to determine whether the image is or is not grayscale.
                    ' ...

                Next i

                Return result
            End If

    End Select

End Function
Run Code Online (Sandbox Code Playgroud)

C#(代码转换,未经测试)

public static bool IsImageGrayScale(Image img) {

    switch (img.PixelFormat) {

        case PixelFormat.Format16bppGrayScale:
            return true;

        default:
            int pixelCount = (img.Width * img.Height);
            int bytesPerPixel = (Image.GetPixelFormatSize(img.PixelFormat) / 8);

            if ((bytesPerPixel != 3) && (bytesPerPixel != 4)) {
                throw new NotImplementedException(message: "Only pixel formats that has 3 or 4 bytes-per-pixel are supported.");

            } else {
                bool result = false;

                // Lock the bitmap's bits.
                Bitmap bmp = (Bitmap)img;
                Rectangle rect = new Rectangle(Point.Empty, bmp.Size);
                BitmapData data = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);

                // Get the address of the first line.
                IntPtr ptr = data.Scan0;

                // Declare an array to hold the bytes of the bitmap. 
                int numBytes = (data.Stride * bmp.Height);
                byte[] rgbValues = new byte[numBytes];

                // Copy the RGB values into the array.
                Marshal.Copy(ptr, rgbValues, 0, numBytes);

                // Unlock the bitmap's bits.
                bmp.UnlockBits(data);

                // Iterate the pixels.
                for (int i = 0; i <= rgbValues.Length - bytesPerPixel; i += bytesPerPixel) {

                    Color c = Color.FromArgb(red: rgbValues[i + 2], 
                                             green: rgbValues[i + 1], 
                                             blue: rgbValues[i]);

                    // I don't know what kind of comparison I need to do with the pixels, 
                    // so I don't know how to proceed here to determine whether the image is or is not grayscale.
                    // ...

                }

                return result;
            }
    }
}
Run Code Online (Sandbox Code Playgroud)

Jim*_*imi 6

What I propose is using Presentation Core's System.Windows.Media.Imaging, which exposes a the abstract BitmapDecoder class, base of all decoders that Windows Imaging directly supports:

System.Windows.Media.Imaging.BmpBitmapDecoder
System.Windows.Media.Imaging.GifBitmapDecoder
System.Windows.Media.Imaging.IconBitmapDecoder
System.Windows.Media.Imaging.JpegBitmapDecoder
System.Windows.Media.Imaging.LateBoundBitmapDecoder
System.Windows.Media.Imaging.PngBitmapDecoder
System.Windows.Media.Imaging.TiffBitmapDecoder
System.Windows.Media.Imaging.WmpBitmapDecoder

When decoding an Image file stream, the correct decoder is cast from the abstract class to the specific class.

The decoded Image frames are cast to a BitmapFrame Class, whose members translate to a BitmapSource class, which references all the decoded informations about the Image Stream.

Of interest, in this case, is the BitmapSource.Format property, which exposes a System.Windows.Media.PixelFormat Structure and its enumeration of recognized formats.

See also PixelFormats Properties

These formats include:

PixelFormats.Gray32Float
PixelFormats.Gray16
PixelFormats.Gray8
PixelFormats.Gray4
PixelFormats.Gray2

These flags can be tested as usual.


此类可用于收集有关Bitmap格式的信息.
我已经包含了一个Property,IsGrayscale它使用之前列出的PixelFormats返回Image PixelFormat的测试结果.

图像格式由属性引用(不同的源,用于比较. 其他属性是不言自明的. BitmapInfo.Format BitmapInfo.Metadata.Format

实现此类的项目必须引用:

PresentationCore
System.Xaml
WindowsBase
Run Code Online (Sandbox Code Playgroud)


属性:

ImageSize            (Size)          => Size of the Image
Dpi                  (Size)          => DpiX and DpiY of the Image
PixelSize            (Size)          => Size in Pixels ot the Image
Masks                (List)          => List of Byte Masks
BitsPerPixel         (int)           => Bits per Pixel
PixelFormat          (PixelFormat)   => Pixel format as reported by the Decoder
ImageType            (string)        => Textual expression of the image format (GIF, JPG etc.)
HasPalette           (bool)          => The Image has a Palette
Palette              (BitmapPalette) => Palette representation of the Image Colors
HasThumbnail         (bool)          => The Image includes a Thumbnail image
Thumbnail            (BitmapImage)   => The Image Thumbnail, in BitmapImage format
Frames               (int)           => Number of frames. Animated Images are represented by a sequence of frames
FramesContent        (FramesInfo)    => Informations about all frame included in this Image
IsMetadataSuppported (bool)          => The Image has Metadata informations
Metadata             (MetadataInfo)  => Class referencing all the Metadata informations a Image contains
AnimationSupported   (bool)          => This Format supports frame Animations
Animated             (bool)          => The Image is a timed sequence of frames
Run Code Online (Sandbox Code Playgroud)


方法:

public enum DeepScanOptions : int  {
    Default = 0,
    Skip,
    Force
}

public bool IsGrayScale(DeepScanOptions DeepScan)
Run Code Online (Sandbox Code Playgroud)

PixelFormat给定图像内部调色板的情况下,检查图像是否被视为GrayScale.该DeepScanOptions枚举用于确定如何进行扫描.
样本使用部分中的更多详细信息.


public enum GrayScaleInfo : int {
    None = 0,
    Partial, 
    GrayScale,
    Undefined
}

public ImagingBitmapInfo.GrayScaleInfo IsGrayScaleFrames()
Run Code Online (Sandbox Code Playgroud)

报告框架选项板的状态.它可能会返回:

None: The Image has no Grayscale Frames
Partial: Some Frames are GrayScale
GrayScale: All Frames have a GrayScale Palette
Undefined: The Image probably has no Palette Information. The Image pixel format is reported by the PixelFormat property


public ImagingBitmapInfo.GrayScaleStats GrayScaleSimilarity();
Run Code Online (Sandbox Code Playgroud)

This method performs a statistical evaluation (Average, (Sum(Min) <=> Sum(Max)), considering the Colors of all the internal Palettes of an Image, to verify how much the internal colorific representation can be assimilated to a Grayscale pattern.
It returns a ImagingBitmapInfo.GrayScaleStats, which exposes these Properties:

int Palettes: Number of Palette evaluated
float AverageMaxDistance: Average distance (Max) between RGB components
float AverageMinDistance: Average distance (Min) between RGB components
float AverageLogDistance: Average logical distance between RGB components
float GrayScalePercent: Percentage of similatity
float GrayScaleAveragePercent: Percentage of logical similarity

List<FrameStat> PerFrameValues: Class that reports the calculated results for each Palette entry. It exposes these Properties:

int ColorEntries: Number of Colors in the current Palette
float DistanceMax: Distance (Max) between RGB components
float DistanceMin: Distance (Min) between RGB components
float DistanceAverage: Average distance between RGB components


public void FrameSourceAddRange(BitmapFrame[] bitmapFrames)
Run Code Online (Sandbox Code Playgroud)

Inserts all Image Frames information in a FramesInfo Class.
It's used internally, but can be filled manually when an instance of the main class, ImagingBitmapInfo, is created. Exposes these properties:

FramesTotalNumber: Total number od Frames included in the Image
FramesColorNumber: Number of frames that have a Color Palette
FramesGrayscaleNumber: Number of GrayScale Frames
FramesBlackWhiteNumber: Number of B&W Frames

List<Frames>: Class List of all frames. The FramesInfo Class object Exposes these properties:

FrameSize: Size of the Frame
FrameDpi: DpiX and DpiY of the Frame PixelFormat: PixelFormat of the Frame
IsColorFrame: The frame has a Color Palette
IsGrayScaleFrame: The frame has a GrayScale Palette
IsBlackWhiteFrame: The frame has a B&W Palette


public System.Drawing.Bitmap ThumbnailToBitmap()
Run Code Online (Sandbox Code Playgroud)

Converts a System.Windows.Media.Imaging BitmapImage in a System.Drawing Bitmap format that can be used in WinForms Controls/Classes. (Not properly tested at this time).


Sample usage:
The main class, ImagingBitmapInfo, is initialized passing to the BitmapFormatInfo() method a File Path or a File Stream.

ImagingBitmapInfo BitmapInfo = BitmapFormatInfo(@"[ImagePath]");
//or 
ImagingBitmapInfo BitmapInfo = BitmapFormatInfo([FileStream]);
Run Code Online (Sandbox Code Playgroud)

To verify whether the Image has a GrayScale PixelFormat, call the IsGrayScale(ImagingBitmapInfo.DeepScanOptions) method, specifying thow this information must be retrieved.

ImagingBitmapInfo.DeepScanOptions.Default
The class decides, based on the Image Pixel Format, whether to perform a Deep Scan of the image Color Palette (if a Palette is present). If the Pixel Format already reports a GrayScale Image (e.g. PixelFormats.Gray32Float, PixelFormats.Gray16 etc.), a Deep scan is not performed. If the Pixel Format is an Indexed one, the scan is performed; if the PixelFormat is Color format, the scan is not performed.

Note that some Images (Gifs, mostly) may report a Color PixelFormat, while the inner format (Palette) might be GrayScale.

ImagingBitmapInfo.DeepScanOptions.Force
Instructs to execute a Deep Scan of the Palettes of all Frames, no matter what PixelFormat is reported by the Image decoder.
Used to discover if a reported Color Image has one or more Grayscale Frames.

ImagingBitmapInfo.DeepScanOptions.Skip
Instructs to not perform a Deep scan of the Palettes, even if it would be normally performed, given the smelly Pixel Format.

System.Windows.Media.PixelFormat pixelFormat = BitmapInfo.PixelFormat;
bool BitmapIsGrayscale = BitmapInfo.IsGrayScale(ImagingBitmapInfo.DeepScanOptions.Force);
Run Code Online (Sandbox Code Playgroud)


If the results are different from what is expected, a complete check of the Image Frames PixelFormat can be performed calling:

ImagingBitmapInfo.GrayScaleInfo GrayScaleFrames = BitmapInfo.IsGrayScaleFrames();
Run Code Online (Sandbox Code Playgroud)

This method performs a complete check of all Frames and reports if any of the internal Frames have a GrayScale PixelFormat. The result can be one of the GrayScaleInfo enumerator values:
None, Partial, GrayScale, Undefined.
If the result is GrayScale, all internal Frames have a GrayScale PixelFormat.
Undefined means that the Image has no Palette informations.

To create a statistical representation of Grayscale similarity of an Image Palettes' Color entries, call the GrayScaleSimilarity() method:

ImagingBitmapInfo.GrayScaleStats Stats = BitmapInfo.GrayScaleSimilarity();

float GrayScalePercent = Stats.GrayScalePercent
float RGBAverageDistancePercent = Stats.GrayScaleAveragePercent
float RGBPatternMaxDistance = Stats.AverageMaxDistance
Run Code Online (Sandbox Code Playgroud)


using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

public class ImagingBitmapInfo
{
    FramesInfo framesInfo;

    public ImagingBitmapInfo()
    {
        this.framesInfo = new FramesInfo();
        this.Metadata = new MetadataInfo();
        this.Metadata.ApplicationName = string.Empty;
        this.Metadata.Author = new List<string>() {  };
        this.Metadata.CameraManufacturer = string.Empty;
        this.Metadata.CameraModel = string.Empty;
        this.Metadata.Comment = string.Empty;
        this.Metadata.Copyright = string.Empty;
        this.Metadata.DateTaken = string.Empty;
        this.Metadata.Subject = string.Empty;
        this.Metadata.Title = string.Empty;
    }

    public Size ImageSize { get; set; }
    public Size Dpi { get; set; }
    public Size PixelSize { get; set; }
    public List<PixelFormatChannelMask> Masks { get; set; }
    public int BitsPerPixel { get; set; }
    public PixelFormat PixelFormat { get; set; }
    public string ImageType { get; set; }
    public bool HasPalette { get; set; }
    public BitmapPalette Palette { get; set; }
    public bool HasThumbnail { get; set; }
    public BitmapImage Thumbnail { get; set; }
    public int Frames { get; set; }
    public FramesInfo FramesContent
    { get { return this.framesInfo; } }
    public bool IsMetadataSuppported { get; set; }
    public MetadataInfo Metadata { get; set; }
    public bool AnimationSupported { get; set; }
    public bool Animated { get; set; }

    public enum DeepScanOptions : int
    {
        Default = 0,
        Skip,
        Force
    }

    public enum GrayScaleInfo : int
    {
        None = 0,
        Partial, 
        GrayScale,
        Undefined
    }

    public System.Drawing.Bitmap ThumbnailToBitmap()
    {
        if (this.Thumbnail == null)
            return null;
        using (System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(
                         this.Thumbnail.DecodePixelWidth, 
                         this.Thumbnail.DecodePixelHeight))
        using (MemoryStream outStream = new MemoryStream())
        {
            BitmapEncoder encoder = new BmpBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(this.Thumbnail));
            encoder.Save(outStream);
            return (System.Drawing.Bitmap)System.Drawing.Bitmap.FromStream(outStream);
        }
    }

    public void FrameSourceAddRange(BitmapFrame[] bitmapFrames)
    {
        if (bitmapFrames == null) return;

        this.framesInfo.Frames.AddRange(bitmapFrames.Select(bf => new FramesInfo.Frame() 
        { 
            Palette = bf.Palette,
            FrameSize = new Size(bf.PixelWidth, bf.PixelHeight),
            FrameDpi = new Size(bf.DpiX, bf.DpiY),
            PixelFormat = bf.Format,
            IsGrayScaleFrame = CheckIfGrayScale(bf.Format, bf.Palette, DeepScanOptions.Force),
            IsBlackWhiteFrame = (bf.Format == PixelFormats.BlackWhite)
        }));

        this.framesInfo.Frames.Where(f => (!f.IsGrayScaleFrame & !f.IsBlackWhiteFrame))
                              .All(f => f.IsColorFrame = true);
    }

    public GrayScaleInfo IsGrayScaleFrames()
    {
        if (this.framesInfo.Frames.Count == 0)
            return GrayScaleInfo.Undefined;
        if (this.framesInfo.FramesGrayscaleNumber > 0)
            return (this.framesInfo.FramesGrayscaleNumber == this.framesInfo.FramesTotalNumber)
                ? GrayScaleInfo.GrayScale : GrayScaleInfo.Partial;
        return GrayScaleInfo.None;
    }

    public bool IsGrayScale(DeepScanOptions DeepScan)
    {
        return CheckIfGrayScale(this.PixelFormat, this.Palette, DeepScan);
    }

    private bool CheckIfGrayScale(PixelFormat pixelFormat, BitmapPalette palette, DeepScanOptions DeepScan)
    {
        if (pixelFormat == PixelFormats.Gray32Float ||
            pixelFormat == PixelFormats.Gray16 ||
            pixelFormat == PixelFormats.Gray8 ||
            pixelFormat == PixelFormats.Gray4 ||
            pixelFormat == PixelFormats.Gray2)
        {
            if (palette == null || (DeepScan != DeepScanOptions.Force)) { return true; }
        }

        if (pixelFormat == PixelFormats.Indexed8 ||
            pixelFormat == PixelFormats.Indexed4 ||
            pixelFormat == PixelFormats.Indexed2)
        {
            DeepScan = (DeepScan != DeepScanOptions.Skip) ? DeepScanOptions.Force : DeepScan;
        }

        if ((DeepScan != DeepScanOptions.Skip) & palette != null)
        {
            List<Color> IndexedColors = palette.Colors.ToList();
            return IndexedColors.All(rgb => (rgb.R == rgb.G && rgb.G == rgb.B && rgb.B == rgb.R));
        }
        return false;
    }

    public GrayScaleStats GrayScaleSimilarity()
    {
        if (!this.HasPalette) return null;

        GrayScaleStats stats = new GrayScaleStats();
        float AccumulatorMax = 0F;
        float AccumulatorMin = 0F;
        float AccumulatorAvg = 0F;
        float[] Distance = new float[3];

        stats.Palettes = this.Frames;

        foreach (FramesInfo.Frame frame in this.framesInfo.Frames)
        {
            GrayScaleStats.FrameStat framestat = new GrayScaleStats.FrameStat() 
            { ColorEntries = frame.Palette.Colors.Count };

            foreach (Color pEntry in frame.Palette.Colors)
            {
                if (!(pEntry.R == pEntry.G && pEntry.G == pEntry.B && pEntry.B == pEntry.R))
                {
                    Distance[0] = Math.Abs(pEntry.R - pEntry.G);
                    Distance[1] = Math.Abs(pEntry.G - pEntry.B);
                    Distance[2] = Math.Abs(pEntry.B - pEntry.R);
                    AccumulatorMax += (float)(Distance.Max());
                    AccumulatorMin += (float)(Distance.Min());
                    AccumulatorAvg += (float)(Distance.Average());
                }
            }
            framestat.DistanceMax = (float)((AccumulatorMax / 2.56) / framestat.ColorEntries);
            framestat.DistanceMin = (float)((AccumulatorMin / 2.56) / framestat.ColorEntries);
            framestat.DistanceAverage = (float)((AccumulatorAvg / 2.56) / framestat.ColorEntries);
            stats.PerFrameValues.Add(framestat);
            AccumulatorMax = 0F;
            AccumulatorMin = 0F;
            AccumulatorAvg = 0F;
        }
        stats.AverageMaxDistance = stats.PerFrameValues.Max(mx => mx.DistanceMax);
        stats.AverageMinDistance = stats.PerFrameValues.Min(mn => mn.DistanceMin);
        stats.AverageLogDistance = stats.PerFrameValues.Average(avg => avg.DistanceAverage);
        stats.GrayScaleAveragePercent = 100F - stats.AverageLogDistance;
        stats.GrayScalePercent = 100F - ((stats.AverageMaxDistance - stats.AverageMinDistance) / 2);
        return stats;
    }

    public class GrayScaleStats
    {
        public GrayScaleStats()
        {
            this.PerFrameValues = new List<FrameStat>();
        }

        public List<FrameStat> PerFrameValues { get; set; }
        public int Palettes { get; set; }
        public float AverageMaxDistance { get; set; }
        public float AverageMinDistance { get; set; }
        public float AverageLogDistance { get; set; }
        public float GrayScalePercent { get; set; }
        public float GrayScaleAveragePercent { get; set; }

        public class FrameStat
        {
            public int ColorEntries { get; set; }
            public float DistanceMax { get; set; }
            public float DistanceMin { get; set; }
            public float DistanceAverage { get; set; }
        }
    }

    public class FramesInfo
    {
        public FramesInfo()
        {
            this.Frames = new List<Frame>();
        }

        public int FramesTotalNumber
        {
            get { return (this.Frames != null) ? this.Frames.Count() : 0; }
            private set { }
        }

        public int FramesColorNumber
        {
            get { return (this.Frames != null) ? this.Frames 
                              .Where(f => f.IsColorFrame == true)
                              .Count() : 0; }
            private set { }
        }
        public int FramesGrayscaleNumber
        {
            get {return (this.Frames != null) ? this.Frames
                             .Where(f => f.IsGrayScaleFrame == true)
                             .Count() : 0; }
            private set { }
        }

        public int FramesBlackWhiteNumber
        {
            get { return (this.Frames != null) ? this.Frames
                              .Where(f => f.IsBlackWhiteFrame == true)
                              .Count() : 0; }
            private set { }
        }

        public List<Frame> Frames { get; private set; }

        internal class Frame
        {
            public BitmapPalette Palette { get; set; }
            public Size FrameSize { get; set; }
            public Size FrameDpi { get; set; }
            public PixelFormat PixelFormat { get; set; }
            public bool IsColorFrame { get; set; }
            public bool IsGrayScaleFrame { get; set; }
            public bool IsBlackWhiteFrame { get; set; }
        }
    }

    public class MetadataInfo
    {
        public string ApplicationName { get; set; }
        public List<string> Author { get; set; }
        public string Copyright { get; set; }
        public string CameraManufacturer { get; set; }
        public string CameraModel { get; set; }
        public string Comment { get; set; }
        public string Format { get; set; }
        public string Subject { get; set; }
        public string Title { get; set; }
        public string DateTaken { get; set; }
        public int Rating { get; set; }
    }
}


public static ImagingBitmapInfo BitmapPixelFormat(string FileName)
{
    using (FileStream stream = File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.None))
    {
        return BitmapPixelFormat(stream);
    }
}

public static ImagingBitmapInfo BitmapPixelFormat(FileStream stream)
{
    ImagingBitmapInfo imageInfo = new ImagingBitmapInfo();
    var bitmapDecoder = BitmapDecoder.Create(stream, 
                                                BitmapCreateOptions.PreservePixelFormat, 
                                                BitmapCacheOption.Default);

    BitmapSource bitmapSource = bitmapDecoder.Frames[0];
    ImageMetadata imageMetadata = bitmapSource.Metadata;
    BitmapMetadata bitmapMetadata = (BitmapMetadata)bitmapSource.Metadata;

    try
    {
        imageInfo.Frames = bitmapDecoder.Frames.Count();
        if (imageInfo.Frames > 0)
            imageInfo.FrameSourceAddRange(bitmapDecoder.Frames.ToArray());

        imageInfo.ImageType = bitmapMetadata.Format.ToUpperInvariant();
        imageInfo.PixelFormat = bitmapSource.Format;
        imageInfo.HasPalette = ((bitmapSource.Palette != null) && (bitmapSource.Palette.Colors.Count > 0)) ? true : false;
        imageInfo.Palette = bitmapSource.Palette;
        imageInfo.ImageSize = new Size((float)bitmapSource.Height, (float)bitmapSource.Width);
        imageInfo.Dpi = new Size((float)bitmapSource.DpiX, (float)bitmapSource.DpiY);
        imageInfo.PixelSize = new Size(bitmapSource.PixelHeight, bitmapSource.PixelWidth);
        imageInfo.Masks = bitmapSource.Format.Masks.ToList();
        imageInfo.BitsPerPixel = bitmapSource.Format.BitsPerPixel;
        imageInfo.AnimationSupported = bitmapDecoder.CodecInfo.SupportsAnimation;
        imageInfo.Animated = (imageInfo.AnimationSupported && (imageInfo.Frames > 1)) ? true : false;
        imageInfo.HasThumbnail = bitmapDecoder.Thumbnail != null;
        if (imageInfo.HasThumbnail)
            imageInfo.Thumbnail = (BitmapImage)bitmapDecoder.Thumbnail.CloneCurrentValue();


        imageInfo.Metadata.Format = bitmapMetadata.Format;
        //If not supported, Catch and set imageInfo.SetMetadataNonSupported()
        imageInfo.Metadata.ApplicationName = bitmapMetadata.ApplicationName;
        imageInfo.Metadata.Author = (bitmapMetadata.Author != null) 
                                  ? bitmapMetadata.Author.ToList<string>() 
                                  : null;
        imageInfo.Metadata.CameraModel = bitmapMetadata.CameraModel;
        imageInfo.Metadata.CameraManufacturer = bitmapMetadata.CameraManufacturer;
        imageInfo.Metadata.CameraModel = bitmapMetadata.Comment;
        imageInfo.Metadata.Copyright = bitmapMetadata.Copyright;
        imageInfo.Metadata.Subject = bitmapMetadata.Subject;
        imageInfo.Metadata.Title = bitmapMetadata.Title;
        imageInfo.Metadata.Rating = bitmapMetadata.Rating;
        imageInfo.Metadata.Format = bitmapMetadata.Format;
        imageInfo.Metadata.DateTaken = bitmapMetadata.DateTaken;

    }
    catch (System.NotSupportedException)
    { imageInfo.IsMetadataSuppported = false; }

    catch (System.Exception ex) { /* Log ex */ throw ex; }

    return imageInfo;
}
Run Code Online (Sandbox Code Playgroud)

Update:

This is, more or less, the same setup, but WinForms oriented.
Meaning that only the System.Drawing assembly is used.

System.Drawing.Imaging has less option (there's also a nasty bug in GDI+, related to Bitmap Encoders, never corrected) and some informations are not directly available.
The relevant parts are there, anyway.

Grayscale Images are detected correctly, as far as I could test.

To note that, if an Image has an indexed Palette (e.g. Gif format), the reported ImageFlags Flag ColorSpaceGRAY is never correct. Nor is the PixelFormat.Format16bppGrayScale.
The only possible way (that I found) to verify whether the image is a Grayscale one in this case, is to parse the color Palette. It takes only a few Ticks to complete, annoying nonetheless.
It's working correctly for other formats.

Same procedure, as listed before.
Can be used this way:

ImagingBitmapInfo BitmapInfo = BitmapPixelFormat(@"[ImagePath]");
bool BitmapIsGrayscale = BitmapInfo.IsGrayScale();
Run Code Online (Sandbox Code Playgroud)

or

ImagingBitmapInfo BitmapInfo = BitmapPixelFormat([ImageStream]);
bool BitmapIsGrayscale = BitmapInfo.IsGrayScale();
Run Code Online (Sandbox Code Playgroud)


Code moved to PasteBin for lack of space in this Post body.