Внимание! Под катом много графики и кода.
Итак, начнем…
Насыщенность
Ингредиенты:
— система цветности HSV,
— функция разбиения на слои «split»,
— функция объединения слоев «merge».
Для изменения насыщенности изображение преобразуется в систему цветности HSV и разбивается на слои. К значениям слоя «Sature» прибавляется шаг. Слои объединяются. Все просто:
void CImageEditor::Sature(int step)
{
try
{
std::vector<Mat> hsv;
cv::cvtColor(*m_imgEdit, *m_imgEdit, cv::ColorConversionCodes::COLOR_RGB2HSV_FULL);
cv::split(*m_imgEdit, hsv);
hsv[1] += step * 5;
cv::merge(hsv, *m_imgEdit);
cv::cvtColor(*m_imgEdit, *m_imgEdit, cv::ColorConversionCodes::COLOR_HSV2RGB_FULL);
}
catch (Exception ex)
{
}
}
Экспозиция
Ингредиенты:
— система цветности HSV,
— функция «split», «merge», а также функция преобразования гистограммой «LUT»,
— гистограмма преобразованная функцией x + sin(x * 0.01255) * step * 10,
— защита от переполнения байтовых значений гистограммы.
Как и в случае с насыщенностью, изображение преобразуется в HSV и разбивается на слои. Для слоя «Value» выполняем преобразование с помощью гистограммы, заданной функцией i + sin(i * 0.01255) * step * 10. При этом не забываем защититься от переполнения байтового числа.
void CImageEditor::Expo(int step)
{
try
{
std::vector<Mat> hsv;
cv::cvtColor(*m_imgEdit, *m_imgEdit, cv::ColorConversionCodes::COLOR_RGB2HSV_FULL);
Mat lut = GetGammaExpo(step);
cv::split(*m_imgEdit, hsv);
cv::LUT(hsv[2], lut, hsv[2]);
cv::merge(hsv, *m_imgEdit);
cv::cvtColor(*m_imgEdit, *m_imgEdit, cv::ColorConversionCodes::COLOR_HSV2RGB_FULL);
}
catch (Exception ex)
{
}
}
cv::Mat CImageEditor::GetGammaExpo(int step)
{
Mat result(1, 256, CV_8UC1);
uchar* p = result.data;
for (int i = 0; i < 256; i++)
{
p[i] = AddDoubleToByte(i, std::sin(i * 0.01255) * step * 10);
}
return result;
}
byte CImageEditor::AddDoubleToByte(byte bt, double d)
{
byte result = bt;
if (double(result) + d > 255)
result = 255;
else if (double(result) + d < 0)
result = 0;
else
{
result += d;
}
return result;
}
График функции x + sin(x * 0.01255) * step * 10
Функция в основном затрагивает середину диапазона.
Оттенок
Ингредиенты:
— система цветности RGB,
— функция «split», «merge» и «LUT»,
— гистограммы, преобразованные функцией экспозиции, для красного, синего и зеленого каналов,
— защита от переполнения значений гистограммы.
Параметр оттенка характеризует наличие в изображении зеленого и пурпурного цвета. В системе цветности RGB можно управлять зеленым слоем, но при этом нужно не забывать компенсировать падение яркости другим двумя слоями. Для преобразования красного и синего слоев используется положительная гамма-функция экспозиции, для зеленого – отрицательная.
void CImageEditor::Hue(int step)
{
try
{
std::vector<Mat> rgb;
Mat lut0 = GetGammaExpo(step), lut1 = GetGammaExpo(-step), lut2 = GetGammaExpo(step);
cv::split(*m_imgEdit, rgb);
LUT(rgb[0], lut0, rgb[0]);
LUT(rgb[1], lut1, rgb[1]);
LUT(rgb[2], lut2, rgb[2]);
cv::merge(rgb, *m_imgEdit);
}
catch (Exception ex)
{
}
}
Цветовая температура
Ингредиенты: те же, что и в оттенке, но гистограммы для красного и зеленого положительные, а для синего слоя двойная отрицательная.
Цветовая температура характеризует наличие в изображении желтого и синего цветов. Значит будем «крутить» синий.
void CImageEditor::Temperature(int step)
{
try
{
std::vector<Mat> rgb;
Mat lut0 = GetGammaExpo(-step*2), lut1 = GetGammaExpo(step), lut2 = GetGammaExpo(step);
cv::split(*m_imgEdit, rgb);
LUT(rgb[0], lut0, rgb[0]);
LUT(rgb[1], lut1, rgb[1]);
LUT(rgb[2], lut2, rgb[2]);
cv::merge(rgb, *m_imgEdit);
}
catch (Exception ex)
{
}
}
Свет и тени
Ингредиенты:
— система цветности HSV,
— функция «split», «merge», «LUT»,
— гистограмма теней, преобразованная функцией (0.36811145*e)^(-(x^1.7))*0.2x*step,
— гистограмма светов, преобразованная функцией (0.36811145*e)^(-(256 — x)^1.7)*0.2(256-x)*step,
— защита от переполнения значений гистограммы.
Параметр «свет» характеризует яркость светлых областей изображения, а параметр «тени» — яркость темных областей. Преобразовывать будем канал яркостей.
<img src="" alt=«image»/>
На графике функция преобразования теней обозначается красной линией, функция света – зеленой.
void CImageEditor::White(int step)
{
try
{
std::vector<Mat> hsv;
cv::cvtColor(*m_imgEdit, *m_imgEdit, cv::ColorConversionCodes::COLOR_RGB2HSV_FULL);
cv::split(*m_imgEdit, hsv);
Mat lut = GetGammaLightShadow(step, true);
LUT(hsv[2], lut, hsv[2]);
cv::merge(hsv, *m_imgEdit);
cv::cvtColor(*m_imgEdit, *m_imgEdit, cv::ColorConversionCodes::COLOR_HSV2RGB_FULL);
}
catch (Exception ex)
{
AfxMessageBox(CString(CStringA(ex.msg.begin())));
throw;
}
}
void CImageEditor::Shadow(int step)
{
try
{
std::vector<Mat> hsv;
cv::cvtColor(*m_imgEdit, *m_imgEdit, cv::ColorConversionCodes::COLOR_RGB2HSV_FULL);
cv::split(*m_imgEdit, hsv);
Mat lut = GetGammaLightShadow(step, false);
LUT(hsv[2], lut, hsv[2]);
cv::merge(hsv, *m_imgEdit);
cv::cvtColor(*m_imgEdit, *m_imgEdit, cv::ColorConversionCodes::COLOR_HSV2RGB_FULL);
}
catch (Exception ex)
{
AfxMessageBox(CString(CStringA(ex.msg.begin())));
throw;
}
}
Mat CImageEditor::GetGammaLightShadow(int step, bool reverse)
{
Mat result(1, 256, CV_8UC1);
for (int i = 0; i < 256; i++)
{
*(result.data + i) = AddDoubleToByte(i, std::pow(0.36811145*M_E,
-std::pow(abs((reverse ? 256 : 0) - i), 1.7))*0.2*step*abs((reverse ? 256 : 0) - i));
}
return result;
}
Контраст
Ингредиенты:
— система цветности RGB,
— функция «split», «merge», «LUT»,
— уровень контраста «(100+step)/100»,
— гистограмма контрастности, полученная из формулы ((x/255 – 0.5)*constrastLevel + 0.5)*255.
Контраст определяется в разности яркостей. Т.е. для увеличения контраста нам нужно раздвинуть диапазон яркостей от центра к краям. Преобразование выполняется для всех слоев.
void CImageEditor::Contrast(int step)
{
try
{
std::vector<Mat> rgb;
cv::split(*m_imgEdit, rgb);
Mat lut(1, 256, CV_8UC1);
double contrastLevel = double(100 + step) / 100;
uchar* p = lut.data;
double d;
for (int i = 0; i < 256; i++)
{
d = ((double(i) / 255 - 0.5)*contrastLevel + 0.5) * 255;
if (d > 255)
d = 255;
if (d < 0)
d = 0;
p[i] = d;
}
LUT(rgb[0], lut, rgb[0]);
LUT(rgb[1], lut, rgb[1]);
LUT(rgb[2], lut, rgb[2]);
cv::merge(rgb, *m_imgEdit);
}
catch (Exception ex)
{
AfxMessageBox(CString(CStringA(ex.msg.begin())));
throw;
}
}
Красная линия – повышенный контраст, зеленая – пониженный.
Резкость
Ингредиенты:
— функция размытия «blur»,
— матрица свертки, с рассчитанными коэффициентами,
— функция преобразования матрицей свертки «filter2D»,
— копия изображения.
Резкость (четкость) определяется выделением отдельных элементов, их контуров. Величина, обратная резкости – размытость.
В opencv для размытия изображения используем функцию blur, принимающую в качестве параметров исходное изображение, выходное изображение, и размер матрицы размытия. От размера матрицы размытия и зависит сила размытия. Этот размер должен быть четным, чтобы не указывать вручную центр матрицы.
Четкость в opencv проще всего повысить с помощью матрицы свертки, используя специальную для этого матрицу. Функция «filter2D», которая принимает исходное изображение, результирующее изображение, количество бит на значение матрицы свертки, матрицу свертки, выполняет непосредственно преобразование. Итак, как будет выглядеть метод повышения/понижения четкости.
void CImageEditor::Clarity(int step)
{
try
{
if (step < 0)
{
cv::blur(*m_imgEdit, *m_imgEdit, cv::Size(-step * 2 + 1, -step * 2 + 1));
}
else
{
Mat dst = m_imgEdit->clone();
float matr[9] {
-0.0375 - 0.05*step, -0.0375 - 0.05*step, -0.0375 - 0.05*step,
-0.0375 - 0.05*step, 1.3 + 0.4*step, -0.0375 - 0.05*step,
-0.0375 - 0.05*step, -0.0375 - 0.05*step, -0.0375 - 0.05*step
};
Mat kernel_matrix = Mat(3, 3, CV_32FC1, &matr);
cv::filter2D(*m_imgEdit, dst, 32, kernel_matrix);
m_imgEdit = make_shared<Mat>(dst);
}
}
catch (Exception ex)
{
AfxMessageBox(CString(CStringA(ex.msg.begin())));
throw;
}
}
Итог
Почти никакой магии. Ну а магические числа найдены эмпирически, поэтому вместо них можно использовать свои, наиболее подходящие.
Ссылка на демонстрационное приложение.
Комментарии (24)
Shablonarium
02.10.2015 17:58+1Какова производительность?
alex-v-93
02.10.2015 18:27Хороший вопрос. Изображение ~3500x3500 по всем фильтрам на одном ядре 2.7GHz обрабатывается около одной секунды. При этом я запускал 32-битный процесс на win-64, а значит тратится время на преобразование команд в 64-bit.
Также для 32-битного процесса есть ограничение на разрешение изображения — не больше 8000x8000. Это связано в большим объемом данных, занимаемым изображением при повышении точности, которое происходит в функции filter2D.Shablonarium
02.10.2015 18:53+1А можно примеры сравнительно с другими продуктами или программами если есть? Хочется примерно понять какой оверхэд c OpenCV, 15% или 200%.
alex-v-93
02.10.2015 19:13С фотошопом, гимпом и прочими редакторами нет смысла сравнивать, т.к. любой уважающий себя редактор мгновенно обрабатывает preview изображения, выводя на экран, а потом уже фоном «допиливают» оригинал.
Когда выбирал библиотеку для редактора, думал взять GDI+, но в сравнении с openCV GDI+ проигрывала больше, чем в 10 раз, хотя там алгоритмы яркости, контрастности и пр. уже реализованы — остается только вызвать.
При грамотном использовании скорость работы фильтров с openCV будет не меньше, чем у фотошопа.
Marsikus
Можно поинтересоваться, а зачем вы на гитхаб выкладываете запакованное в RAR архив? Первый раз такое вижу :)
Obramko
Так ведь Reposit1.
alex-v-93
Изначально планировал загрузить весь проект, но непонятным причинам приложение github при загрузке просто падало. Проект загрузился только в таком виде.
slaykovsky
За rar вообще должно быть стыдно в 2015 году.
alex-v-93
Перевыложил в нормальном виде.
KvanTTT
А зачем бинари в исходниках? Для релизов есть соответствующая функциональность.
pfemidi
Не в свете github+rar, на гитхабе действительно rar не к месту, а просто поинтересоваться: если за rar вообще должно быть стыдно в 2015 году, то за что в 2015 году и в уже наступающем 2016 должно или может быть не стыдно?
slaykovsky
Over9K открытых форматов: ZIP, Tar, etc.
Ну и да, в git класть архив, это совсем не то пальто.
KvanTTT
Есть множество других может и не очень распространенных (как и WinRar тоже), но зато бесплатных форматов (7zip). И, как выше уже отметились, открытых. Таким образом, смысл использования проприетарного архиватора WinRar и соответствующего формата теряется. А так вообще zip является стандартом де-факто.
alex-v-93
rar архив можно открыть с помощью бесплатного 7-zip.
KvanTTT
Зато нельзя создать с помощью него.
alex-v-93
В данном контексте главное его прочитать.
pfemidi
Извините, но rar (не именно WinRar, а просто rar) распространён ничуть не меньше, чем zip. Чего не скажешь про тот же 7zip, который пусть и бесплатен, но используется хорошо если одним человеком из ста. Впрочем ответ я получил, теперь знаю почему rar'ом, который жмёт лучше чем zip (но хуже, чем bzip2, правда быстрее) пользоваться стыдно. Стыжусь, но на винде продолжаю пользоваться именно им.
calx
Сам вопрос, кто жмёт лучше или быстрее, уже давно неактуален в подавляющем большинстве случаев, поэтому нет никакого смысла использовать что-то кроме стандартного zip (или tar+gzip/bzip2 если не предполагается разворачивать архив на винде).
Darthman
Вот у меня дома нечем открыть 7zip, я им не пользуюсь. Зато WinRar есть. rar и zip для архивов в сети — стандарт де факто. Вот что мне из-за одного проекта чьего-то себе 7zip ставить? Право, не смешите. А популярные все архиваторы и разархиваторы умеют rar распаковывать. Если не ошибаюсь это даже в total comander встроено.
KvanTTT
Разве его не открывает WinRar, это даже странно. А я не пользуюсь раром уже лет 5. Зачем нужен рар, когда есть зип?