从STM32开发板上获取图片数据并保存为BMP文件
上周解决了WIFI 传输过程中连接中断,电脑端接收不到数据的问题。又花了一周的时间才正确的把从开发板上获取的图像数据转换、编码保存为BMP 图像。
本来是打算在开发板上,以字符串的形式每次发送512个字节,电脑端再解析出来。但是图片解析一直有问题。最后直接用串口在调试助手上把一帧图像的数据以16进制的形式打印出来,发现数据根本对不上。最后改成以打印十六进制的形式将数据发送到电脑,开发板发送部分的代码如下:
void wifi_send_data(unsigned short *data,int len)
{
unsigned short *p = data;
u8 count=0; //用于计数单次发送的个数
p = (unsigned short*)malloc(sizeof(unsigned short)*128);
if(atk_8266_consta_check() == '+')
{
printf("连接正常\r\n");
}
else
{
printf("连接中断\r\n");
return;
}
atk_8266_at_response(1);
delay_ms(10);
free(p);
p=data;
//首先发送起始信息
atk_8266_quit_trans();
atk_8266_send_cmd("AT+CIPSEND","OK",20);
u3_printf("start of msg");
atk_8266_at_response(1);
delay_ms(20);
while(len > 0)
{
// if(len >= 32)
// {
// len -= 32;
// u3_printf("%32.32s",p);
// p+=16; //short 类型的指针对应两个char 类型指针移动的长度
// }
// else
// {
// u3_printf("%s",p);
// len=0;
// }
//修改为发送十六进制数的字符串,每次发送64个short 类型的数
count=0;
if(len >= 64)
{
len -= 64;
while(count < 64)
{
u3_printf("%x",*p);
++p;
++count;
}
}
else
{
while(count < len)
{
u3_printf("%x",*p);
++count;
}
len =0;
}
get_feedback();
delay_ms(20);
}
//发送标识一帧图像结束的消息
u3_printf("end of msg");
atk_8266_at_response(1);
delay_ms(1000);
printf("发送结束==========================================\r\n");
// char *p = data;
// char *send_content = (char*)malloc(sizeof(char)*2);
// while(len > 0)
// {
// atk_8266_quit_trans();
// atk_8266_send_cmd("AT+CIPSEND","OK",20);
// sprintf(send_content,"%02x%02x",*p,*(p+1));
// u3_printf(send_content); //发送数据
// p +=2;
// len -= 2;
// atk_8266_at_response(1);
// }
// free(send_content);
}
下面就是对接受到的图像数据进行解析了。我用的摄像头是OV7670 ,采用的是RGB565 ,即每个像素点最高为为RED ,5个bit ,中间为GREEN,6个bit ,最低为BLUE ,5个bit 。转换为在电脑上能显示的RGB 只需要分别将RGB独立为一个字节,低位为0。比如RGB565 为0XFF, 先用逻辑运算获取到每个分量,R:0X1F,G:0X3F,B:0X1F,之后RGB 分左移3、2、3位。得到:R:0XF8,G:0XF8,B:0XF8,最后得到的RGB占24位,各个字节分别位RGB。
下面是我在VS 环境下实现的代码。由于结构体的字节对齐的原因,结构体的总长度并不等于各个字段的长度之和,所以需要使用#pragma 指定对齐方式(在Linux 中对应的是__attribute__ (packed))。下面的分别是BMP 文件的文件头与信息头。
#pragma pack(push,1)
typedef struct mtagMYBITMAPFILEHEADER {
unsigned short bfType;
unsigned int bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned int bfOffBits;
}MYBITMAPFILEHEADER;
typedef struct mtagMYBITMAPINFOHEADER {
unsigned int biSize;
unsigned int biWdith;
unsigned int biHeight;
unsigned short biPlanes;
unsigned short biBitCount;
unsigned int biCompression;
unsigned int biSizeImage;
unsigned int biXPelsPerMeter;
unsigned int biYPelsPerMeter;
unsigned int biClrUsed;
unsigned int biClrImportant;
}MYBITMAPINFOHEADER;
typedef struct mtagPaletteRGB {
unsigned char b;
unsigned char g;
unsigned char r;
}paletteRGB;
#pragma pack(pop)
int g_len = sizeof(MYBITMAPFILEHEADER) + sizeof(MYBITMAPINFOHEADER);
void initBMPHeader(MYBITMAPFILEHEADER *pBmpFileHeader) {
pBmpFileHeader->bfType = 0X4D42;
pBmpFileHeader->bfSize = g_len + 160 * 120 * 3;
pBmpFileHeader->bfReserved1 = 0X00;
pBmpFileHeader->bfReserved2 = 0X00;
pBmpFileHeader->bfOffBits = g_len;
}
void initBMPInfoHeader(MYBITMAPINFOHEADER *pBmpInfoHeader) {
pBmpInfoHeader->biSize = sizeof(MYBITMAPINFOHEADER);
pBmpInfoHeader->biWdith = 160;
pBmpInfoHeader->biHeight = 120;
pBmpInfoHeader->biPlanes = 1;
pBmpInfoHeader->biBitCount = 24;
pBmpInfoHeader->biCompression = 0;
pBmpInfoHeader->biSizeImage = (160 * 120 * 3);
pBmpInfoHeader->biXPelsPerMeter = pBmpInfoHeader->biYPelsPerMeter = 0;
pBmpInfoHeader->biClrUsed = 0;
pBmpInfoHeader->biClrImportant = 0;
}
void convertBMP(FILE *fs, FILE *fd, unsigned short *pSrcShortBuffer, unsigned char *pDestIntBuffer);
//将RGB565 编码保存为BMP 文件
void rgb565_2_bmp(char *src, char *dest) {
MYBITMAPFILEHEADER bmpFileHeader;
initBMPHeader(&bmpFileHeader);
MYBITMAPINFOHEADER bmpInfoHeader;
initBMPInfoHeader(&bmpInfoHeader);
paletteRGB bmpPalette;
memset(&bmpPalette, 0, sizeof(paletteRGB));
printf("src file path is %s\n", src);
printf("dest file path is %s\n", dest);
FILE *fd = fopen(dest, "w+");
FILE *fs = fopen(src, "r+");
fwrite(&bmpFileHeader, sizeof(bmpFileHeader), 1, fd);
fwrite(&bmpInfoHeader, sizeof(bmpInfoHeader), 1, fd);
unsigned char *pDestIntBuffer = new unsigned char[3 * 160];
memset(pDestIntBuffer, 0, sizeof(unsigned char) * 160 * 3);
unsigned short *pSrcShortBuffer = new unsigned short[160];
memset(pSrcShortBuffer, 0, sizeof(unsigned short) * 160);
convertBMP(fs, fd, pSrcShortBuffer, pDestIntBuffer);
fclose(fs);
fclose(fd);
delete[] pDestIntBuffer;
delete[] pSrcShortBuffer;
}
unsigned short changeEndian(unsigned short num) {
unsigned short temp = (num & 0XFF);
temp <<= 8;
temp |= ((num & 0XFF00) >> 8);
return temp;
}
void convertBMP(FILE *fs, FILE *fd, unsigned short *pSrcShortBuffer, unsigned char *pDestIntBuffer) {
int height = 1;
int srcLineLength = sizeof(unsigned short) * 160;
int dstLineLength = sizeof(unsigned char) * 3 * 160;
//long fileEnd = 0;
//fseek(fs, 0, SEEK_END);
//fileEnd = ftell(fs);
//printf("file end is :%ld\n", fileEnd);
//fseek(fs, (height - 1)*srcLineLength, SEEK_SET);
//long lastline = ftell(fs);
//printf("last line is :%ld\n", lastline);
while (height <= 120) {
fseek(fs, (height - 1)*srcLineLength, SEEK_SET);
fread((void*)pSrcShortBuffer, 1, srcLineLength, fs);
for (int x = 0; x < 160; ++x) {
unsigned short srcPixel = changeEndian(*(pSrcShortBuffer + x));
unsigned char srcR = (unsigned)(srcPixel & 0XF800) >> 11;
unsigned char srcG = (unsigned)(srcPixel & 0X07E0) >> 5;
unsigned char srcB = (unsigned)(srcPixel & 0X001F);
pDestIntBuffer[x * 3 + 0] = srcB;
pDestIntBuffer[x * 3 + 1] = srcG;
pDestIntBuffer[x * 3 + 2] = srcR;
pDestIntBuffer[x * 3 + 0] <<= 3;
pDestIntBuffer[x * 3 + 1] <<= 2;
pDestIntBuffer[x * 3 + 2] <<= 3;
}
fwrite((void *)pDestIntBuffer, sizeof(unsigned char), dstLineLength, fd);
++height;
}
}
//实现的JAVA NativeMethod
JNIEXPORT void JNICALL Java_daemon_NativeMethod_rgb_1to_1bmp
(JNIEnv *env, jobject obj, jstring src, jstring dest) {
char *src_file = NULL;
src_file = new char[100];
src_file = (char*)(env->GetStringUTFChars(src, 0));
char *dest_file = NULL;
dest_file = new char[100];
dest_file = (char*)(env->GetStringUTFChars(dest, 0));
printf("start to convert\n");
rgb565_2_bmp(src_file, dest_file);
printf("convert finished\n");
env->ReleaseStringUTFChars(src, 0);
env->ReleaseStringUTFChars(dest, 0);
}
在上面的转换中,有一个地方是需要注意的。就是大端小端的转换。在成功编码之前,我用代码解析一帧图像很多次没有成功,突然有一次能够从得到的图片中看到一些轮廓了,但是整体的颜色偏蓝,我以为是WIFI 传输的问题。最后想到网络传输中是大端模式,而Intel 处理器是小端模式,在将代码修改之后,图片很清晰......
代码最后是实现的JNI ,这样在JAVA 端就调用C++ 的方法处理图片了,毕竟使用C++ 处理位运算比较方便,还有一个原因就是JAVA 虚拟机使用的是大端模式......。实现的步骤可以参考这篇博客IntelliJ IDEA平台下JNI编程(一)—HelloWorld篇
在上面的这些步骤之前还需要对接收到的数据进行转码。因为在开发板上是使用printf 以十六进制输出的。所以电脑端接收到的数据就是16进制的字符串而不是实际的16进制(82D0 字符串 ≠ 0X82D0)。因此需要将两个字节的字符串转换为一个字节的ASCII码。比如在文件中有82这两个字符,而这两个字符在开发板上通过%X 将16进制的0X82 显示为“82”。所以需要分别对‘8’这个字符转换为ASCII码、对“2”这个字符转换为ASCII 码。“8” = 0X38 -> 0X08 、“2” = 0X32 -> 0X02。 之后将两个字符转换的ASCII 码进行合并,原来的第一个字节放在高4位,原来的第二个字节转换后的结果放在低4位。即得到的结果是0X82。对于字母的情况,如果是小写就减去0X57 ;如果是大写,就减去0X37。
下面是具体的实现代码:
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
////将HEX 转换为ASCII
//由于fread 每次读取HEX 文件中一个字符,转换HEX 到ASCII 需要将可见的两个HEX 字符转换成ASCII
//首先将两个HEX 对应的十六进制合并
//数字例如 HEX 文件中82 读取到的是0X3832 ,则应该0X38 - 0X30 ,左移4位 | 0X32 -0X30
//字母(HEX 文件中全部是小写字母) ,文件中是da, 读取到的是0X6461 ,则应该 0X64 -0X57 ,左移4位,| 0X61 -0X57
unsigned char hexToChar(unsigned short ch) {
unsigned char result;
unsigned char byte_high = (ch >> 8) & 0XFF;
unsigned char byte_low = (ch & 0XFF);
if ((byte_high >= 0X30) && (byte_high <= 0X39)) {
byte_high -= 0X30;
}
else {
byte_high -= 0X57;
}
if ((byte_low >= 0X30) && (byte_low <= 0X39)) {
byte_low -= 0X30;
}
else {
byte_low -= 0X57;
}
result = byte_high;
result <<= 4;
result |= byte_low;
return result;
}
//将一组HEX 转换为ASCII码
unsigned char* hexsToChars(unsigned char *buf, int len) {
unsigned char *result = (unsigned char*)malloc(sizeof(unsigned char)*len / 2); //HEX 字符两个字节转换为一个ASCII 字符
unsigned short tmp = 0;
for (int i = 0; i < (len / 2); ++i) {
tmp = *(buf + 2 * i); //例如文件中是80, 则读取到的应该是十六进制3830,但是应该转换为0X80 对应的ASCII 码
tmp <<= 8;
tmp |= *(buf + 2 * i + 1);
result[i] = hexToChar(tmp);
}
return result;
}
//将HEX 文件转换为ASCII 编码的文件
//dirPath : 文件所在的文件夹路径
//fileName : 文件名称 不包含路径
void convertHexFile2Ascii(char *dirPath, char *fileName) {
char *srcFile = (char *)malloc(sizeof(char) * 512);
strcpy(srcFile, dirPath);
strcat(srcFile, fileName);
char *destFile = (char *)malloc(sizeof(char) * 512); //用于临时保存转换结果的文件
strcpy(destFile, dirPath);
strcat(destFile, "temp");
FILE *fs = fopen(srcFile, "r");
FILE *fd = fopen(destFile, "w+"); //文件不存在,创建
unsigned char *buf = (unsigned char*)malloc(sizeof(unsigned char) * 1024);
int readlen = 0; //记录单次实际读取的字节数
while ((readlen = fread(buf, sizeof(unsigned char), 1024, fs)) > 0) {
buf = hexsToChars(buf, readlen); //获取转换得到的ASCII 字符
fwrite(buf, sizeof(unsigned char), readlen/2, fd); //将转换的结果写入到一个临时的文件中, HEX字符转换ASCII ,长度减半
free(buf);
buf = (unsigned char*)malloc(sizeof(unsigned char) * 1024);
}
fclose(fs);
fclose(fd);
//将原来的文件删除,并将临时文件重命名为原文件名
remove(srcFile);
rename(destFile, strcat(strcpy((char*)malloc(sizeof(char) * 512), dirPath), fileName));
free(buf);
free(srcFile);
}
int main()
{
char *dirPath = (char*)"D:\\Program Files (x86)\\IntellijIdeaWorkspace\\embed_server\\images\\";
char *srcFile = (char*)"raw1";
convertHexFile2Ascii(dirPath, srcFile);
return 0;
}
上面的代码中,创建一个临时的文件,将转换后的结果写入到该临时文件中,在转换完毕后,将原文件删除,并将临时文件命名为原来的文件名。
下面是几张截图:
下面这一张图片是从开发板接收到的原数据,都是16进制的字符串
下面这一张是将HEX 字符转换为ASCII 码后的文件
下面的是编码为BMP 后的图像。BMP 文件无法上传,在这里就放一张截图了。
上一篇: JAVA实现对BMP图片的读取