Доступ к значениям пикселей без загрузки изображения в память для больших изображений

Мне нужно вычислить среднее значение изображения с помощью библиотеки CImg следующим образом:

int i = 0;
float mean = 0;
CImg<float> img("image.cimg");  
float *ptr = img.data(); //retrieves pointer to the first value
while(i<img.width()*img.height()*img.spectrum()){
    mean += *(ptr+i);
    ++i;
}
std::cout << "mean: " << mean/i << std::endl;

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

Когда размер изображения увеличивается слишком сильно, третья строка моего кода потребляет слишком много ресурсов моего компьютера, потому что согласно documentation одновременно сохраняет все пиксели изображения в буфере памяти.

Я подумал о решении еще более низкого уровня, используя системные вызовы open() и read() следующим образом:

int i = 0;
int k = WIDTH*HEIGHT*SPECTRUM; //assuming this values are known
float mean = 0, aux;
int fd = open("image.cimg", O_RDONLY);
while(i<k){ 
    read(fd, &aux, sizeof(float));
    mean += aux; 
    ++i;
}
close(fd);
std::cout << "mean: " << mean/i << std::endl;

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


person Anthony    schedule 26.11.2013    source источник
comment
Составные данные обычно содержат заголовок, описывающий информацию. CImg-c'tor знает, как это читать, и предоставляет соответствующие номера (img.width() и т. д.). Ваше наивное решение читает заголовок, как будто это данные изображения, и делает другие предположения о макете, которые могут быть неверными.   -  person IInspectable    schedule 26.11.2013
comment
В дополнение к @IInspectable: определите, что хранится на диске так же, как при загрузке в память. Вы не можете ответить на этот вопрос, не зная, как CImg хранит свои данные внутри, но, скорее всего, ответ будет отрицательным. Подпрограмма чтения интерпретирует такие вещи, как ширина, высота, количество бит на пиксель и т. д., и скрывает эти детали от программиста через свой класс. Если вы можете получить описание ваших файлов cimg (тип, с которым я не знаком), вы можете сами написать класс и добавить, например, функцию чтения только одной строки. Это требует времени, но вы можете использовать гораздо меньше памяти.   -  person Jongware    schedule 26.11.2013


Ответы (1)


Проблема заключается во второй строке вашего кода, потому что вы сделали mean (хотя лучше было бы назвать ее sum) простой float. Поскольку каждый пиксель вашего изображения также является float, вы столкнетесь с проблемами, если ваше изображение, скажем, 10 000x10 000, потому что вы попытаетесь сохранить сумму 100M floats в float.

Самое простое решение - изменить строку 2 на:

double mean=0;

В качестве альтернативы вы можете вычислять среднее значение постепенно по мере продвижения без его переполнения следующим образом:

float mean = 0;
int i = 1;
while(...){
  mean+= (x - mean)/i;
  ++i;
}

Кстати, если у вас действительно большие изображения, могу порекомендовать vips, это очень быстро и очень эффективно, например, если я создам TIF размером 10 000x10 000 пикселей и попрошу vips усреднить его из командной строки:

time vips avg image.tif --vips-leak
0.499994

memory: high-water mark 7.33 MB

real    0m0.384s
user    0m0.492s
sys     0m0.233s

Вы можете видеть, что это занимает 0,4 секунды и достигает пика при использовании памяти 7 МБ.

person Mark Setchell    schedule 30.09.2016