欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从STM32开发板上获取图片数据并保存为BMP文件

程序员文章站 2024-01-24 17:12:40
...

    上周解决了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进制的字符串

从STM32开发板上获取图片数据并保存为BMP文件

    下面这一张是将HEX 字符转换为ASCII 码后的文件

从STM32开发板上获取图片数据并保存为BMP文件

    下面的是编码为BMP 后的图像。BMP 文件无法上传,在这里就放一张截图了。

从STM32开发板上获取图片数据并保存为BMP文件

相关标签: BMP 嵌入式