把opencv Mat 按位存成bmp二值图像 (1bit 1pixel)

网上有很多关于bmp图像的资料,但是很多都是存成灰度图,和彩色图,我这个代码是把Mat存成二值图
首先我肯定是站在前人的基础上,一些关于bmp的介绍操作
不错的代码: http://www.cnblogs.com/skyseraph/archive/2011/03/10/1979532.html

各种blog都说:      一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图象最下面一行的左边第一个象素,然后是左边第二个象素……接下来是倒数第二行左边第一个象素,左边第二个象素……依次类推 ,最后得到的是最上面一行的最右一个象素。
但是对于按位存储图像大家都忽略了一个很重要的东西:
对于一行来说左边的像素点存在内存的低位,右边的像素点存在内存的高位。
但是对于每一个byte中左边的像素点存在byte的高位,右边的像素点存在byte的低位。。

假设这里是一行
低地址
左边像素






高地址
右边像素

假设这里是一个Byte
高地址
左边像素






低地址
右边像素
尼玛,这个坑了我一天时间,之前弄出来的图像一直是错位的。    --------------》    
                                                                       正常的图像应该是--------------------》

1.用位操作把Mat存到一段类存中,注意每个像素占一个bit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int  mat_to_binary( const  cv::Mat img,  int  line_byte,  char * data)
{
     int  width = img.cols;
     int  height = img.rows;
     size_t  line_size = line_byte * 8;
     size_t  bit_size = line_size * height;
   
     char  *p = data;  int  offset, v; unsigned  char  temp;
     for ( int  row=height-1; row>=0; row-- ) {       
         for  ( int  col=0; col<width; col++ ) {  
             offset = col % 8;
             v = img.at<uchar>(row, col);
             temp = 1;
             temp = temp << (8 - offset -1);
             if (v == 255 ) {                
                 *(p + col/8) |= temp;
             else  {
                 temp = ~temp;
                 *(p + col/8) &= temp;
             }
         }
         for ( int  j = width/8 ; j < line_byte; j++)
             p[j] = 0;
         p = p + line_byte;
   }
   return  0;
}
2. 把Mat写成bmp格式的图像(包含 windows.h 头文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
int  save_bmp_image( const  cv::Mat img, std::string dst) 
{
     int  width = img.cols;
     int  height = img.rows;
     const  int  biBitCount = 1;
 
     //颜色表大小,以字节为单位,灰度图像颜色表为256*4字节,彩色图像颜色表大小为0,二值图为2*4
     int  color_type_num = 2;
     int  colorTablesize = color_type_num *  sizeof (RGBQUAD);
     RGBQUAD *pColorTable =  new  RGBQUAD[color_type_num];
     for ( int  i = 0; i < color_type_num; i++) {
         pColorTable[i].rgbBlue = i*255;
         pColorTable[i].rgbRed  = i*255;
         pColorTable[i].rgbGreen= i*255;
         pColorTable[i].rgbReserved = 0;
     }
 
     //待存储图像数据每行字节数为4的倍数
     int  line_byte = (width * biBitCount/8+3)/4*4;
     char * p_data = ( char *) malloc (line_byte*height);
     mat_to_binary(img, line_byte, p_data);
 
     std::ofstream fp(dst.c_str(), std::ios::binary | std::ios::out);
 
     if (!fp.is_open()) {
         std::cout <<  "open "  << dst << " failed!"  << std::endl;
         return  -1;
     }
 
     //申请位图文件头结构变量,填写文件头信息
     BITMAPFILEHEADER fileHead;
     fileHead.bfType = 0x4D42;   //bmp类型
     
     fileHead.bfSize=  sizeof (BITMAPFILEHEADER) +  sizeof (BITMAPINFOHEADER)\
         + colorTablesize + line_byte*height;               //bfSize是图像文件4个组成部分之和
     fileHead.bfReserved1 = 0;
     fileHead.bfReserved2 = 0;    
     fileHead.bfOffBits = 54+colorTablesize;               //bfOffBits是图像文件前3个部分所需空间之和    
     
     fp.write(( char *)&fileHead,  sizeof (BITMAPFILEHEADER)); //写文件头进文件
 
     //申请位图信息头结构变量,填写信息头信息
     BITMAPINFOHEADER head; 
     head.biBitCount = biBitCount;
     head.biClrImportant = 0;
     head.biClrUsed = 0;
     head.biCompression = 0;
     head.biHeight = height;
     head.biPlanes = 1;
     head.biSize = 40;
     head.biSizeImage = line_byte*height;
     head.biWidth = width;
     head.biXPelsPerMeter = 0;
     head.biYPelsPerMeter = 0;
 
     //写位图信息头进内存  
     fp.write(( char *)&head,  sizeof (BITMAPINFOHEADER));
 
     //颜色表,写入文件 
     fp.write(( char *)pColorTable,  sizeof (RGBQUAD)*color_type_num);  
 
     //写位图数据进文件pBmpBuf
     fp.write(( char *)p_data, height*line_byte);
     fp.close();
     
     delete  []pColorTable;
     delete  []p_data;
     return  0;
}

  • 后记:
关于把Mat转为二进制的函数  mat_to_binary()。
我试了很多个版本:
首先是boost::dynamic_bitset
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int  mat_to_binary_boost( const  cv::Mat img,  int  lineByte, boost::dynamic_bitset<> &data)
{
     int  width = img.cols;
     int  height = img.rows;
     size_t  line_size = lineByte * 8;
     size_t  bit_size = line_size * height;
     data.resize(bit_size);
     int  channel = img.channels();
     int  type = img.type();
     size_t  id = 0;   
     for ( int  row=height-1; row>=0; row-- ) {       
     //for(int row=0; row < height; row++ ) {       
         for  ( int  col=0; col<width; col++ ) {  
             unsigned  char  v = img.at<uchar>(row, col);
             if (v == 255 ) {
                 data[id + col/8*8 + 8 - col%8 - 1] = 1;
             else  {
                 data[id + col/8*8 + 8 - col%8 - 1] = 0;
             }           
         }        
         id += line_size;
     }    
     return  0;
}

从dynamic_bitset到一段指定的内存中, 我没有找到直接能够访问dynamic_bitset的办法
1
2
3
4
5
6
7
     boost::dynamic_bitset<> img_data;
     mat_to_binary_boost(img, line_byte, img_data);
     boost::dynamic_bitset<>::block_type* p_data = (boost::dynamic_bitset<>::block_type*) malloc ( sizeof ( char )*img_data.bits_per_block * img_data.num_blocks()/8 +1);
     int  temp =  sizeof ( char )*img_data.bits_per_block * img_data.num_blocks();
     int  t1 = img_data.bits_per_block;
     int  t2 =  img_data.num_blocks();
     boost::to_block_range(img_data, p_data);  //这里的p_data不能是一个char*, 一定要是boost::dynamic_bitset<>::block_type 类型.
                                              //因为这个函数的实现是调用std::copy(),类型不一样可能会出错
然后是std::bitset,一开始就没打算用std::bitset, 因为他只能开固定大小。但是无奈一开始 boost::dynamic_bitset 用得不对,也试了一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int  mat_to_binary_bit_set( const  cv::Mat img,  int  lineByte, std::bitset<11200>& data)
{
     int  width = img.cols;
     int  height = img.rows;
     size_t  line_size = lineByte * 8;
     size_t  bit_size = line_size * height;
    
     size_t  id = 0;   
     for ( int  row=height-1; row>=0; row-- ) {       
         for  ( int  col=0; col<width; col++ ) {  
             unsigned  char  v = img.at<uchar>(row, col);
             if (v == 255 ) {
                 data[id + col/8*8 + 8 - col%8 - 1] = 1;
             else  {
                 data[id + col/8*8 + 8 - col%8 - 1] = 0;
             }           
         }        
         id += line_size;
   }
   return  0;
}
这是我从bitset中取那一段内存的方法,其实这样做是完全错误的,最后得到图片出现了
 错位  -------------------------->>>>  
1
2
3
std::bitset<11200> data;
     mat_to_binary_bit_set(img,  line_byte, data);
     char * p_data = ( char *)&data[0];
后来把代码改成这样(这也是毫无道理的)得到的图片就 
正常了  ------------->>>>>>>>>>>
1
2
3
    std::bitset<11200> data;
     mat_to_binary_bit_set(img,  line_byte, data);
     char * p_data = ( char *)&data[0] + 28*17 + 16;
看了一下bitset关于operator [] 的源码:
1
2
3
4
5
6
       {    // test if bit at _Pos is set
         if  (_Bits <= _Pos)
             _Xran();     // _Pos off end
         return  ((_Array[_Pos / _Bitsperword]
             & ((_Ty)1 << _Pos % _Bitsperword)) != 0);
         }
这明明返回的是一个临时变量嘛,我把 p_data指针绑在一个 临时值上面, 这个值加上 28×17 +16 正好处于bitset 那一段内存。
这有可能就是以上 正常不正常的解释吧。。。。其实 data[id] 返回的是某一个bit的值,然而c++ 并没有指向bit 的指针,里面必然会做转化,这就注定了 &data[0]不能取到data的内存地址

我还试了一把vector<bool>,方式和bitset差不多,这个就更扯淡了,出来的全是一堆乱码。
vector<bool>'s elements aren't addressable because C++ doesn't have pointers and references to bits.  也是差不错的道理
  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值