1. 程式人生 > >影象處理(Image Processing) ---------- 影象和影像壓縮(Compression)(C#實現)

影象處理(Image Processing) ---------- 影象和影像壓縮(Compression)(C#實現)

空間域壓縮:

 

  • 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