环境:Win7+VS2013+OpenCV2.4.13
材料:14bit的raw红外数据,低8位+高8位
raw格式:
1、摄像头或者探测器得到的原始数据,一般的是14位,于是需要两个字节保存。
2、只有一个通道像素数据。
BMP格式:
1、win最常用图片格式
2、有数据头,信息头,数据等等信息
把raw变成bmp,使用opencv,很多教程都说可以使用cvCvtColor函数,但是我一直没有成功过,可能是我的raw数据和标准的有冲突或者不一致。
于是,我纯手工完成。
首先,我有一个思路就是,听说所有bmp的文件头和信息头都是一样的,并且占据1078个字节,找了几幅图像验证了一下,好像是一样的,于是乎,读取已经存在的bmp的文件头和信息头,然后把raw数据直接加到文件头和信息头的后面。
下面是我的代码:
#define BMP_WIDTH 384
#define BMP_HEIGHT 288
#define WRITE_COUNT_PILXEL 2 //写的时候每个像素点4个字节
#define BMP_COUNT_PILXEL 1 //图像每个像素3个字节
#define BUFFER_SIZE BMP_WIDTH*BMP_HEIGHT*WRITE_COUNT_PILXEL //缓冲区大小
#define BMP_BUFFER_SIZE BMP_WIDTH*BMP_HEIGHT*BMP_COUNT_PILXEL //用于存储图像的缓冲区大小
//使用纯c代码
//可以出图,但是图形自己基本没有了
//按照第二种是可以出图的,很清晰的。
int raw2bmp()
{
/***********读取本地bmp文件头*************/
unsigned char buf1[111670] = { 0 };// 111670=384*288+1078
FILE *fp1 = fopen("D:\\images\\CV\\图片\\chuliyihou.bmp", "rb");
if (!fp1)
{
printf("fopen failed \n");
return 0;
}
fread(buf1, 1, 1078, fp1);
fclose(fp1);
/***********读取本地raw文件头*************/
unsigned char read_buffer[BUFFER_SIZE] = { 0 };//初始化read_buffer缓冲区一维数组
FILE *fp = fopen("D:\\images\\CV\\图片\\yuantu.raw", "rb");//打开lena.raw
if (fp == NULL)
{
printf("can't open the file yuantu.raw\n");
return -1;
}
int ret = fread(read_buffer, BUFFER_SIZE, 1, fp);
if (ret != 1)
{
printf("Fail to read raw data\r\n");
return -2;
}
int i, j;
long max = INT_MIN;
long min = INT_MAX;
//求出整幅图里面像素最大值和最小值
for (i = 0; iif ((i + 1) % 2){
unsigned char a2 = read_buffer[i];
unsigned char a1 = read_buffer[i + 1];
long t1 = a1 << 8;
long t2 = t1 + a2;
if (t2 > max){
max = t2;
}
if (t2 < min){
min = t2;
}
}
}
printf("max=%d", max);
printf("min=%d", min);
j = 1078;
for (i = 0; iif ((i + 1) % 2){
unsigned char a2 = read_buffer[i];
unsigned char a1 = read_buffer[i + 1];
//按14到8位的归一化
long t1 = a1 << 8;
long t2 = t1 + a2;
//unsigned char res = t2 / 64; //第一种方式
unsigned char res = (255 * (t2 - min)) / (max - min);//第二种
buf1[j] = res;
j = j + 1;
}
}
FILE *fp_save = fopen("D:\\images\\CV\\图片\\yuantu.bmp", "wb");
fwrite(buf1, 1, 111670, fp_save);
fclose(fp_save);
cin.get();
return 0;
}
注意:为什么要求最大值和最小值?这是因为我的是红外探测的raw格式数据,像素分布很集中,对比度很低,基本是一片白或者一片黑,最大值和最小值可以完成像素值分散。
问题:不管我怎么写,怎么改,我的bmp和原图的有一个旋转角度,我解决不了,但是总算是出现图片了。出于不满意,另外想了一条思路。
第二条思路:
在看OpenCV的Mat类是时候,他有一个构造函数:
//! constructor for matrix headers pointing to user-allocated data
Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);
参数介绍:
rows:行,也就是高;
cols:列,也就是宽;
type:可以写CV_8UC1,表示一个字节长度的unsigned char类型;
data:属于void*类型,任何指针类型都可以传进来,为了适配;
step:默认就好;
通过这个构造函数,我大概猜想一下,如果我可以得到一个8位的unsigned char类型的数组,宽和高也知道的情况下,raw是不是就可以直接变成Mat呢?
现在问题就在如何把14bit的raw变成8bit的,网上有直接舍弃低6位,这也是最简单最直接的方法,但是这样就把我对比度本来不大的图片变得越发纯了。放弃这个方法。也就是说,我需要把14bit的按比例缩小到8bit。
还有一种,14bit和8bit之间隔了6bit,把14bit的数据统一除以64(2的6次方),我发现这种方式依然对我的红外图像的对比度有很大影响,也是看不清楚的。
最后在实验室师妹的启发下,给了下面的公式:
Max:原来14bit的像素中最大值
Min:原来14bit的像素中最小值
val:原来14bit的像素的实际值,也就是变换前的值
ret:变化后的8bit像素值
我的代码如下:
//使用opencv完成raw到bmp的转变
//完美实现
int createBmpFromRaw()
{
char *rawFileName = "D:\\images\\CV\\图片\\yuantu.raw";
FILE *fp = NULL;
int ret = 0;
int width = 384;
int height = 288;
//为14位的raw数据分配内存空间
//为什么要乘以2?这是因为两个字节存储一个像素
unsigned char pRawData[384 * 288 * 2] = { 0 };
if (NULL == pRawData)
{
printf("Fail to calloc pRawData!\n");
return -1;
}
//打开raw文件
fp = fopen(rawFileName, "rb");
if (NULL == fp)
{
printf("Fail to read %s!\n", rawFileName);
return -1;
}
//读取数据到pRawData中去
//第二个参数表示读取字节数
ret = fread(pRawData, 384 * 288 * 2, 1, fp);
if (1 != ret)
{
printf("Fail to fread data!\n");
return -1;
}
//这个用来存储变化后的8bit的数组结果
unsigned char buf1[384 * 288] = { 0 };//把14位的变成8位,那么数组长度减半
int i, j;
long max = INT_MIN;
long min = INT_MAX;
//求出整幅图里面像素最大值和最小值
for (i = 0; i<384 * 288 * 2; i++){
if ((i + 1) % 2){
unsigned char a2 = pRawData[i];
unsigned char a1 = pRawData[i + 1];
long t1 = a1 << 8;
long t2 = t1 + a2;
if (t2 > max){
max = t2;
}
if (t2 < min){
min = t2;
}
}
}
//经过测试,可以得到最大像素值7660和最小像素值6167
//这个值和我用Matlab读取的是一致的
printf("max=%d", max);
printf("min=%d", min);
j = 0;
for (i = 0; i<384 * 288 * 2; i++){
if ((i + 1) % 2){
unsigned char a2 = pRawData[i];
unsigned char a1 = pRawData[i + 1];
//按14到8位的归一化
long t1 = a1 << 8;
long t2 = t1 + a2;
//第一种直接舍弃的,忽略了
//unsigned char res = t2 / 64; //第二种方式
unsigned char res = (255 * (t2 - min)) / (max - min);//第三种
buf1[j] = res;
j = j + 1;
}
}
//下面就是完成raw数据到OpenCV数据结构的转换
Mat iMat(height, width, CV_8UC1, buf1);
imwrite("D:\\images\\CV\\图片\\yuantu1.bmp", iMat);
cin.get();
return 0;
}