空间域压缩:
- Fractal Coding :https://blog.csdn.net/weixin_35811044
- Run Length Coding: 图像中连续出现的相同Pixel,只记录一个但需多一个符号记录其出现的次数,无损压缩。Ex.111110000003355 --> 51602325。 PCX图像就是采用此压缩方式,可以具体看我的另一篇blog:https://mp.csdn.net/postedit/83997166。
-
Sub-sampling:最基础的图像压缩技术,有损压缩,会降低图像的品质,根据人类眼睛对图像的色度(colour)不敏感而对亮度(luminance)很敏感,采取减少Pixel数的压缩方式。压缩方式:1、直接减,认为相靠近的Pixel都比较相似。比如2x2的方形,四个Pixel只保留一个。2、取均值,取四周的Pixle的平均值后保留。解压方式:1、直接复制。2、插值法。
-
Coarse Quantiztion: 与sub-sampling相似,但不同的是它的Pixel数不会减少,减少的是每个Pixel的bit数,也叫做bit depth reduction(pixel的深度减少)。如下图:每一个pixel的bit数从左到右减少.(大概像是8bit-->6bit-->2bit)
-
Vector quantization:类似于字典,会有一个table,每一个table entry由4个pixel构成,作为一个sample。压缩方式:将图像的pixel,每4个分为一个block,然后与table里的所有sample比较,取最为相似的sample记录其table entry number。解压方式:按照记录的table entry number 对照table 取出相应的 sample(4个pixel值)。这同样是一个有损压缩,因为table 不可能会存所有的pixel组合,所以取的是最相似的sample。
频域压缩:
- Transform Coding: 空间域转换到频域在图像处理中最常用的即DCT(Discrete Cosine Transform),空间域转换到频域是无失真的,若什么都不做直接反变换可得到原图。对于一张图来说资讯太大,为了降低复杂度,通常会先将图片按NxN分割成一个一个的小块,再逐一进行DCT转换 。如:8x8 有 64个Pixel,转换后有 8x8 64个频率系数,再用适合的方法zig.......來排序,排成为 低 -------------> 高 的一维频率系数,然后再量化压缩,最后反变换回空间域。
时域压缩(Video):
- Sub-sampling: 在空间域上sub-sampling是采用减少Pixel数,那么在时间域上同样可以如此,即减少帧数。比如: 30 frame / s 的视频,那么只记录1、3、5、7、9.....奇数帧,即可压缩一半。解压方式:要复原原来丢失2、4、6、8、10......。1、直接复制前面的帧。2、取前后帧的平均值。可以看出在时域依旧是有损压缩。
- Difference Coding: 也叫预测编码(压缩)法。在空间域里即Relative-coding压缩,记录与前一个Pixel值的差值(原来要记125、128、130,现在只要记125、3、2),这是根据相近的Pixel值变化通常不会太大。在时间域上也是如此,认为在短时间內,影像的动作变化也不会太大。因此压缩时记录时间轴上,与前一帧差值,为0的代表完全没有变化不记录的话,那就还需要多记录差值的(x,y)坐标,这样才知道差值是属于帧內哪一个pixel,这是额外的开销。如果很多pixel都变了那就需要很多而外的开销來记录差值的坐标,反而可能会出现比压缩前还大的情况。两种改进方式:1、本身Difference Coding是无失真的压缩,但是对于影像我们并不需要无失真,所以为了提高压缩比引入失真压缩,即当pixel的差异超过某个值才记录,否则就当作没有变。2、从pixel level 上升到 block level,即将帧按块分割(如:8x8),当一个block中所有的差值之和大于某一个值时才记录,否则当作没有变。(即Block Based Difference Coding)
- Block Based Motion Compensation: 当视频有大量的移动或者是摄像机的水平上下移动,Difference Coding就变的很无力了,就比如摄像头只要水平移动一点整个影像的pixel位置就全变了,差值就如雨后春笋般冒出。因此就需要移动补偿法。如图:当前压缩的帧叫current frame,前一帧叫reference frame,帧按一定大小分成了不重叠的block。假设当前压缩到current frame的蓝色block叫target block,然后将其从头(红色block)开始与reference frame中的每一个block对比,找的最相似的block(蓝色)叫matching block,然后记录两者之间的位置偏移(,),如果位置图像没变就记入(0,0)。解压方式:按照记录的偏移(,)结合本身坐标,映射回reference frame 取其block值。
这里有个疑问点:当前要压缩的帧多了前一帧中不存在的物体,那么压缩时按block对比,取前一帧中最相似的block作为记录。因此解压时这不存在的物体会用前一帧中很相似的block拼凑出来,可以说是失帧的。所以这儿还会使用一种技术叫GOP,即定时会更新一次帧,就是定时保留一个帧不压缩作为之后压缩帧的新reference frame。另外视频信息量是很大的1秒钟30张图片,所以当一个球突然飞入视频中,current frame有球,压缩时,reference frame是无球的,就会用reference frame中与球相似的block把球拼出来,虽然不太准确但是视频我们允许前面几帧不太准确,之后马上会有GOP更新,那么就会以有球的帧作为之后要压缩的帧参考的前一帧了。
C#实现视频压缩:
class VideoOperation
{
//获取当前压缩frame和其前一个frame的所有块,并存在对应的bitmap数组中。
public void VideoGetPool(Bitmap targetImage, Bitmap referenceImage, out List<Bitmap> CurrentPool, out List<Bitmap> CandidatePool)
{
CurrentPool = new List<Bitmap>();
CandidatePool = new List<Bitmap>();
int widthT = targetImage.Width;
int heightT = targetImage.Height;
int widthR = referenceImage.Width;
int heightR = referenceImage.Height;
//Rangeblock和Domainblock的大小(单边)
int rangeSize = 4;
int domainSize = 4;
//块数
int Rx = widthT / rangeSize;
int Ry = heightT / rangeSize;
int Dx = widthR / domainSize;
int Dy = heightR / domainSize;
int x1 = 0;
int y1 = 0;
int x2 = 0;
int y2 = 0;
for (int j = 0; j < Ry; j++)
{
for (int i = 0; i < Rx; i++)
{
Bitmap CurrentBlock = targetImage.Clone(new Rectangle(x1, y1, rangeSize, rangeSize), PixelFormat.Format24bppRgb);
CurrentPool.Add(CurrentBlock);
x1 += rangeSize;
}
y1 += rangeSize;
x1 = 0;
}
for (int j = 0; j < Dy; j++)
{
for (int i = 0; i < Dx; i++)
{
Bitmap domainBlock = referenceImage.Clone(new Rectangle(x2, y2, domainSize, domainSize), PixelFormat.Format24bppRgb);
CandidatePool.Add(domainBlock);
x2 += domainSize;
}
y2 += domainSize;
x2 = 0;
}
}
//均值方式计算块间差异:
public double CalculateMin(Bitmap targetImage, Bitmap referenceImage)
{
double sumR = 0;
double sumD = 0;
double aveR = 0;
double aveD = 0;
for (int j = 0; j < 4; j++)
{
for (int i = 0; i < 4; i++)
{
sumR += targetImage.GetPixel(i, j).R;
sumD += referenceImage.GetPixel(i, j).R;
}
}
aveR = sumR / 16.0;
aveD = sumD / 16.0;
double s = Math.Abs(aveR - aveD);
return s;
}
}
//输入从VideoGetPool中获取的参照块池和当前块池。逐一对比,找到最像的块,记录坐标偏移。
private void VideoEncode(List<Bitmap> CurrentPool, List<Bitmap> CandidatePool)
{
double s = 0;
Dictionary<int, int> MotionVector = new Dictionary<int, int>();
List<double> ssum = new List<double>();
for (int k = 0; k < CurrentPool.LongCount(); k++)
{
if (vop.CalculateMin(CurrentPool[k], CandidatePool[k]) < 10)
{
MotionVector.Add(k, k);
}
else
{
for (int t = 0; t < CandidatePool.LongCount(); t++)
{
//调用均值计算,对比块间差距。
s = vop.CalculateMin(CurrentPool[k], CandidatePool[t]);
ssum.Add(s);
//if (s < 1) break;
}
double min = ssum[0];
int motion = 0;
for (int n = 0; n < ssum.LongCount(); n++)
{
if (ssum[n] < min)
{ //取差距最小的块,储存。
min = ssum[n];
motion = n;
}
}
MotionVector.Add(k, motion);
ssum.Clear();
}
}
//将坐标偏移写入文档存储,作为压缩文件。
StreamWriter sfile = new StreamWriter(@"F:/VirtualStudioData/ImageProcessing01/encode.txt",true);
int x = 0;
int y = 0;
for (int i = 0; i < MotionVector.LongCount(); i++)
{
if (i == MotionVector.LongCount()-1)
{
x = MotionVector[i] % 64 * 4;
y = MotionVector[i] / 64 * 4;
sfile.Write(x + "," + y + ";" + "|");
}
else
{
x = MotionVector[i] % 64 * 4;
y = MotionVector[i] / 64 * 4;
sfile.Write(x + "," + y + ";");
}
}
sfile.Flush();
sfile.Close();
//file.Close();
}
仅为个人理解,如有不足,请指教。 https://blog.csdn.net/weixin_35811044