如需快速参考,请参阅github link。
我正在尝试实现简单的JPEG压缩。 我将提供一些更值得注意的方法。
问题是我没有看到任何显着的0运行,所以我的RLE编码没有做任何压缩图像。
代码:
RGB到YCbCr转换:
private static Ycbcr AlternativeRgbtoYCbCr ( Rgb rgb ) {
var y = 16f + (65.481f*rgb.R + 128.553f*rgb.G + 24.966f*rgb.B);
var cr = 128f + (-37.797f * rgb.R - 74.203f * rgb.G + 112.0f * rgb.B);
var cb = 128f + (112.0f*rgb.R - 93.786f*rgb.G - 18.214f*rgb.B);
return new Ycbcr(y, cb, cr);
}
分裂到ColorSpaces:
/// <summary>
/// Loops through all the pixels and converts them to ycbcr
/// </summary>
public void SplitBytesIntoColorSpaces() {
if (_imageIsSplit) return;
for (var x = 0; x < LeftImageBitmap.Width; x++) {
//var innerlist = new List<Ycbcr>();
var innerY = new List<float>();
var innerCr = new List<float>();
var innerCb = new List<float>();
for (var y = 0; y < LeftImageBitmap.Height; y++) {
var color = ToRgb(LeftImageBitmap.GetPixel(x, y));
//innerlist.Add(RgbtoYCbCr(color));
innerY.Add(AlternativeRgbtoYCbCr(color).Y);
innerCr.Add(AlternativeRgbtoYCbCr(color).Cr);
innerCb.Add(AlternativeRgbtoYCbCr(color).Cb);
}
//ChromeList.Add(innerlist);
LumList.Add(innerY);
CrList.Add(innerCb);
CbList.Add(innerCr);
}
_imageIsSplit = true;
}
子采样:
/// <summary>
/// 4:2:0 subsampling
/// </summary>
private void SubSample420() {
var tempCrArray = new List<List<float>>(CrList.Count/2);
var tempCbArray = new List<List<float>>(CbList.Count/2);
for (var x = 0; x < CrList.Count/2; x++) {
var rowCr = new List<float>();
var rowCb = new List<float>();
for (var y = 0; y < CrList.Count/2; y++) {
rowCb.Add(0);
rowCr.Add(0);
}
tempCrArray.Add(rowCr);
tempCbArray.Add(rowCb);
}
for (int x = 0, x2 = 0; x < CrList.Count; x += 2, x2 ++) {
if (x2 >= tempCrArray.Count) continue;
var crrow = tempCrArray[x2];
var cbrow = tempCbArray[x2];
for (int y = 0, y2 = 0; y < CrList[x].Count; y += 2, y2++) {
if (y2 >= crrow.Count) continue;
crrow[y2] = CrList[x][y];
cbrow[y2] = CbList[x][y];
}
}
CrList = new List<List<float>>(tempCbArray);
CbList = new List<List<float>>(tempCrArray);
_subSampled420 = true;
}
现在进行dct处理:
逐步执行数组8x8的步骤功能。
/// <summary>
/// Steps through a 2d array 8 pixels at a time.
/// When x reaches the end, x is set to 0 and y
/// is incremented by 8.
/// </summary>
/// <param name="x">ref position to the loop's x</param>
/// <param name="y">ref position to the loop's y</param>
/// <param name="xl">maximum x position</param>
/// <param name="yl">maximum y position</param>
private static void Step(ref int x, ref int y, int xl, int yl) {
if (x + 8 < xl) {
x += 8;
}
else if (x + 8 >= xl) {
x = 0;
y += 8;
}
}
DCT:
//DCT function that creates a task and runs dct on it
public double[,] Go(double[,] d) {
_data = d;
var task = Task<double[,]>.Factory.StartNew(() => {
var output = new double[8, 8];
for (var x = 0; x < _data.GetLength(0); x++)
for (var y = 0; y < _data.GetLength(1); y++) {
output[x, y] = GetValueForward(x, y);
}
return output;
});
return task.Result;
}
//gets a singular value for dct
private double GetValueForward(int u, int v) {
double freq = 0;
for (var i = 0; i < 8; i++) {
for (var j = 0; j < 8; j++) {
freq +=
Math.Cos((2*i + 1)*u*Pi/16)*
Math.Cos((2*j + 1)*v*Pi/16)*
_data[i, j];
}
}
freq *= 2*C(u)*C(v)/Math.Sqrt(8*8);
return freq;
}
执行所有处理的功能:
/// <summary>
/// DctImageProcessor's main method.
/// This method will run on 8x8 chunks
/// and process them into double arrays by channel.
/// </summary>
public void Process() {
//get data manager's ycbcr chromelist
_luminanceDatalist = Manager.LumList;
_crDatalist = Manager.CrList;
_cbDatalist = Manager.CbList;
_lumOutList = new List<double[,]>();
_cbOutList = new List<double[,]>();
_crOutList = new List<double[,]>();
var lumx = _luminanceDatalist[0].Count;
var lumy = _luminanceDatalist.Count;
var channelx = _crDatalist[0].Count;
var channely = _crDatalist.Count;
//SetArray(_luminanceDatalist);
//dct
var dct = new Dct();
//quantizer
var q = new Quantizer();
//loop and Step() + store values into _lumOutList
for (int i = 0, j = 0; i < lumx && j < lumy; Step(ref i, ref j, lumx, lumy)) {
var lumarr = ForDctLum(i, j);
var lumdct = dct.Go(lumarr);
q.QuantizeLuminance(ref lumdct);
_lumOutList.Add(lumdct);
}
for ( int i = 0, j = 0; i < channelx && j < channely; Step(ref i, ref j, channelx, channely) ) {
var crArr = ForDctCr(i, j);
var cbArr = ForDctCb(i, j);
var crdct = dct.Go(crArr);
var cbdct = dct.Go(cbArr);
q.QuantizeChrominance(ref crdct);
q.QuantizeChrominance(ref cbdct);
_crOutList.Add(crdct);
_cbOutList.Add(cbdct);
}
//rle
var rleOutputs = _lumOutList.Select(Rle.ZigZag).ToList();
rleOutputs.AddRange(_crOutList.Select(Rle.ZigZag));
rleOutputs.AddRange(_cbOutList.Select(Rle.ZigZag));
var encoded = Rle.Encode(rleOutputs);
File.WriteAllBytes("./output.dct", encoded);
}
RLE: 之字形阅读:
public static byte[] ZigZag(double[,] input) {
var result = new double[8, 8];
var output = new byte[64];
int i = 0, j = 0;
var d = -1; // -1 for top-right move, +1 for bottom-left move
int start = 0, end = 8*8 - 1;
do {
output[start++] = (byte) input[i, j];
output[end--] = (byte) input[8 - i - 1, 8 - j - 1];
i += d;
j -= d;
if (i < 0) {
i++;
d = -d; // top reached, reverse
}
else if (j < 0) {
j++;
d = -d; // left reached, reverse
}
} while (start < end);
if (start == end)
result[i, j] = start;
return output;
}
运行长度编码。
public static byte[] Encode(List<byte[]> list) {
var ret = new List<byte>();
var prev = list[0][0];
byte count = 0;
const byte delim = 255;
foreach (var val in list.SelectMany(arr => arr)) {
if (val != prev) {
if (count > 1) {
ret.Add(delim);
ret.Add(count);
ret.Add(prev);
}
else {
ret.Add(prev);
}
prev = val;
count = 1;
}
else {
count ++;
prev = val;
}
}
return ret.ToArray();
}
我知道这是一个很长的帖子,但我无法自己解决这个问题。我们使用的这本书并没有多大帮助,所以我只能随意编写代码直到有效。
现在我可以使用可以均匀分割成8x8块的图像,例如github上的lena.tif图像。
问题出现在我的RGB&gt; YCBCR&gt;子样本&gt; DCT>量化 在这一点上,我的值没有像他们应该的那样运行0。
所有建议都表示赞赏。
编辑:
量化表:
private double[,] ChrominanceQuantizationMatrix { get; } = new double[8, 8] {
{17, 18, 24, 47, 99, 99, 99, 99},
{18, 21, 26, 66, 99, 99, 99, 99},
{24, 26, 56, 99, 99, 99, 99, 99},
{47, 66, 99, 99, 99, 99, 99, 99},
{99, 99, 99, 99, 99, 99, 99, 99},
{99, 99, 99, 99, 99, 99, 99, 99},
{99, 99, 99, 99, 99, 99, 99, 99},
{99, 99, 99, 99, 99, 99, 99, 99}
};
private double[,] LuminanceQuantizationMatrix { get; } = new double[8, 8] {
{16, 11, 10, 16, 24, 40, 51, 61},
{12, 12, 14, 19, 26, 58, 60, 55},
{14, 13, 16, 24, 40, 57, 69, 56},
{14, 17, 22, 29, 51, 87, 80, 62},
{18, 22, 37, 56, 68, 109, 103, 77},
{24, 35, 55, 64, 81, 104, 113, 92},
{49, 64, 78, 87, 103, 121, 120, 101},
{72, 92, 95, 98, 112, 100, 103, 99}
};
编辑2:
量化方法:
public void QuantizeLuminance(ref double[,] data) {
for (var i = 0; i < 8; i++) {
for (var j = 0; j < 8; j++) {
data[i, j] /= LuminanceQuantizationMatrix[i, j];
}
}
}