c#中的位图最小值和最大值

时间:2017-07-11 10:54:11

标签: c# image-processing

我在C#中有一个bitmap对象,其创建方式如下:

Bitmap bmp = new Bitmap(_currentImage.Width, _currentImage.Height, PixelFormat.Format48bppRgb);

位图由第三方函数调用填充,并加载了正确的图像。

现在,我想对它做一些简单的图像统计。有没有方便的方法来查询图像中的最小值和最大值,比如在RED通道中。

2 个答案:

答案 0 :(得分:2)

这是一个读取所有48bpp像素的简单版本,以Red为例(未经测试)

unsafe static ushort MaxRed(Bitmap bm)
{
    var bd = bm.LockBits(new Rectangle(Point.Empty, bm.Size), ImageLockMode.ReadOnly, PixelFormat.Format48bppRgb);
    ushort maxRed = 0;
    for (int y = 0; y < bm.Height; y++)
    {
        ushort* ptr = (ushort*)(bd.Scan0 + y * bd.Stride);
        for (int x = 0; x < bm.Width; x++)
        {
            ushort b = *ptr++;
            ushort g = *ptr++;
            ushort r = *ptr++;
            maxRed = Math.Max(maxRed, r);
        }
    }
    bm.UnlockBits(bd);
    return maxRed;
}

不安全,因为它比使用Marshal更容易,但您可以将其转换为该版本,例如使用ReadInt16(IntPtr, Int32)或将整个图像复制到数组中(当然,这会将其翻倍)空间要求)。

答案 1 :(得分:1)

正如@harold所指出的,您使用的图像格式阻止您使用GetPixel,因为此方法返回Color,其内部存储其rgb值为byte。当你使用每像素48位的图像(16位=每种颜色2个字节)时,一个字节就会变小。

因此,您需要使用返回LockBits对象的BitmapData方法。此返回对象的属性Scan0表示指向锁定范围数据中第一个字节的指针。

我想出了以下方法来获得最大r值。它将与PixelFormats属性中的两种指定格式一起使用,并且可以轻松添加更多格式。

public class PixelFormatData
{
    // example => rgb are three values,
    //         => argb are four values
    public int ValueCount { get; set; }
    public int BitsPerPixel { get; set; }

    public PixelFormatData(int valueCount, int bitsPerPixel)
    {
        ValueCount = valueCount;
        BitsPerPixel = bitsPerPixel;
    }
}

public static readonly Dictionary<PixelFormat, PixelFormatData> PixelFormats = new Dictionary<PixelFormat, PixelFormatData>
{
    { PixelFormat.Format24bppRgb, new PixelFormatData(3, 24) },
    { PixelFormat.Format48bppRgb, new PixelFormatData(3, 48) }
};

public static IEnumerable<byte[]> GetBytes(Bitmap image, int bytesPerPixel)
{
    var imageData = image.LockBits(new Rectangle(Point.Empty, image.Size), ImageLockMode.ReadOnly, image.PixelFormat);
    var ptr = imageData.Scan0;
    var imageSize = image.Width * image.Height;
    for (int x = 0; x < imageSize; x++)
    {
        yield return ptr.CopyAndMove(bytesPerPixel);
    }
    image.UnlockBits(imageData);
}

public static IEnumerable<int> GetValues(Bitmap image, int valueIndex)
{
    if (!PixelFormats.ContainsKey(image.PixelFormat))
        throw new ArgumentException(nameof(image.PixelFormat));

    var pixelFormatData = PixelFormats[image.PixelFormat];

    if (valueIndex < 0 || valueIndex >= pixelFormatData.ValueCount)
        throw new ArgumentException(nameof(valueIndex));

    int bytesPerPixel = pixelFormatData.BitsPerPixel / 8,
        bytesPerValue = bytesPerPixel / pixelFormatData.ValueCount;

    return GetBytes(image, bytesPerPixel)
        .Select(bytes =>
            bytes.Skip(bytesPerValue * valueIndex)
                 .Take(bytesPerValue)
                 .RightPad(4))
        .Select(valueData => BitConverter.ToInt32(valueData.ToArray(), 0));
}

使用代码需要这两种扩展方法。

public static class EnumerableExtensions
{
    public static List<T> RightPad<T>(this IEnumerable<T> collection, int total)
    {
        var list = collection.ToList();
        while (list.Count < 8)
            list.Add(default(T));
        return list;
    }
}

public static class IntPtrExtensions
{
    public static byte[] CopyAndMove(this IntPtr ptr, int count)
    {
        byte[] bytes = new byte[count];
        Marshal.Copy(ptr, bytes, 0, count);
        ptr += count;
        return bytes;
    }
}

这就是它的使用方式。

using (var file = new FileStream(@"C:\mypath\myPicture.png", FileMode.Open))
{
    Bitmap image = new Bitmap(file);
    // the color is saved in the followig format (gbr) so the 
    // red color is index 2
    Console.WriteLine(GetValues(image, 2).Max());
}

我使用Format24bppRgb图片对其进行了测试。

如果每个像素的位数为8且低于此值,您还可以使用GetPixel来检查每个像素。它比上面的方法慢大约3倍。

byte highestRed = 0;
using (var file = new FileStream(@"C:\mypath\mypicture.jpg", FileMode.Open))
{
    Bitmap image = new Bitmap(file);
    for (int x = 0; x < image.Width; x++)
    {
        for (int y = 0; y < image.Height; y++)
        {
            var color = image.GetPixel(x, y);
            if(highestRed < color.R)
                highestRed = color.R;
        }
    }
}