圖像處理程序的序列化和反序列化

      所謂序列化,就是講內存數據保存爲磁盤數據的過程,反序列化就是反過來理解。對於圖像處理程序來講,最主要的變量是圖片,而後還有相關的參數或運算結果。這裏區分4個部分、由簡單到複雜,分享一下本身的研究成果,但願可以給須要的工程師提供一些幫助。
  
1、基本操做
        OpenCV自己提供了FileStorage的序列化保存方法,這對於保存參數來講很是適合;可是若是用來保存圖片,會將原始圖片的體積多倍增大,速度也比較慢。Mfc自己也提供了序列化的操做,可是使用起來的話,須要注意的地方比較多,比不上OpenCV來的直接。
        咱們最終想要經過保存獲得,而且可以被圖像處理程序讀取的,是一個單一的文件。這個文件不只包含了圖片數據,並且包括相關的參數和運算結果,同時這個文件不能太大。因此我想到採用zip壓縮/解壓的方式來打包原始圖片和運算結果。實驗證實,效果是可以符合要求的。
        在打包代碼的選擇上,找到了比較好的實現。zip.c++/unzip.c++中提供了穩定而且便於使用的壓縮解壓過程(具體使用參考對應的.h文件,壓縮文件能夠設定密碼)。實際使用中,保存的時候參數保存爲.xml文件,圖片保存爲.jpg圖片,然後統一壓縮成.go文件;讀取的時候反過來操做。
        爲了說明問題,編寫例程。如今把使用說明一下,具體細節能夠參考代碼。
一、點擊讀取圖片,能夠讀入jpg或bmp圖片,同時手工設置參數一到三
二、點擊保存,保存爲.go文件
三、點擊打開,打開相應的.go文件,同時解壓縮後,圖片和參數分別顯示出來。
        本例程主要展示的是「圖像處理程序的序列化和反序列化」,然後結合實際使用過程當中發現的問題進行衍生。但願可以有相似需求的工程師提供一些幫助。
    
主要代碼: //保存序列化結果
void CGOsaveView : : OnButtonSave()
{
    CString str1;string s1;
    CString str2;string s2;
    CString str3;string s3;

    CString szFilters = _T( "go(*.go)|*.go|*(*.*)|*.*||" );
    CString FilePathName = "" ;
    CFileDialog dlg(FALSE,NULL,NULL, 0 ,szFilters, this );
    if (dlg.DoModal() == IDOK){
        FilePathName = dlg.GetPathName();
    }   

    if (m_fimage.rows < = 0 )
    {
        AfxMessageBox( "m_fimage爲空!" );
        return ;
    }
   
    GetDlgItemText(IDC_EDIT1,str1);
    GetDlgItemText(IDC_EDIT2,str2);
    GetDlgItemText(IDC_EDIT3,str3);
    s1 = str1.GetBuffer( 0 );
    s2 = str2.GetBuffer( 0 );
    s3 = str3.GetBuffer( 0 );

    string filename = "params.xml" ;
    FileStorage fs(filename, FileStorage : : WRITE);
    fs << "str1" << s1;
    fs << "str2" << s2;
    fs << "str3" << s3;
    fs.release();

    imwrite( "m_fimage.jpg" ,m_fimage);

    AfxMessageBox( "數據保存成功!" );


    HZIP hz = CreateZip(FilePathName, "GreenOpen" ); //能夠設定密碼
    ZipAdd(hz, "params.xml" , "params.xml" );
    ZipAdd(hz, "m_fimage.jpg" , "m_fimage.jpg" );
    CloseZip(hz);
    AfxMessageBox( "數據壓縮成功!" );

  
}

//打開序列化結果
void CGOsaveView : : OnButtonOpen()
{
    string s1;
    string s2;
    string s3;

    CString szFilters = _T( "*(*.*)|*.*|go(*.go)|*.go||" );
    CString FilePathName = "" ;
    CFileDialog dlg(TRUE,NULL,NULL, 0 ,szFilters, this );
    if (dlg.DoModal() == IDOK){
        FilePathName = dlg.GetPathName();
    }   
    HZIP hz = OpenZip(FilePathName, "GreenOpen" );
    ZIPENTRY ze; GetZipItem(hz, - 1 , & ze); int numitems = ze.index;
    if (numitems < = 0 )
    {
        AfxMessageBox( "文件讀取錯誤!" );
        return ;
    }
    for ( int i = 0 ; i < numitems; i ++ )
    { 
        GetZipItem(hz,i, & ze);
        UnzipItem(hz,i,ze.name);
    }
    CloseZip(hz);
    AfxMessageBox( "數據解壓縮成功" );
    m_fimage = imread( "m_fimage.jpg" );
    if (m_fimage.rows < = 0 )
    {
        AfxMessageBox( "文件讀取錯誤!" );
        return ;
    }
    string filename = "params.xml" ;
    FileStorage fs(filename, FileStorage : : READ);
    fs[ "str1" ] >> s1;
    fs[ "str2" ] >> s2;
    fs[ "str3" ] >> s3;

    SetDlgItemText(IDC_EDIT1,s1.c_str());
    SetDlgItemText(IDC_EDIT2,s2.c_str());
    SetDlgItemText(IDC_EDIT3,s3.c_str());
    AfxMessageBox( "數據反序列成功" );
    SOURSHOW;
}
 
         咱們須要注意到的是這裏的Mat是能夠直接序列化的,這種方法對於存儲OpenCV一類的變量來講,很是方便。可是若是是本身設定的結構體了?
2、存儲本身的結構體
          這裏給出一個新的例子,值得參考:
//另存當前模板數據
BOOL CGOImageShopDoc : :OutPutElementItems(string filename)
{
    FileStorage fs(filename, FileStorage : :WRITE);
     ////具體寫下內容,注意OpenCV支持Rect等基礎結構的序列化
     int iElementStruct  = m_rctTracker.size(); //數量
    fs  <<  "iElementStruct"  << iElementStruct;
     //按照openCV推薦的方法來寫入和讀取數據。
    fs  <<  "ElementContent"  <<  "[";
     for ( int i  =  0; i  < iElementStruct; i ++)
    {
        string strName(CW2A(m_rctTracker[i].name.GetString()));
        string strTypeName(CW2A(m_rctTracker[i].typeName.GetString()));
         int iLeft  = m_rctTracker[i].AreaTracker.m_rect.left;
         int iTop  = m_rctTracker[i].AreaTracker.m_rect.top;
         int iWidth  = m_rctTracker[i].AreaTracker.m_rect.Width();
         int iHeight  = m_rctTracker[i].AreaTracker.m_rect.Height();
         fs << "{:" << "strName" <<strName << "strTypeName" <<strTypeName << "rectLeft" <<iLeft << "rectTop" <<iTop << "rectWidth" <<iWidth << "rectHeight" <<iHeight << "}";    
    }
    fs  <<  "]";
     ////書寫內容結束
    fs.release();
     return TRUE;
}
 
//讀取模板書
BOOL CGOImageShopDoc : :ReadElementsItems(string filename)
{
     //讀取數據
    FileStorage fs(filename, FileStorage : :READ);
     if (fs.isOpened())
    {
         //清空現有數據
        m_rctTracker.clear();
         //具體業務
         int iElementStruct  =  - 1;
        Rect rect;
        fs[ "iElementStruct">> iElementStruct;
        cv : :FileNode features  = fs[ "ElementContent"];
        cv : :FileNodeIterator it  = features.begin(), it_end  = features.end();
         int idx  =  0;
         for (; it  != it_end;  ++it, idx ++)
        {
            string strName;string strTypeName;
             int iLeft;
             int iTop;
             int iWidth;
             int iHeight;
            strName  = (string)( *it)[ "strName"];  //得到strName
            strTypeName =(string)( *it)[ "strTypeName"];
            iLeft  = ( int)( *it)[ "rectLeft"];
            iTop  = ( int)( *it)[ "rectTop"];
            iWidth  = ( int)( *it)[ "rectWidth"];
            iHeight  = ( int)( *it)[ "rectHeight"];
            CRect rect  = CRect(iLeft, iTop, iLeft +iWidth, iTop +iHeight); //得到rect
             //生成識別區域
            Mat matROI  = m_imageRaw(Rect(iLeft,iTop,iWidth,iHeight));
            vector <CRect > vecFindRect ;
             if (strTypeName  ==  "定位")
            {
                vecFindRect  = findRect(matROI);
            }
           ……
        }
    }
    fs.release();
    
     return TRUE;
}
若是咱們打開這裏保存的文件,能夠發現這種模式:
%YAML : 1. 0
-- -
iElementStruct : 15
ElementContent :
    - { strName : "定位", rectLeft : 37, rectTop : 73, rectWidth : 241,
       rectHeight : 120 }
    - { strName : "定位", rectLeft : 1556, rectTop : 107, rectWidth : 130,
       rectHeight : 70 }
    - { strName : "定位", rectLeft : 3127, rectTop : 99, rectWidth : 93,
       rectHeight : 70 }
    - { strName : "定位", rectLeft : 19, rectTop : 2187, rectWidth : 95,
       rectHeight : 77 }
    - { strName : "定位", rectLeft : 1592, rectTop : 2203, rectWidth : 95,
       rectHeight : 44 }
    - { strName : "定位", rectLeft : 3151, rectTop : 2184, rectWidth : 84,
       rectHeight : 68 }
    - { strName : "考號", rectLeft : 1042, rectTop : 419, rectWidth : 300,
       rectHeight : 121 }
    - { strName : "主觀分數", rectLeft : 161, rectTop : 678, rectWidth : 929,
       rectHeight : 63 }
    - { strName : "主觀分數", rectLeft : 1789, rectTop : 203, rectWidth : 869,
       rectHeight : 76 }
    - { strName : "主觀分數", rectLeft : 1777, rectTop : 717, rectWidth : 868,
       rectHeight : 64 }
    - { strName : "主觀分數", rectLeft : 1785, rectTop : 1713, rectWidth : 388,
       rectHeight : 66 }
    - { strName : "主觀題", rectLeft : 76, rectTop : 825, rectWidth : 1450,
       rectHeight : 1246 }
    - { strName : "主觀題", rectLeft : 1692, rectTop : 367, rectWidth : 1524,
       rectHeight : 323 }
    - { strName : "主觀題", rectLeft : 1696, rectTop : 864, rectWidth : 1518,
       rectHeight : 749 }
    - { strName : "主觀題", rectLeft : 1696, rectTop : 1787, rectWidth : 1534,
       rectHeight : 307 }
 
那麼,這種方式是OpenCV支持的結構保存方式,每個
- { strName : "主觀題", rectLeft : 1696, rectTop : 1787, rectWidth : 1534,
       rectHeight : 307 }
是一個能夠存儲讀取的結構。
 
3、FileNode支持哪些結構
          在這個例子中,咱們很是醜陋地使用了4個int值來定義一個Rect。爲何不能直接定義?
         好比編寫代碼
     string filename  =   "序列化.yml" ;
    FileStorage fs(filename, FileStorage : :WRITE);
    fs  <<  "str1"  << 1;
    cv : :Rect cvRect( 10, 10, 10, 10);
    fs << "cvRect" <<cvRect;
    fs.release();
     return  0;
         生成這樣的結果:
%YAML : 1. 0
-- -
str1 : 1
cvRect : [ 10, 10, 10, 10 ]
 
可是,若是咱們讀取這個Rect,而且編寫這樣的代碼
則會報錯:
          爲了進一步解析這個問題,翻看OpenCV的代碼:
class CV_EXPORTS_W_SIMPLE FileNode
{
public :
    //! type of the file storage node
    enum Type
    {
        NONE       = 0, //!< empty node
        INT       = 1, //!< an integer
        REAL       = 2, //!< floating-point number
        FLOAT     = REAL, //!< synonym or REAL
        STR       = 3, //!< text string in UTF-8 encoding
        STRING     = STR, //!< synonym for STR
        REF       = 4, //!< integer of size size_t. Typically used for storing complex dynamic structures where some elements reference the others
        SEQ       = 5, //!< sequence
        MAP       = 6, //!< mapping
        TYPE_MASK = 7,
        FLOW       = 8,   //!< compact representation of a sequence or mapping. Used only by YAML writer
        USER       = 16, //!< a registered object (e.g. a matrix)
        EMPTY     = 32, //!< empty structure (sequence or mapping)
        NAMED     = 64   //!< the node has a name (i.e. it is element of a mapping)
    };
         那麼的確是不可能直接轉換爲全部的OpenCV類型,這裏只是保存爲了其餘節點的序列,經過代碼測試也的確是這樣。
 
               在這種狀況下,咱們能夠首先將序列讀入vector中,很是有用。
然後再根據實際狀況進行封裝。
         
4、更進一步,進行類封裝
         若是想更進一步,天然須要採用類的方法,這裏是一個很好的例子。
# include  <opencv2\opencv.hpp >
# include  "opencv2/imgproc/imgproc.hpp"
# include  "opencv2/highgui/highgui.hpp"
# include  "opencv2/core/core.hpp"
# include  <iostream >
# include  <fstream >
 
using  namespace std;
using  namespace cv;
 
class ColletorMat
{
private :
     int indexFrame;
     bool found;
    Mat frame;
 
public :
 
    ColletorMat( int index,  bool found, Mat frame)
    {
         this - >indexFrame  = index;
         this - >found  = found;
         this - >frame  = frame;
    }
 
     ~ColletorMat()
    {
 
    }
 
     // settors
     void set_indexFrame( int index)
    {
         this - >indexFrame  = index;
    }
 
     void set_found( bool found)
    {
         this - >found  = found;
    }
 
     void set_frame(Mat frame)
    {
         this - >frame  = frame;
    }
 
     // accessors
     int get_indexFrame()
    {
         return  this - >indexFrame;
    }
 
     bool get_found()
    {
         return  this - >found;
    }
 
    Mat get_frame()
    {
         return  this - >frame;
    }
 
};
 
void matwrite(ofstream & fs,  const Mat & mat,  int index,  bool checking)
{
     // Data Object
     int indexFrame  = index;
     bool found  = checking;
    fs.write(( char *) &indexFrame,  sizeof( int));     // indexFrame
    fs.write(( char *) &found,  sizeof( bool));     // bool checking
 
     // Header
     int type  = mat.type();
     int channels  = mat.channels();
    fs.write(( char *) &mat.rows,  sizeof( int));     // rows
    fs.write(( char *) &mat.cols,  sizeof( int));     // cols
    fs.write(( char *) &type,  sizeof( int));         // type
    fs.write(( char *) &channels,  sizeof( int));     // channels
 
     // Data
     if (mat.isContinuous())
    {
        fs.write(mat.ptr < char >( 0), (mat.dataend  - mat.datastart));
    }
     else
    {
         int rowsz  = CV_ELEM_SIZE(type)  * mat.cols;
         for ( int r  =  0; r  < mat.rows;  ++r)
        {
            fs.write(mat.ptr < char >(r), rowsz);
        }
    }
}
 
ColletorMat matread(ifstream & fs)
{
     // Data Object
     int indexFrame;
     bool found;
    fs.read(( char *) &indexFrame,  sizeof( int));      //
    fs.read(( char *) &found,  sizeof( bool));          //
 
     // Header
     int rows, cols, type, channels;
    fs.read(( char *) &rows,  sizeof( int));          // rows
    fs.read(( char *) &cols,  sizeof( int));          // cols
    fs.read(( char *) &type,  sizeof( int));          // type
    fs.read(( char *) &channels,  sizeof( int));      // channels
 
     // Data
    Mat mat(rows, cols, type);
    fs.read(( char *)mat.data, CV_ELEM_SIZE(type)  * rows  * cols);
 
    ColletorMat ojbectMat(indexFrame, found, mat);
     return ojbectMat;
}
 
int main()
{
     // Save the random generated data
    {
        Mat image1, image2, image3;
        image1  = imread( "C:\\opencvVid\\data_seq\\Human3\\0001.jpg");
        image2  = imread( "C:\\opencvVid\\data_seq\\Human3\\0002.jpg");
        image3  = imread( "C:\\opencvVid\\data_seq\\Human3\\0003.jpg");
 
         if (image1.empty()  || image2.empty()  || image3.empty()) {
            std : :cout  <<  "error: image not readed from file\n";
             return( 0);
        }
 
        imshow( "M1",image1);
        imshow( "M2",image2);
        imshow( "M3",image3);
 
        ( char)cvWaitKey( 0);
 
        ofstream fs( "azdoudYoussef.bin", fstream : :binary);
        matwrite(fs, image1,  100true);
        matwrite(fs, image2,  200true);
        matwrite(fs, image3,  300true);
        fs.close();
 
         double tic  =  double(getTickCount());
        ifstream loadFs( "azdoudYoussef.bin", ios : :binary);
 
         if( !loadFs.is_open()){
            cout  <<  "error while opening the binary file"  << endl;
        }
 
        ColletorMat lcolletorMat1  = matread(loadFs);
        ColletorMat lcolletorMat2  = matread(loadFs);
        ColletorMat lcolletorMat3  = matread(loadFs);
 
        cout  <<  "frames loaded up "  << endl;
 
        vector <ColletorMat > setFrames;
        setFrames.push_back(lcolletorMat1);
        setFrames.push_back(lcolletorMat2);
        setFrames.push_back(lcolletorMat3);
 
        imshow( "1", lcolletorMat1.get_frame());
        imshow( "2", lcolletorMat2.get_frame());
        imshow( "3", lcolletorMat3.get_frame());
        ( char)cvWaitKey( 0);
 
        cout  <<  "indexFrame"  <<lcolletorMat1.get_indexFrame()  <<  "found"  << lcolletorMat1.get_found();
         double toc  = ( double(getTickCount())  - tic)  *  1000/ getTickFrequency();
        cout  <<  "Using Raw: "  << toc  << endl;
        loadFs.close();
 
    }
     return  0; }
相關文章
相關標籤/搜索