Рассмотрим следующую, казалось бы, простую проблему, с которой я столкнулся при решении заданий в CV с использованием масок и реализации AA в трассировке лучей.

Цвет в формате RGB имеет три канала, каждый из которых представлен знаком unsigned char, следовательно, 0 <= channel value <= 255. Из соображений симметрии давайте рассмотрим один канал. Я хочу суммировать значения каналов по определенному набору точек, затем усреднить и округлить это (а не просто усечь) и сохранить обратно в unsigned char. Обратите внимание, что во всех задачах результирующее усредненное значение гарантированно находится в диапазоне [0, 255], это инвариант алгоритма.

Это кажется простой задачей, но меня беспокоит следующее:

стр.1 . Самое важное: выбор типа , см. Ниже псевдокод, какие типы мне нужны. Точнее, я получаю ошибку преобразования сужения, которая превращается в ошибку с установленным -pedantic-errors, и я не хочу просто отмахиваться от нее.

стр. 2 . Идея перехода от более широкого типа, который может накапливать много uchar, обратно к типу uchar. Я уверен, что алгоритм гарантированно выдаст значение в диапазоне uchar, но достаточно ли этого? Стоит ли мне беспокоиться об этом?

Псевдокод (cv::Vec3b - это массив фиксированного размера 3x1 unsigned char в OpenCV, который я использую для хранения и вывода изображений):

// this returns a value in the range [0, 255]
cv::Vec3b processing_func(const point&) { ... }

accum_type acc_r = 0, acc_g = 0 , acc_b = 0;
for (point p: point_set) {
    const cv::Vec3b color = processing_func(p);
    acc_r += color[0]; acc_g += color[1]; acc_b += color[2];
}
cv::Vec3b result_color{roundf(acc_r / (floating_type)set_size),
                       roundf(acc_g / (floating_type)set_size),
                       roundf(acc_b / (floating_type)set_size)};
// hello narrowing conversion ^^^

Обратитесь к стр.1 , какими должны быть accum_type и floating_type (последнее требуется для правильного деления без усечения), или вы могли бы закодировать его по-другому?

Отредактируйте после первого комментария: set_size - целое число больше 0, при необходимости его можно жестко запрограммировать, но в целом оно обязательно изменится. Это например Фактор AA, квадрат; или размер маски и т. д.

2
iksemyonov 29 Фев 2016 в 21:04

4 ответа

Лучший ответ

Я бы хотел использовать целые числа без знака: -

uint acc_r = set_size / 2, acc_g = set_size / 2, acc_b = set_size / 2;

for (point p: point_set) {
    const cv::Vec3b color = processing_func(p);
    acc_r += color[0]; acc_g += color[1]; acc_b += color[2];
}

cv::Vec3b result_color{acc_r / set_size, acc_g / set_size, acc_b / set_size}

Обратите внимание, что я инициализирую значения RGB равными 1/2 (set_size / 2), так что деление округляется, а не усекает.

1
Skizz 29 Фев 2016 в 18:19

Я бы использовал unsigned для типа аккумулятора (при условии, что вы накапливаете менее 2 ^ 24 значений) и double для типа с плавающей запятой (поскольку это тип буквальных десятичных значений).

Тогда вам просто не хватает static_cast<unsigned char>(rounded_floating_point_value), чтобы без предупреждений вернуть ваши округленные результаты с плавающей запятой к желаемому типу.

1
Mark B 29 Фев 2016 в 18:26

Стр.1: Используйте явное приведение.

П.2: В общем мало. Каждый раз, когда вы работаете с типами с плавающей запятой, вы теряете точность. Это зависит от вашей операции по отношению к вашему типу с плавающей запятой. Чтобы предотвратить это в вашем случае, вы можете проверить насыщенность, например:

uchar v = (uchar)(value < 255.0 ? value : 255);

Но не думаю, что вам это понадобится.

2
knivil 29 Фев 2016 в 19:12

Я бы использовал unsigned int

cv::Vec3b processing_func(const point&) { ... }

unsigned int acc_r, acc_g, acc_b;
    for (point p: point_set) {
    const cv::Vec3b color = processing_func(p);
    acc_r += color[0]; acc_g += color[1]; acc_b += color[2];
}

Тогда для округления я бы сделал нас из оператора% следующим образом

cv::Vec3b result_color{acc_r / set_size + acc_r % set_size * 2 / set_size,
                       acc_g / set_size + acc_g % set_size * 2 / set_size,
                       acc_b / set_size + acc_b % set_size * 2 / set_size};

Обратите внимание, что 0 <= a % b * 2 < 2b, поэтому a % b * 2 / b равно 0, когда a%b < b/2 и 1, когда a%b >= b/2

1
Adl A 29 Фев 2016 в 19:00