OpenCV中文网站

 找回密码
 立即注册
搜索
热搜: 安装 配置
查看: 2711|回复: 0

图像处理程序的序列化和反序列化

[复制链接]
发表于 2019-5-5 20:37:11 | 显示全部楼层 |阅读模式
                                                图像处理程序的序列化和反序列化                                                                              所谓序列化,就是讲内存数据保存为磁盘数据的过程,反序列化就是反过来理解。对于图像处理程序来说,最主要的变量是图片,然后还有相关的参数或运算结果。这里区分4个部分、由简单到复杂,分享一下自己的研究成果,希望能够给需要的工程师提供一些帮助。
   

一、基本操作
        OpenCV本身提供了FileStorage的序列化保存方法,这对于保存参数来说非常适合;但是如果用来保存图片,会将原始图片的体积多倍增大,速度也比较慢。Mfc本身也提供了序列化的操作,但是使用起来的话,需要注意的地方比较多,比不上OpenCV来的直接。        我们最终想要通过保存得到,并且能够被图像处理程序读取的,是一个单一的文件。这个文件不仅包含了图片数据,而且包括相关的参数和运算结果,同时这个文件不能太大。所以我想到采用zip压缩/解压的方式来打包原始图片和运算结果。实验证明,效果是能够符合要求的。
        在打包代码的选择上,找到了比较好的实现。zip.c++/unzip.c++中提供了稳定并且便于使用的压缩解压过程(具体使用参考对应的.h文件,压缩文件可以设定密码)。实际使用中,保存的时候参数保存为.xml文件,图片保存为.jpg图片,而后统一压缩成.go文件;读取的时候反过来操作。
        为了说明问题,编写例程。现在把使用说明一下,具体细节可以参考代码。
1、点击读取图片,可以读入jpg或bmp图片,同时手工设置参数一到三
2、点击保存,保存为.go文件
3、点击打开,打开相应的.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一类的变量来说,非常方便。但是如果是自己设定的结构体了?
二、存储自己的结构体
        这里给出一个新的例子,值得参考:
//另存当前模板数据
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.name.GetString()));
        string strTypeName(CW2A(m_rctTracker.typeName.GetString()));
        int iLeft = m_rctTracker.AreaTracker.m_rect.left;
        int iTop = m_rctTracker.AreaTracker.m_rect.top;
        int iWidth = m_rctTracker.AreaTracker.m_rect.Width();
        int iHeight = m_rctTracker.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 }


是一个可以存储读取的结构。

三、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中,非常有用。

而后再根据实际情况进行封装。
       
四、更进一步,进行类封装
        如果想更进一步,自然需要采用类的方法,这里是一个很好的例子。

#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, 100, true);
        matwrite(fs, image2, 200, true);
        matwrite(fs, image3, 300, true);
        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;
}








回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|OpenCV中文网站

GMT+8, 2024-3-29 01:48 , Processed in 0.020436 second(s), 15 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表