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

释放双眼,带上耳机,听听看~!

      所谓序列化,就是讲内存数据保存为磁盘数据的过程,反序列化就是反过来理解。对于图像处理程序来说,最主要的变量是图片,然后还有相关的参数或运算结果。这里区分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[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 }
是一个可以存储读取的结构。
 
三、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;

}

 

给TA打赏
共{{data.count}}人
人已打赏
随笔日记

nginx 配置反向代理,负载均衡实战解析,关于Nginx负载均衡的6种策略

2020-11-9 4:44:20

随笔日记

ASP.NET Core 静态文件

2020-11-9 4:44:22

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索