[Из песочницы] Просто редактор использованием OpenCV
В этой статье я расскажу, как достаточно быстро и просто написать редактор изображений на C++ с использованием библиотеки компьютерного зрения opencv. Реализованы такие эффекты, как насыщенность, экспозиция, резкость, контрастность и другие. Никакой магии!
Внимание! Под катом много графики и кода.
Итак, начнем…
Насыщенность
Ингредиенты:
— система цветности 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;
}
}
Итог
Почти никакой магии. Ну а магические числа найдены эмпирически, поэтому вместо них можно использовать свои, наиболее подходящие.
Ссылка на работающее приложение.