我在C#中有一个bitmap
对象,其创建方式如下:
Bitmap bmp = new Bitmap(_currentImage.Width, _currentImage.Height, PixelFormat.Format48bppRgb);
位图由第三方函数调用填充,并加载了正确的图像。
现在,我想对它做一些简单的图像统计。有没有方便的方法来查询图像中的最小值和最大值,比如在RED通道中。
答案 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;
}
}
}