Как прочитать растровое изображение на Arduino
Я хочу преобразовать растровое изображение в двоичный массив. Мое растровое изображение представляет собой монохроматическое изображение 1bpp размером 272 * 208 пикселей. Я смущен, когда ширина, которую я получаю от моего изображения, составляет 16 вместо 272, высота исправлена. И когда я пропускаю заголовок растрового изображения, чтобы получить информацию о растровом изображении, я получаю строку бессмысленных чисел в своем текстовом файле.
#include <SPI.h>
#include <SD.h>
#include <TFT.h>
File bmpImage;
File textFile;
void setup()
{
Serial.begin(9600);
while (!Serial) {
;
}
Serial.print("Initializing SD card...");
if (!SD.begin(53)) {
Serial.println("initialization failed!");
return;
}
Serial.println("initialization done.");
int height = 0;
int width = 0;
// Открыть
bmpImage = SD.open("Circle.bmp", FILE_READ);
textFile = SD.open("test.txt", FILE_WRITE);
bmpImage.seek(0x12);// ширина в пикселях = 16
width = bmpImage.read();
bmpImage.seek(0x16);// высота в пикселях = 208
height = bmpImage.read();
Serial.println(width);
Serial.println(height);
int imageSize = height*width;
bmpImage.seek(0x36);//пропустить заголовок растрового изображения
for(int i = 0; i < height; i ++) {
for (int j = 0; j < width; j ++) {
textFile.write(bmpImage.read());
textFile.write(" ");
}
textFile.write("\n");
}
bmpImage.close();
textFile.close();
Serial.println("done write");
}
void loop()
{
// после настройки ничего не происходит
}
@Seawish, 👍5
3 ответа
Это был очень сырой способ чтения файла BMP. Существует множество предположений, которые могут привести к ошибкам.
- Это файл в формате BMP. Подпись заголовка не проверяется.
- Предполагается, что смещение изображения находится сразу после заголовка файла. Это не читается.
- Ширина изображения меньше 256. Считывается только младший байт 32-битной ширины изображения. Поскольку 272>256, младший байт равен 16.
- Высота изображения меньше 256. Считывается только младший байт 32-битной высоты изображения.
Это маленькое растровое изображение, но его чтение займет очень много времени при чтении по одному байту за раз. Существуют методы повышения производительности, но это ответ на другой вопрос.
Более надежный способ чтения заголовка BMP и информации об изображении – определить их как структуру и прочитать.
struct bmp_file_header_t {
uint16_t signature;
uint32_t file_size;
uint16_t reserved[2];
uint32_t image_offset;
};
struct bmp_image_header_t {
uint32_t header_size;
uint32_t image_width;
uint32_t image_height;
uint16_t color_planes;
uint16_t bits_per_pixel;
uint32_t compression_method;
uint32_t image_size;
uint32_t horizontal_resolution;
uint32_t vertical_resolution;
uint32_t colors_in_palette;
uint32_t important_colors;
};
bmpImage = SD.open("Circle.bmp", FILE_READ);
...
// Читаем заголовок файла
bmp_file_header_t fileHeader;
bmpImage.read(&fileHeader, sizeof(fileHeader));
...
// Проверить подпись
...
// Читаем заголовок изображения
bmp_image_header_t imageHeader;
bmpImage.read(&imageHeader, sizeof(imageHeader));
...
// Проверяем размер и формат изображения
...
// Найдите пиксели
bmpImage.seek(fileHeader.image_offset);
Информация о файлах и изображениях хранится с прямым порядком байтов, поэтому они прекрасно работают на AVR.
Прежде всего я предлагаю вам прочитать полную спецификацию заголовка файла bmp (например, в википедии).
Особенно обратите внимание на это
- У вас может быть таблица цветов (палитра): следовательно, начальный адрес растровых данных НЕ МОЖЕТ быть 0x36
- Пиксели не сохраняются по одному на байт, а могут быть упакованы (см. Хранение пикселей)
- Существуют разные типы заголовков
- Ширина и высота — это 4-байтовые целые числа, так что ваша ширина подойдет (272 = 256 + 16)
При этом, если ваше растровое изображение имеет стандартный заголовок BITMAPINFOHEADER
и если ваше растровое изображение имеет стандартное 24-битное кодирование (16 миллионов цвета) вы можете сделать что-то вроде этого:
int32_t readNbytesInt(File *p_file, int position, byte nBytes)
{
if (nBytes > 4)
return 0;
p_file->seek(position);
int32_t weight = 1;
int32_t result = 0;
for (; nBytes; nBytes--)
{
result += weight * p_file->read();
weight <<= 8;
}
return result;
}
void setup()
{
Serial.begin(9600);
while (!Serial);
Serial.print("Initializing SD card...");
if (!SD.begin(53)) {
Serial.println("initialization failed!");
while (1); // <- так следует блокировать выполнение, а не возвраты
}
Serial.println("initialization done.");
// Открыть
File bmpImage = SD.open("Circle.bmp", FILE_READ);
File textFile = SD.open("test.txt", FILE_WRITE);
int32_t dataStartingOffset = readNbytesInt(&bmpImage, 0x0A, 4);
// Изменяем их типы на int32_t (4 байта)
int32_t width = readNbytesInt(&bmpImage, 0x12, 4);
int32_t height = readNbytesInt(&bmpImage, 0x16, 4);
Serial.println(width);
Serial.println(height);
int16_t pixelsize = readNbytesInt(&bmpImage, 0x1C, 2);
if (pixelsize != 24)
{
Serial.println("Image is not 24 bpp");
while (1);
}
bmpImage.seek(dataStartingOffset);//пропустить заголовок растрового изображения
// 24bpp означает, что у вас есть три байта на пиксель, обычно BGR
byte R, G, B;
for(int32_t i = 0; i < height; i ++) {
for (int32_t j = 0; j < width; j ++) {
B = bmpImage.read();
G = bmpImage.read();
R = bmpImage.read();
textFile.print("R");
textFile.print(R);
textFile.print("G");
textFile.print(G);
textFile.print("B");
textFile.print(B);
textFile.print(" ");
}
textFile.print("\n");
}
bmpImage.close();
textFile.close();
Serial.println("done write");
}
Текстовый вывод должен быть файлом с массивом строк типа R10G100B25
, где R — красный компонент пикселя, G — зеленый компонент, а B — синий.
Теперь это совместимо только с форматом 24bpp. Если вы хотите поддерживать больше форматов, вам нужно будет прочитать спецификации и закодировать их.
Дайте мне знать, если это сработает для вас
Или вы можете оставить свой старый код с некоторыми изменениями, если вы просто хотите читать свое изображение, не беспокоясь о слишком большом количестве проверок.
Измените тип imageSize на unsigned long
.
Высота и ширина — это 4-байтовые поля, поэтому одиночный метод read() их не обрезает. Вы должны объявить высоту и ширину как переменные long или int32_t, а затем прочитать 4 байта при каждом смещении, используя битовый сдвиг, чтобы собрать байты в одно число и помнить, что это маленький порядок байтов. Что-то вроде этого:
long width |= bmpImage.read();
width |= (long)(bmpImage.read()) << 8;
width |= (long)(bmpImage.read()) << 16;
width |= (long)(bmpImage.read()) << 24;
Повторите это, чтобы получить высоту, но используйте abs(), чтобы получить абсолютное значение, поскольку оно обычно отрицательное. Вы также можете проверить таблицу цветов; обычно хорошим индикатором является поле ColorDepth (смещение 28); если меньше 16, то вам следует ожидать таблицу цветов. Но если вы это уже знаете, вы можете пропустить это и перейти к начальному смещению, обычно 0x36. Ваш текущий код не учитывает отступы (даже если ваше изображение не имеет отступов), а также не использует фактическую ширину изображения в байтах. Чтобы получить эту ширину, предполагая, что BMP использует RGB888 24 бита на пиксель:
width = ((width * 24) + 31) / 32;
width *= 4;
Теперь вы можете читать каждую строку, хотя обычно она располагается снизу вверх. Однако вы не правильно пишете в файл. Вы должны использовать textfile.print()
, а не write()
; первый запишет десятичное значение каждого байта в ваш файл, а второй запишет точный незакодированный байт в текстовый файл, что приведет к большому количеству тарабарщины, когда вы попытаетесь отобразить содержимое файла, скажем, в Блокноте, поскольку диапазон печатаемых символов (в кодировке ASCII по умолчанию) составляет от 32 до 127, и ваше изображение, вероятно, имеет много байтов за пределами этого диапазона.
- Как разделить входящую строку?
- Как использовать SPI на Arduino?
- Как сбросить или отформатировать Arduino?
- Управление скоростью вентилятора с помощью библиотеки Arduino PID
- Arduino Due vs Mega 2560
- Как получить уникальный идентификатор для всех плат Arduino?
- Почему я получаю avrdude: stk500v2_ReceiveMessage(): timeout error when uploading to Arduino Mega?
- Тайм-аут связи Arduino Mega с ошибкой программатора