如何使用c#(读取每个像素的值)处理和提取图像数据,其中像素是10位深度?
此外,图像有4个波段(R,G,B和NIR)。
提前致谢。
答案 0 :(得分:1)
我在C ++中编码而不是在C#中编码所以你需要移植我的代码......
您应该添加像素合成(每个波段的位数及其顺序)。
所以我会创建一个,你必须改变你的情况!!!
bit: |9 8 7 6 5 4 3 2 1 0|
band: | R | G | B | NIR |
R - 2位
现在如何使用它......
现在4 * 10 = 40和40/8 = 5这意味着每4个像素对齐5个字节(LCM(10,8))
假设这样可以保存您的图像
int xs,ys; // resolution
int siz; // BYTE size of whole image data ... siz = ceil(xs*ys*10/8)
BYTE *dat=new BYTE[siz+5]; // 10bit image data
所以现在如何从5个BYTES读取4个像素并转换为更符合BYTE的东西......
数据布局是这样的:
| 0 | 1 | 2 | 3 | 4 | // BYTE
|rrgggbbn|nn rrgggb|bnnn rrgg|gbbnnn rr|gggbbnnn|
| 0 | 1 | 2 | 3 | // Pixel
首先选择与BYTES对齐的新像素格式
bit: |15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0|
band: | R | G | B | NIR | // all bands are 4 bits
我会像这样转换像素格式:
void convert10to16 (BYTE *dst,BYTE *src)
{
int i=0,o=0;
BYTE in,out;
in=scr[i]; i++; // rrgggbbn
out =(in>>2)&0x30; // 00rr0000
out|=(in>>3)&0x07; // 00rr0ggg
dst[o]=out; o++;
out =(in<<3)&0x30; // 00bb0000
out|=(in<<2)&0x04; // 00bb0n00
in=scr[i]; i++; // nnrrgggb
out|=(in>>6)&0x03; // 00bb0nnn
dst[o]=out; o++;
out =(in )&0x30; // 00rr0000
out|=(in>>1)&0x07; // 00rr0ggg
dst[o]=out; o++;
out =(in<<5)&0x20; // 00b00000
in=scr[i]; i++; // bnnnrrgg
out|=(in>>3)&0x10; // 00bb0000
out|=(in>>4)&0x07; // 00bb0nnn
dst[o]=out; o++;
out =(in<<2)&0x30; // 00rr0000
out|=(in<<1)&0x06; // 00rr0gg0
in=scr[i]; i++; // gbbnnnrr
out|=(in>>7)&0x01; // 00rr0ggg
dst[o]=out; o++;
out =(in>>1)&0x30; // 00bb0000
out|=(in>>2)&0x07; // 00bb0nnn
dst[o]=out; o++;
out =(in<<4)&0x30; // 00rr0000
in=scr[i]; i++; // gggbbnnn
out|=(in>>5)&0x07; // 00rr0ggg
dst[o]=out; o++;
out =(in<<1)&0x30; // 00bb0000
out|=(in )&0x07; // 00bb0nnn
dst[o]=out; o++;
}
希望我没有错误或在某处错字,但你应该明白这个想法
所以现在只是
BYTE *xxx=new BYTE[xs*ys*2+8] // 16 bit per pixel data (2 BYTE per pixel)
BYTE *src,*dst;
int i;
for (src=dat,dst=xxx,i=0;i<siz;i+=5,src+=5,dst+=8)
convert10to16(dst,src);
您也可以重写此内容以访问单个像素而无需转换,但访问速度要慢得多
答案 1 :(得分:0)
我通过ScummVM代码启发自己,根据数组中每个颜色分量的位长和位位置编写此类来自动转换颜色:
/// <summary>
/// Class to automate the unpacking (and packing/writing) of RGB(A) colours in colour formats with packed bits.
/// Inspired by https://github.com/scummvm/scummvm/blob/master/graphics/pixelformat.h
/// This class works slightly differently than the ScummVM version, using 4-entry arrays for all data, with each entry
/// representing one of the colour components, so the code can easily loop over them and perform the same action on each one.
/// </summary>
public class PixelFormatter
{
/// <summary>Standard PixelFormatter for .Net's RGBA format.</summary>
public static PixelFormatter Format32BitArgb = new PixelFormatter(4, 8, 16, 8, 8, 8, 0, 8, 24, true);
/// <summary>Number of bytes to read per pixel.</summary>
private Byte bytesPerPixel;
/// <summary>Amount of bits for each component (R,G,B,A)</summary>
private Byte[] bitsAmounts = new Byte[4];
/// <summary>Amount of bits to shift for each component (R,G,B,A)</summary>
private Byte[] shiftAmounts = new Byte[4];
/// <summary>Masks to limit the amount of bits for each component, derived from the bitsAmounts.</summary>
private UInt32[] limitMasks = new UInt32[4];
/// <summary>Multiplier for each component (R,G,B,A). If not explicitly given this can be derived from the number of bits.</summary>
private Double[] multipliers = new Double[4];
/// <summary>Defaults for for each component (R,G,B,A)</summary>
private Byte[] defaults = new Byte[] { 0, 0, 0, 255 };
/// <summary>True to read the input bytes as little-endian.</summary>
private Boolean littleEndian;
/// <summary>The colour components. Though most stuff will just loop an int from 0 to 4, this shows the order.</summary>
private enum ColorComponent
{
Red = 0,
Green = 1,
Blue = 2,
Alpha = 3
}
/// <summary>
/// Creats a new PixelFormatter, with automatic calculation of colour multipliers using the CalculateMultiplier function.
/// </summary>
/// <param name="bytesPerPixel">Amount of bytes to read per pixel</param>
/// <param name="redBits">Amount of bits to read for the red colour component</param>
/// <param name="redShift">Amount of bits to shift the data to get to the red colour component</param>
/// <param name="greenBits">Amount of bits to read for the green colour component</param>
/// <param name="greenShift">Amount of bits to shift the data to get to the green colour component</param>
/// <param name="blueBits">Amount of bits to read for the blue colour component</param>
/// <param name="blueShift">Amount of bits to shift the data to get to the blue colour component</param>
/// <param name="alphaBits">Amount of bits to read for the alpha colour component</param>
/// <param name="alphaShift">Amount of bits to shift the data to get to the alpha colour component</param>
/// <param name="littleEndian">True if the read bytes are interpreted as little-endian.</param>
public PixelFormatter(Byte bytesPerPixel, Byte redBits, Byte redShift, Byte greenBits, Byte greenShift,
Byte blueBits, Byte blueShift, Byte alphaBits, Byte alphaShift, Boolean littleEndian)
: this(bytesPerPixel,
redBits, redShift, CalculateMultiplier(redBits),
greenBits, greenShift, CalculateMultiplier(greenBits),
blueBits, blueShift, CalculateMultiplier(blueBits),
alphaBits, alphaShift, CalculateMultiplier(alphaBits), littleEndian)
{ }
/// <summary>
/// Creates a new PixelFormatter.
/// </summary>
/// <param name="bytesPerPixel">Amount of bytes to read per pixel</param>
/// <param name="redBits">Amount of bits to read for the red colour component</param>
/// <param name="redShift">Amount of bits to shift the data to get to the red colour component</param>
/// <param name="redMultiplier">Multiplier for the red component's value to adjust it to the normal 0-255 range.</param>
/// <param name="greenBits">Amount of bits to read for the green colour component</param>
/// <param name="greenShift">Amount of bits to shift the data to get to the green colour component</param>
/// <param name="greenMultiplier">Multiplier for the green component's value to adjust it to the normal 0-255 range.</param>
/// <param name="blueBits">Amount of bits to read for the blue colour component</param>
/// <param name="blueShift">Amount of bits to shift the data to get to the blue colour component</param>
/// <param name="blueMultiplier">Multiplier for the blue component's value to adjust it to the normal 0-255 range.</param>
/// <param name="alphaBits">Amount of bits to read for the alpha colour component</param>
/// <param name="alphaShift">Amount of bits to shift the data to get to the alpha colour component</param>
/// <param name="alphaMultiplier">Multiplier for the alpha component's value to adjust it to the normal 0-255 range.</param>
/// <param name="littleEndian">True if the read bytes are interpreted as little-endian.</param>
public PixelFormatter(Byte bytesPerPixel, Byte redBits, Byte redShift, Double redMultiplier,
Byte greenBits, Byte greenShift, Double greenMultiplier,
Byte blueBits, Byte blueShift, Double blueMultiplier,
Byte alphaBits, Byte alphaShift, Double alphaMultiplier, Boolean littleEndian)
{
this.bytesPerPixel = bytesPerPixel;
this.littleEndian = littleEndian;
this.bitsAmounts [(Int32)ColorComponent.Red] = redBits;
this.shiftAmounts[(Int32)ColorComponent.Red] = redShift;
this.multipliers [(Int32)ColorComponent.Red] = redMultiplier;
this.limitMasks[(Int32)ColorComponent.Red] = GetLimitMask(redBits, redShift);
this.bitsAmounts [(Int32)ColorComponent.Green] = greenBits;
this.shiftAmounts[(Int32)ColorComponent.Green] = greenShift;
this.multipliers [(Int32)ColorComponent.Green] = greenMultiplier;
this.limitMasks[(Int32)ColorComponent.Green] = GetLimitMask(greenBits, greenShift);
this.bitsAmounts [(Int32)ColorComponent.Blue] = blueBits;
this.shiftAmounts[(Int32)ColorComponent.Blue] = blueShift;
this.multipliers [(Int32)ColorComponent.Blue] = blueMultiplier;
this.limitMasks[(Int32)ColorComponent.Blue] = GetLimitMask(blueBits, blueShift);
this.bitsAmounts [(Int32)ColorComponent.Alpha] = alphaBits;
this.shiftAmounts[(Int32)ColorComponent.Alpha] = alphaShift;
this.multipliers [(Int32)ColorComponent.Alpha] = alphaMultiplier;
this.limitMasks[(Int32)ColorComponent.Alpha] = GetLimitMask(alphaBits, alphaShift);
}
private static UInt32 GetLimitMask(Byte bpp, Byte shift)
{
return (UInt32)(((1 << bpp) - 1) << shift);
}
/// <summary>
/// Using this multiplier instead of a basic int ensures a true uniform distribution of values of this bits length over the 0-255 range.
/// </summary>
/// <param name="colorComponentBitLength">Bits length of the color component</param>
/// <returns>The most correct multiplier to convert colour components of the given bits length to a 0-255 range.</returns>
public static Double CalculateMultiplier(Byte colorComponentBitLength)
{
return 255.0 / ((1 << colorComponentBitLength) - 1);
}
public Color GetColor(Byte[] data, Int32 offset)
{
UInt32 value = ArrayUtils.ReadIntFromByteArray(data, offset, this.bytesPerPixel, this.littleEndian);
return GetColorFromValue(value);
}
public void WriteColor(Byte[] data, Int32 offset, Color color)
{
UInt32 value = GetValueFromColor(color);
ArrayUtils.WriteIntToByteArray(data, offset, this.bytesPerPixel, this.littleEndian, value);
}
public Color GetColorFromValue(UInt32 readValue)
{
Byte[] components = new Byte[4];
for (Int32 i = 0; i < 4; i++)
{
if (bitsAmounts[i] == 0)
components[i] = defaults[i];
else
components[i] = GetChannelFromValue(readValue, (ColorComponent)i);
}
return Color.FromArgb(components[(Int32)ColorComponent.Alpha],
components[(Int32)ColorComponent.Red],
components[(Int32)ColorComponent.Green],
components[(Int32)ColorComponent.Blue]);
}
private Byte GetChannelFromValue(UInt32 readValue, ColorComponent type)
{
UInt32 val = (UInt32)(readValue & limitMasks[(Int32)type]);
val = (UInt32)(val >> this.shiftAmounts[(Int32)type]);
Double valD = (Double)(val * multipliers[(Int32)type]);
return (Byte)Math.Min(255, Math.Round(valD, MidpointRounding.AwayFromZero));
}
public UInt32 GetValueFromColor(Color color)
{
Byte[] components = new Byte[] { color.R, color.G, color.B, color.A};
UInt32 val = 0;
for (Int32 i = 0; i < 4; i++)
{
UInt32 mask = (UInt32)((1 << bitsAmounts[i]) - 1);
Double tempValD = (Double)components[i] / this.multipliers[i];
UInt32 tempVal = (Byte)Math.Min(mask, Math.Round(tempValD, MidpointRounding.AwayFromZero));
tempVal = (UInt32)(tempVal << this.shiftAmounts[i]);
val |= tempVal;
}
return val;
}
}
使用它需要知道的是需要考虑字节的顺序(little-endian或big-endian),最终读取值中每个颜色分量的位偏移(我限制为32)比特,因为它总是为每个组件提供一个完整的字节),以及每个组件的位数。
当然,您使用的是NIR而不是alpha,但这并没有改变任何方法;即使使用我的代码,你也可以考虑&#34; alpha&#34;值为NIR。
如果这些位确实被打包在一起,其中下一个像素使用前一个像素的剩余6位,依此类推,事情变得更加复杂,并且你必须以像素为单位处理它们,正如Spektre所说的那样在他的回答中。但是你可以自己阅读并移动它们(使用>>
将它们向下移动)以获得最终值,然后直接调用GetColorFromValue
来获取颜色。
我用它来读取从某些N64 ROM中提取的图像的一个例子:
//bytes 84 21 ==> 0x8421 (BE) ==bin==> 1000 0100 0010 0001 ==split==> 10000 10000 10000 1 ==dec==> 16 16 16 1 (RGBA) ==adjust==> 128 128 128 255
private static PixelFormatter SixteenBppFormatter = new PixelFormatter(2, 5, 11, 5, 6, 5, 1, 1, 0, false);
protected static Byte[] Convert16bTo32b(Byte[] imageData, Int32 startOffset, Int32 width, Int32 height, ref Int32 stride)
{
Int32 newImageStride = width * 4; ;
Byte[] newImageData = new Byte[height * newImageStride];
for (Int32 y = 0; y < height; y++)
{
for (Int32 x = 0; x < width; x++)
{
Int32 sourceOffset = y * stride + x * 2;
Int32 targetOffset = y * newImageStride + x * 4;
Color c = SixteenBppFormatter.GetColor(imageData, startOffset + sourceOffset);
PixelFormatter.Format32BitArgb.WriteColor(newImageData, targetOffset, c);
}
}
stride = newImageStride;
return newImageData;
}
获取颜色值的实际方法现在比我在样本解码注释中所做的简单* 8更准确; CalculateMultiplier
函数负责将值均匀分布在0-255范围内。虽然如果你想要以简单的方式(可能在转换中导致较少的舍入错误)这样做,可以使用更复杂的构造函数手动给出乘数。
然后将最后的字节插入到新创建的32位图像中,this method用于访问和写入图像的基础字节。
哦,这是我ReadIntFromByteArray
课程中提及的WriteIntToByteArray
和ArrayUtils
:
public static UInt32 ReadIntFromByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian)
{
Int32 lastByte = bytes - 1;
if (data.Length < startIndex + bytes)
throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to write a " + bytes + "-byte value at offset" + startIndex + ".");
UInt32 value = 0;
for (Int32 index = 0; index < bytes; index++)
{
Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
value += (UInt32)(data[offs] << (8 * index));
}
return value;
}
public static void WriteIntToByteArray(Byte[] data, Int32 startIndex, Int32 bytes, Boolean littleEndian, UInt32 value)
{
Int32 lastByte = bytes - 1;
if (data.Length < startIndex + bytes)
throw new ArgumentOutOfRangeException("startIndex", "Data array is too small to write a " + bytes + "-byte value at offset" + startIndex + ".");
for (Int32 index = 0; index < bytes; index++)
{
Int32 offs = startIndex + (littleEndian ? index : lastByte - index);
data[offs] = (Byte)(value >> (8 * index) & 0xFF);
}
}