Как записывать только регулярно расположенные элементы из буфера символов на диск в C++

Как я могу записать только каждый третий элемент в буфере символов для быстрого файла в C++?

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

Команда ofstream:: write в C++, похоже, записывает только непрерывные блоки двоичных данных, поэтому мой текущий код записывает все три канала и работает слишком медленно:

char * data = getDataFromCamera();
int dataSize = imageWidth * imageHeight * imageChannels;
std::ofstream output;
output.open( fileName, std::ios::out | std::ios::binary );
output.write( data, dataSize );

Я хотел бы иметь возможность заменить последнюю строку с вызовом, как:

int skipSize = imageChannels;
output.write( data, dataSize, skipSize );

где skipSize приведет к тому, что write поместит только каждую треть в выходной файл. Тем не менее, я не смог найти ни одной функции, которая делает это.

Я хотел бы услышать любые идеи для быстрой записи одного канала на диск. Благодарю.

6 ответов

Решение

Допустим, ваш буфер 24-битный RGB, и вы используете 32-битный процессор (поэтому операции с 32-битными объектами наиболее эффективны).

Для большей скорости давайте работать с 12-байтовым блоком за раз. В двенадцати байтах у нас будет 4 пикселя, вот так:

AAABBBCCCDDD

Что составляет 3 32-битных значения:

AAAB
BBCC
CDDD

Мы хотим превратить это в ABCD (одно 32-битное значение).

Мы можем создать ABCD, применяя маску к каждому входу и ORing.

ABCD = A000 | 0BC0 | 000D

В C++, с процессором с прямым порядком байтов, я думаю, что это будет:

unsigned int turn12grayBytesInto4ColorBytes( unsigned int buf[3] )
{
   return (buf[0]&0x000000FF) // mask seems reversed because of little-endianness
        | (buf[1]&0x00FFFF00)
        | (buf[2]&0xFF000000);
}

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

Вам, вероятно, придется скопировать каждый третий элемент в буфер, а затем записать этот буфер на диск.

Вы можете использовать фасет codecvt в локальной системе, чтобы отфильтровать часть вывода.
После создания вы можете наполнить любой поток подходящим локальным потоком, и он будет видеть только каждый третий символ на входе.

#include <locale>
#include <fstream>
#include <iostream>

class Filter: public std::codecvt<char,char,mbstate_t>
{
    public:
   typedef std::codecvt<char,char,mbstate_t> MyType;
   typedef MyType::state_type          state_type;
   typedef MyType::result              result;

    // This indicates that we are converting the input.
    // Thus forcing a call to do_out()
    virtual bool do_always_noconv() const throw()   {return false;}

    // Reads   from -> from_end
    // Writes  to   -> to_end
    virtual result do_out(state_type &state,
             const char *from, const char *from_end, const char* &from_next,
             char       *to,   char       *to_limit, char*       &to_next) const
   {
       // Notice the increment of from
       for(;(from < from_end) && (to < to_limit);from += 3,to += 1)
       {
            (*to) = (*from);
       }
       from_next   = from;
       to_next     = to;

       return((to > to_limit)?partial:ok);
   }
};

Если у вас есть фасет, все, что вам нужно, это знать, как его использовать:

int main(int argc,char* argv[])
{
   // construct a custom filter locale and add it to a local.
   const std::locale filterLocale(std::cout.getloc(), new Filter());

   // Create a stream and imbue it with the locale
   std::ofstream   saveFile;
   saveFile.imbue(filterLocale);


   // Now the stream is imbued we can open it.
   // NB If you open the file stream first. 
   // Any attempt to imbue it with a local will silently fail.
   saveFile.open("Test");
   saveFile << "123123123123123123123123123123123123123123123123123123";

   std::vector<char>   data[1000];
   saveFile.write( &data[0], data.length() /* The filter implements the skipSize */ );
                                           // With a tinay amount of extra work
                                           // You can make filter take a filter size
                                           // parameter.

   return(0);
}

В стандартной библиотеке afaik такой функциональности нет. Решение Джерри Коффина будет работать лучше всего. Я написал простой фрагмент, который должен помочь:

const char * data = getDataFromCamera();
const int channelNum = 0;
const int channelSize = imageWidth * imageHeight;
const int dataSize    = channelSize * imageChannels;
char * singleChannelData = new char[channelSize];
for(int i=0; i<channelSize ++i)
    singleChannelData[i] = data[i*imageChannels];
try {
    std::ofstream output;
    output.open( fileName, std::ios::out | std::ios::binary );
    output.write( singleChannelData, channelSize );
}
catch(const std::ios_base::failure& output_error) {
    delete [] channelSize;
    throw;
}
delete [] singleChannelData;

РЕДАКТИРОВАТЬ: я добавил try..catch. Конечно, вы могли бы также использовать std:: vector для более приятного кода, но это может быть немного медленнее.

Я испытываю желание сказать, что вы должны прочитать ваши данные в структуру, а затем перегрузить оператор вставки.

ostream& operator<< (ostream& out, struct data * s) {
    out.write(s->first);
}

Во-первых, я бы упомянул, что для максимизации скорости записи вы должны записывать буферы, кратные размеру сектора (например, 64 КБ или 256 КБ)

Чтобы ответить на ваш вопрос, вам нужно будет скопировать каждый третий элемент из ваших исходных данных в другой буфер, а затем записать это в поток.

Если я правильно помню, Intel Performance Primitives имеет функции для копирования буферов, пропуская определенное количество элементов. Использование IPP, вероятно, даст более быстрые результаты, чем ваша собственная процедура копирования.

Другие вопросы по тегам