图像处理(Image Processing) ---------- 图像和影像压缩(Compression)(C#实现)

版权声明:此为个人学习与研究成果,若需转载请提前告知。 https://blog.csdn.net/weixin_35811044/article/details/84328876

空间域压缩:

  • 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,然后记录两者之间的位置偏移(\Delta x,\Delta y),如果位置图像没变就记入(0,0)。解压方式:按照记录的偏移(\Delta x,\Delta y)结合本身坐标,映射回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

猜你喜欢

转载自blog.csdn.net/weixin_35811044/article/details/84328876