Проблемы с чтением растровых изображений с SD-карты

У меня проблемы с чтением файла растрового изображения с помощью библиотеки SD. Я написал небольшой заголовок, который читает несжатые 24-битные растровые файлы. Он отлично работает, когда я пробую его в Windows 10 MingW с fread() вместо SD.read(). На моем UNO он считывает только правильный адрес смещения, остальные части - мусор.

Для чистого красного растрового изображения размером 1x1 пиксель я получаю следующий ожидаемый результат в Windows с MingW:

Fname: ..\rot.bmp
Offset: 00000036
W * H: 1 * 1
Bit# 24
Cmp: 0
ArraySize: 1
---------------------------
RGB: ff0000 

Выходы Arduino:

Fname: rot.bmp
Offset: 00000036
W * H: 0 * 2304
Bit# 58
Cmp: 0
ArraySize: 8
---------------------------
Error while reading BMP file

bmp.hpp

#ifndef BMP_HEADER
#define BMP_HEADER

#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)
#include <SD.h>
#include<stdint.h>
#define BYTE uint8_t
#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t
#else
#include<stdlib.h>
#include<windows.h>
#endif // определено(ARDUINO_AVR_UNO) || определено (ARDUINO_AVR_MEGA)
#pragma pack(2)
typedef struct 
{
  BYTE B;
  BYTE G;
  BYTE R;
} pixel;

typedef struct
{
  WORD bfType;
  DWORD bfSize;
  DWORD bfReserved;
  DWORD bfOffset; // смещение в байтовый массив
} bfheader;

typedef struct 
{
  DWORD biSize; // размер этого заголовка
  LONG biWidth; //ширина изображения
  LONG biHeight;//высота изображения
  WORD biPlanes;
  WORD biBitCount; //глубина цвета в битах на пиксель
  DWORD biCompression; //0 для отсутствия (режим RGB)
  DWORD biSizeImage; //размер изображения в байтах; может быть 0, если biCompression - RGB
  LONG biXPelsPerMeter;
  LONG biYPelsPerMeter;
  DWORD biClrUsed; //наполнение палитры
  DWORD biClrImportant; //больше палитры
} biheader;

//возвращает массив растровых изображений BGR
// предполагается, что заголовок и бизаголовок выделяются при выполнении
//возвращает ноль в случае неудачи
inline pixel* readBMP(const char* filename, bfheader* header, biheader* infoheader)
{
    #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)  
    SDLib::File f = SD.open(filename);
    if(!f) return NULL;
    f.read((uint8_t *)header, sizeof(bfheader)); // прочитать 14-байтовый заголовок файла
    f.read((uint8_t *)infoheader, sizeof(biheader)); //читаем 40-байтовый информационный заголовок
    #else
    FILE* f = fopen(filename, "rb");
    if(f == NULL) return NULL;
    fread(header, sizeof(bfheader), 1, f);
    fread(infoheader, sizeof(biheader), 1, f);
    #endif // определено (ARDUINO_AVR_UNO) || определено (ARDUINO_AVR_MEGA)
  if(infoheader->biCompression != 0 || infoheader->biBitCount != 24) //если файл не в кодировке RGB или не 24-битный
  { 
    #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)   
    f.close();
    #else
    fclose(f);
    #endif // определено(ARDUINO_AVR_UNO) || определено (ARDUINO_AVR_MEGA)
    return NULL;
  }
  int size = (int) infoheader->biSizeImage/sizeof(pixel); // получаем размер массива пикселей в байтах
  pixel* data = (pixel*) malloc(sizeof(pixel)*size); // выделить
  #if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)
    f.seek(header->bfOffset); //установить файловый курсор на начальный адрес массива RGB
    f.read((uint8_t *)data, sizeof(uint8_t)*size); // прочитать остальные данные сразу
    f.close();
  #else
    fseek(f, header->bfOffset, SEEK_SET);
    fread(data, sizeof(pixel), size, f);
    fclose(f);
  #endif //__рука__
  return data;
} 
#endif //BMP_HEADER

readbmp_test.ino

#include<arduino.h>
#include <SPI.h>
#include "bmp.hpp"

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // ждем подключения последовательного порта. Требуется только для родного порта USB
  }
  Serial.print("Initializing SD card...");

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");
}

void loop() 
{
  char debug[160];
  char rgbdata[8];
  const char* const files[] = {"rot.bmp", "gruen.bmp", "blau.bmp"};
  int files_len = 3;
  bfheader header;
  biheader infoheader;  
  pixel* image;
  for(int i=0; i<files_len; i++)
  {
    image = readBMP(files[i], &header, &infoheader);
    Serial.println("----------------------------");
    sprintf(debug,  "Fname: %s\n"
                "Offset: %08lx\n"
                "W * H: %ld * %ld\n"
                "Bit# %u\n"
                "Cmp: %lu\n"
                "ArraySize: %lu\n"
                "---------------------------\n", 
                  files[i], 
                  header.bfOffset, 
                  infoheader.biWidth, infoheader.biHeight, 
                  infoheader.biBitCount, 
                  infoheader.biCompression, 
                  infoheader.biSizeImage/sizeof(pixel));
    Serial.print(debug);
    if(image != NULL)
    {

      for(unsigned j=0; j<infoheader.biSizeImage/sizeof(pixel); j++)
      {
        sprintf(rgbdata, "%02x %02x %02x", image[j].R, image[j].G, image[j].B);
        Serial.println(rgbdata);
      }
    }else{
        sprintf(debug, "Error while reading BMP file\n");
        Serial.print(debug);
    }
    free(image);
  }
}

main.cpp //Тест MingW для Windows

#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA)  // компилятор arduino хочет запустить этот файл main?
#else
#include<stdio.h>
#include "bmp.hpp"

int main(const int argc, const char** argv)
{
    if(argc < 2) return -1;

    bfheader header;
    biheader infoheader;
    for(int i=1; i<argc; i++)
    {      
        pixel* image = readBMP(argv[i], &header, &infoheader);
        if(image != NULL)
        {
            printf("Fname: %s\n"
                    "Offset: %08x\n"
                    "W * H: %d * %d\n"
                    "Bit# %d\n"
                    "Cmp: %d\n"
                    "ArraySize: %d\n"
                    "---------------------------\n", 
                    argv[i], 
                    header.bfOffset, 
                    infoheader.biWidth, infoheader.biHeight, 
                    infoheader.biBitCount, 
                    infoheader.biCompression, 
                    infoheader.biSizeImage/sizeof(pixel));    
            for(int j=0; j<infoheader.biSizeImage/sizeof(pixel); j++)
            {
                printf("RGB: %02x%02x%02x\n", image[j].R, image[j].G, image[j].B);
            }
        }else{
                printf("Error while reading BMP file\n");
                return 1;
        }
        free(image);
    }
    return 0;
}
#endif

Я действительно застрял на этом, я уже несколько раз переписывал его. Буду очень признателен за помощь.

, 👍0

Обсуждение

распечатать количество байтов, прочитанных с помощью file.read (возвращаемое значение). попробуйте использовать readBytes вместо read, если количество прочитанных байтов не соответствует ожидаемому размеру, @Juraj

@Juraj Я пробовал с readBytes и читал. Тот же результат: первое чтение возвращает ожидаемые 14 байтов. Второй возвращает только 2 вместо ожидаемых 40., @MatC

распечатайте структуру как байты с помощью println(b, HEX), чтобы увидеть необработанные байты, чтобы проверить их положение в полях структуры, @Juraj

Как мне это сделать? Serial.println(*infoheader, HEX); приводит к 'HardwareSerial::println(biheader&, int)', @MatC

Я пробовал с for(int i=0; i<sizeof(biheader); i++). Вывод, кажется, соответствует шестнадцатеричному дампу файла. Я поместил шестнадцатеричный дамп растрового изображения и вывод на [Pastebin](https://pastebin.com/YkhkXP8z), @MatC

это может быть проблема с printf. у вас есть %d, и он считывает только два байта на Arduino. распечатать значения без sprintf как Serial.println(число), @Juraj

Подсказка: начните с исправления чтения заголовка. Неверный размер. Используйте размер структуры, а не указатель. Используйте один и тот же sizeof для обоих вариантов., @Mikael Patel

@MikaelPatel спасибо за ваш вклад. Я исправил эту часть, но у меня все еще есть проблемы., @MatC

@Juraj Твой совет со sprintf был довольно хорош. Я включил все предупреждения во время компиляции и увидел, что типы данных не соответствуют строкам формата. Я исправил это, и теперь информация в моем заголовке отображается правильно. К сожалению, все еще остается проблема, поскольку чистое красное растровое изображение отображает значения ложных пикселей: sprintf(rgbdata, "%02x %02x %02x", image[j].R, image[j].G, image[j]. Б); Serial.println(rgbdata); отображает: c5 00 00 несмотря на то, что растровое изображение вообще не содержит C5., @MatC


1 ответ


1

f.read((uint8_t *)информационный заголовок, sizeof(информационный заголовок));

должно быть

f.read((uint8_t *)infoheader, sizeof(biheader));

размер переменной (указателя) всего 2. вы хотите прочитать 40 байт структуры biheader

и

f.read((uint8_t *)data, sizeof(uint8_t)*size)

должно быть

f.read((uint8_t *)данные, sizeof(пиксель)*размер)


Следующая проблема — %d и %x в sprintf. %d и %x считывают два байта на Arduino, а не 4 и не 1. Выведите значения без sprintf как Serial.println(number)

,

Ого, спасибо, что указали на это. Он читает ожидаемое количество байтов. Однако его вывод по-прежнему неверен. Fname: rot.bmp Смещение: 00000036 Ш * В: 0 * 1 Бит № 0 Команда: 1 Размер массива: 0, @MatC

Привет, @juraj, я ответил на твой комментарий к основному сообщению., @MatC

Привет @Juraj, я изменил строку формата, поэтому ожидается правильный тип данных, lu для uint32_t и т. Д. Для заголовка, который теперь работает нормально. Pixeldata по-прежнему неверны. Я последовал вашему совету и попытался отобразить Pixeldata, используя необработанный Serial.print. Проблема все еще сохраняется. Fname: rot.bmp Смещение: 00000036 Ш * В: 1 * 1 Бит № 24 Команда: 0 Размер массива: 1 --------------------------- 58 0 0 мой текущий вывод, @MatC

@MatC, помогло?, @Juraj