Запись растрового изображения размером 1 МБ на SD-карту с объемом оперативной памяти всего 96 КБ

Моя цель - заставить Arduino Due (96 КБ ОЗУ) записать растровое изображение размером +-1 МБ на SD-карту.

У меня есть массив из нескольких координат на ардуино, и я хотел бы сгенерировать растровое изображение, в котором все координаты имеют определенный цвет, а все остальные пиксели / фон имеют другой цвет.

У меня есть код для записи растрового изображения на SD-карту (см. Ниже), но проблема в том, что растровое изображение динамически генерируется и выделяется в оперативной памяти, а затем записывается на SD-карту. Поэтому, как только растровое изображение, которое я хочу записать на SD-карту, становится больше, чем доступная оперативная память, я получаю сообщение об ошибке: "раздел xx.elf ".bss" не входит в область "ram".

Кто-нибудь может посоветовать мне, как поместить части растрового изображения в оперативную память и записать их на SD-карту одну за другой. Или, может быть, есть другой лучший подход?

#include <SPI.h>
#include <SD.h>


const int chipSelect = 10;

char name[] = "9px_0000.bmp";       // соглашение об именах файлов (будет автоматически увеличиваться)
const int w = 800;                   // ширина изображения в пикселях
const int h = 400;                    // " высота
const boolean debugPrint = true;    // печатать детали процесса по последовательному?

const uint32_t imgSize = w*h;
int px[w*h];                        // фактические данные в пикселях (оттенки серого - добавлены программно ниже)

File file;

const int amount_CT_samples = 20;
int Yarr[amount_CT_samples];
int Xarr[amount_CT_samples];

void setup() {


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


  SerialUSB.print("Initializing SD card...");

  if (!SD.begin(chipSelect)) {
    SerialUSB.println("initialization failed!");
    return;
  }
  SerialUSB.println("initialization done.");

  // если имя существует, создайте новое имя
  for (int i=0; i<10000; i++) {
    name[4] = (i/1000)%10 + '0';    // тысячи мест
    name[5] = (i/100)%10 + '0';     // сотни
    name[6] = (i/10)%10 + '0';      // десятки
    name[7] = i%10 + '0';           // единицы
    file = SD.open(name, O_CREAT | O_EXCL | O_WRITE);
    if (file) {
      break;
    }
  }

  // установить размер файла (используется в заголовке bmp)
  int rowSize = 4 * ((3*w + 3)/4);      // сколько байт в строке (используется для создания отступов)
  int fileSize = 54 + h*rowSize;        // заголовки (54 байта) + пиксельные данные

  // создать данные изображения; сильно измененная версия с помощью:
  // http://stackoverflow.com/a/2654860
  unsigned char *img = NULL;            // данные изображения
  if (img) {                            // если в массиве уже есть данные, очистите их
    free(img);
  }
  img = (unsigned char *)malloc(3*imgSize);

  for (int y=0; y<h; y++) {
    for (int x=0; x<w; x++) {
      int colorVal = px[y*w + x];                        // классическая формула для px, указанная в строке
      img[(y*w + x)*3+0] = (unsigned char)(colorVal);    // R
      img[(y*w + x)*3+1] = (unsigned char)(colorVal);    // G
      img[(y*w + x)*3+2] = (unsigned char)(colorVal);    // B
      // заполнение (4-й байт) будет добавлено позже по мере необходимости...
    }
  }


  // создать отступ (на основе количества пикселей в строке
  unsigned char bmpPad[rowSize - 3*w];
  for (int i=0; i<sizeof(bmpPad); i++) {         // заполнить 0s
    bmpPad[i] = 0;
  }

  // создание заголовков файлов (также взято из примера StackOverflow)
  unsigned char bmpFileHeader[14] = {            // заголовок файла (всегда начинается с BM!)
    'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0   };
  unsigned char bmpInfoHeader[40] = {            // информация о файле (размер и т.д.)
    40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0   };

  bmpFileHeader[ 2] = (unsigned char)(fileSize      );
  bmpFileHeader[ 3] = (unsigned char)(fileSize >>  8);
  bmpFileHeader[ 4] = (unsigned char)(fileSize >> 16);
  bmpFileHeader[ 5] = (unsigned char)(fileSize >> 24);

  bmpInfoHeader[ 4] = (unsigned char)(       w      );
  bmpInfoHeader[ 5] = (unsigned char)(       w >>  8);
  bmpInfoHeader[ 6] = (unsigned char)(       w >> 16);
  bmpInfoHeader[ 7] = (unsigned char)(       w >> 24);
  bmpInfoHeader[ 8] = (unsigned char)(       h      );
  bmpInfoHeader[ 9] = (unsigned char)(       h >>  8);
  bmpInfoHeader[10] = (unsigned char)(       h >> 16);
  bmpInfoHeader[11] = (unsigned char)(       h >> 24);

  // запишите файл (спасибо форуму!)
  file.write(bmpFileHeader, sizeof(bmpFileHeader));    // записать заголовок файла

}

void loop() { }

Обновить

С помощью следующего кода моя проблема была решена. Спасибо тем, кто ответил на мой вопрос ниже!

#include <SPI.h>
#include <SD.h>
const int chipSelect = 10;

struct Pixel {
    uint8_t r, g, b;
};

char name[] = "CT_0000.bmp";       // соглашение об именах файлов (будет автоматически увеличиваться)
const int w = 800;                   // ширина изображения в пикселях
const int h = 400;                    // " высота
const boolean debugPrint = true;    // печатать детали процесса по последовательному?

const uint32_t imgSize = w*h;
int px[w*h];                        // фактические данные в пикселях (оттенки серого - добавлены программно ниже)

File file;

const int amount_CT_samples = 50;
int Xarr[amount_CT_samples];
int Yarr[amount_CT_samples];

Pixel getPixel(int x, int y) {
    const Pixel black = {0, 0, 0},
                green = {0, 255, 0};

    for(int i=0; i < amount_CT_samples; i++){
      if(Xarr[i] == x && Yarr[i] == (h - y))return green;
    }
    return black;
}

void writeBitmap(File &file, int w, int h) {
    size_t rowSize = 4 * ((3*w + 3)/4);  // дополняется до кратного 4
    size_t fileSize = 54 + h*rowSize;    // включает заголовок

    // Написать заголовок изображения.
    uint8_t header[54] = {
        // Заголовок файла.
        'B','M',
        (uint8_t)(fileSize >>  0),
        (uint8_t)(fileSize >>  8),
        (uint8_t)(fileSize >> 16),
        (uint8_t)(fileSize >> 24),
        0,0, 0,0, 54,0,0,0,

        // Заголовок информации об изображении.
        40,0,0,0,
        (uint8_t)(w >>  0),
        (uint8_t)(w >>  8),
        (uint8_t)(w >> 16),
        (uint8_t)(w >> 24),
        (uint8_t)(h >>  0),
        (uint8_t)(h >>  8),
        (uint8_t)(h >> 16),
        (uint8_t)(h >> 24),
        1,0, 24,0
    };
    file.write(header, sizeof header);

    // Запись данных изображения.
    uint8_t row[rowSize];
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            Pixel pix = getPixel(x, y);
            row[3*x + 0] = pix.b;
            row[3*x + 1] = pix.g;
            row[3*x + 2] = pix.r;
        }
        file.write(row, sizeof row);
    }

    file.close();  
}

void setup() {
    Serial.begin(9600);
    Serial.print("Initializing SD card...");

    //создание фиктивных точек X и Y
    for(int i = 0; i < amount_CT_samples; i++){
        Xarr[i] = i;
        Yarr[i] = i;
    }

    if (!SD.begin(chipSelect)) {
      Serial.println("initialization failed!");
      return;
    }
    Serial.println("initialization done.");

  // если имя существует, создайте новое имя
  for (int i=0; i<10000; i++) {
    name[3] = (i/1000)%10 + '0';    // тысячи мест
    name[4] = (i/100)%10 + '0';     // сотни
    name[5] = (i/10)%10 + '0';      // десятки
    name[6] = i%10 + '0';           // единицы
    file = SD.open(name, O_CREAT | O_EXCL | O_WRITE);
    if (file) {
      break;
    }
  }

  Serial.println("start writing bitmap.");
  writeBitmap(file, w, h);
  Serial.println("done writing bitmap.");
  
}



void loop() { }

, 👍3

Обсуждение

Было бы выполнимо сначала создать и записать заголовки, а затем сгенерировать изображение строка за строкой и сохранить каждую строку на SD-карту перед созданием следующего? Должно быть меньше 3 КБ / строка. Циклы for предполагают, что это может быть выполнимо. Однако я не знаю, куда девается набивка. Кроме того, возможно, вы могли бы генерировать данные непосредственно во флэш-памяти, а не в оперативной памяти, но я не знаю, что можно сделать там в Due., @ocrdu

@ocrdu, спасибо за ваш комментарий. Действительно, построчная запись или прямая запись во flash решили бы мою проблему. Но я не знаю, как я мог бы практически реализовать это., @Sven Onderbeke

Ну, грубо говоря, вы бы использовали значение int px[] размером с одну строку, заполнили его во внутреннем цикле for , поэтому для одного значения h затем запишите его на SD-карту в конце внешнего цикла for, а затем сделайте то же самое для следующей строки, т. Е. следующая буква h во внешнем цикле. Грофвег., @ocrdu

Спасибо, это действительно хорошая идея!, @Sven Onderbeke


2 ответа


Лучший ответ:

3

Вы можете записать файл по частям. Как было предложено ocrdu в комментарии, писать построчно - хорошая стратегия. Чтобы сохранить управляемость сложностью кода, я бы поместил логику определения цвета пикселя в его собственную функцию и использовал другую функцию для управления записью растрового изображения. Например:

struct Pixel {
    uint8_t r, g, b;
};

// Фиктивный пример. Замените на свою собственную логику.
Pixel getPixel(int x, int y) {
    const Pixel black = {0, 0, 0},
                green = {0, 255, 0};
    if (((x - y) & 0x0f) == 0) return green;
    return black;
}

void writeBitmap(File &file, int w, int h) {
    size_t rowSize = 4 * ((3*w + 3)/4);  // дополняется до кратного 4
    size_t fileSize = 54 + h*rowSize;    // включает заголовок

    // Написать заголовок изображения.
    uint8_t header[54] = {
        // Заголовок файла.
        'B','M',
        (uint8_t)(fileSize >>  0),
        (uint8_t)(fileSize >>  8),
        (uint8_t)(fileSize >> 16),
        (uint8_t)(fileSize >> 24),
        0,0, 0,0, 54,0,0,0,

        // Заголовок информации об изображении.
        40,0,0,0,
        (uint8_t)(w >>  0),
        (uint8_t)(w >>  8),
        (uint8_t)(w >> 16),
        (uint8_t)(w >> 24),
        (uint8_t)(h >>  0),
        (uint8_t)(h >>  8),
        (uint8_t)(h >> 16),
        (uint8_t)(h >> 24),
        1,0, 24,0
    };
    file.write(header, sizeof header);

    // Запись данных изображения.
    uint8_t row[rowSize] = {0};
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            Pixel pix = getPixel(x, y);
            row[3*x + 0] = pix.b;
            row[3*x + 1] = pix.g;
            row[3*x + 2] = pix.r;
        }
        file.write(row, sizeof row);
    }
}

Затем, после открытия файла, вам просто нужно:

writeBitmap(file, 800, 400);

Обратите внимание, что приведенная выше функция GetPixel() - это всего лишь фиктивный пример, который рисует зеленые диагональные линии поперек изображения. Идеи о том, как реализовать свою собственную функцию генерации пикселей, см. В ответе timemage.

,

@SvenOnderbeke, это хорошая отправная точка для тебя. Просто чтобы вы не были застигнуты врасплох, я хотел упомянуть, что формат BMP хранит свои изображения таким образом, который большинство из нас назвали бы up-side-down. Строки, появляющиеся первыми в файле, представляют собой содержимое, которое отображается в нижней части изображения при отображении., @timemage

Отлично, большое спасибо! Этот ответ решил мою проблему, я также отредактировал свой вопрос, чтобы теперь он содержал решение., @Sven Onderbeke


1

Обычно я бы написал это по-другому, но я придерживался относительно простой формулировки, просто чтобы проиллюстрировать основную идею:

struct point {
  int x;
  int y;
};


const point points_of_interest[] = {
  { 7, 11},
  {23, 17},
  {13, 19},
  { 3,  5},  
  {17,  3},
};


void setup() {
  Serial.begin(9600);
}


void loop() {
  delay(4000);
  Serial.println("\n\n\n");
  
  for (int i = 0; i < 24; ++i) {
    for (int j = 0; j < 24; ++j) {
      bool coordinate_of_interest = false;

      for (const auto poi: points_of_interest) {       
        coordinate_of_interest = j == poi.x && i == poi.y;         
        if (coordinate_of_interest) {
          break;
        }
      }                      

      if (coordinate_of_interest) {
        Serial.print("XX");
      } else {
        Serial.print("..");
      }
    }
    
    Serial.println();
  }
}

Он печатает сетку размером 24x24 или 576 пикселей. Но сохраняются только пять координат. Было бы важно, если бы это была сетка размером 2400x2400, но она не будет хорошо печататься на развернутом последовательном мониторе для примера.

С вашим растровым изображением вы могли бы стать более изощренными. Например. проверка того, что расстояние (или, более эффективно, расстояние в квадрате; нет операции sqrt ()) от рассматриваемого пикселя (i, j) до любой точки в списке меньше или равно некоторой величине, и раскраска на основе этого. Это приведет к появлению сплошных кругов, центрированных по вашим точкам на вашем растровом изображении. Или цветовой градиент вокруг точки, если вы основываете цвет на расстоянии.

Если у вас много точек, вам нужно будет сделать что-то вроде сортировки их по вертикальным координатам и продвинуть индекс, который представляет точки на вашей горизонтальной линии развертки или ниже нее, и т.д. То есть, если у вас много точек, вы не хотите, чтобы вам приходилось измерять расстояние до всех из них для каждого рассматриваемого пикселя.

,

Спасибо вам за этот совет! Решения, приведенные здесь, действительно помогли мне сгенерировать нужный мне код. (Я отредактировал свой вопрос, так что теперь он содержит решение)., @Sven Onderbeke

@SvenOnderbeke, Мило. Если это действительно было полезно, вы можете отметить это. Но, насколько я понимаю, вполне уместно, что вы должны отметить ответ Эдгара как "принятый", потому что вы описали его как решение вашей проблемы., @timemage