📜 ⬆️ ⬇️

A simple graphic editor using OpenCV

In this article I will explain how to quickly and simply write an image editor in C ++ using the opencv computer vision library. Implemented effects such as saturation, exposure, sharpness, contrast, and others. No magic!

image

Attention! Under the cut a lot of graphics and code.

So, let's begin…
')

Saturation


Ingredients:
- HSV color system,
- the function of splitting into layers «split»,
- merge layer merging function.

To change the saturation, the image is converted to an HSV color system and split into layers. A step is added to the “Sature” layer values. Layers are combined. It's simple:

Saturation
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) { } } 


BeforeAfter

Exposition


Ingredients:
- HSV color system,
- the function “split”, “merge”, and also the transformation function by the histogram “LUT”,
- histogram transformed by the function x + sin (x * 0.01255) * step * 10,
- protection against overflow byte values ​​of the histogram.
As with saturation, the image is converted to HSV and split into layers. For the “Value” layer, we perform the transformation using the histogram specified by the function i + sin (i * 0.01255) * step * 10. At the same time, we do not forget to protect ourselves from overflowing the byte number.
Exposition
 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; } 


BeforeAfter
Graph of the function x + sin (x * 0.01255) * step * 10
image
The function mainly affects the middle of the range.

Tint


Ingredients:
- RGB color system
- function "split", "merge" and "LUT",
- histograms transformed by the exposure function for the red, blue and green channels,
- protection against histogram overflow.

The hue parameter indicates the presence of green and purple in the image. In the RGB color system, you can control the green layer, but you must not forget to compensate for the drop in brightness of the other two layers. A positive gamma exposure function is used to transform the red and blue layers, negative for the green.

Tint
 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) { } } 


BeforeAfter

Colour temperature


Ingredients: the same as in shade, but histograms for red and green are positive, and for the blue layer, double negative.

The color temperature indicates the presence in the image of yellow and blue colors. So we will "turn" blue.

Colour temperature
 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) { } } 


BeforeAfter

Light and shade


Ingredients:
- HSV color system,
- function "split", "merge", "LUT",
- the histogram of shadows transformed by the function (0.36811145 * e) ^ (- (x ^ 1.7)) * 0.2x * step,
- histogram of lights, transformed by the function (0.36811145 * e) ^ (- (256 - x) ^ 1.7) * 0.2 (256-x) * step,
- protection against histogram overflow.

The “light” parameter characterizes the brightness of the bright areas of the image, and the “shadow” parameter defines the brightness of the dark areas. We will convert the channel brightness.

<img src = " "alt =" image "/>

In the graph, the shadow conversion function is indicated by a red line, the light function by a green line.

Light and shade
 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; } 


ShineShadows

Contrast


Ingredients:
- RGB color system
- function "split", "merge", "LUT",
- contrast level "(100 + step) / 100",
- contrast histogram obtained from the formula ((x / 255 - 0.5) * constrastLevel + 0.5) * 255.

The contrast is determined by the difference in brightness. Those. to increase the contrast we need to push the range of brightness from the center to the edges. Conversion is performed for all layers.

Contrast
 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; } } 


image

Red line - increased contrast, green - reduced.

BeforeAfter

Sharpness


Ingredients:
- blur function "blur",
- convolution matrix, with calculated coefficients,
- the transformation function by the convolution matrix “filter2D”,
- a copy of the image.

Sharpness (clarity) is determined by the selection of individual elements, their contours. The inverse of the sharpness is blurring.
In opencv, for blurring an image, we use the blur function, which takes as parameters the original image, the output image, and the size of the blur matrix. The size of the blur matrix and the strength of the blur. This size must be even so as not to manually specify the center of the matrix.

The clarity in opencv is easiest to enhance with a convolution matrix, using a special matrix for this. The “filter2D” function, which receives the original image, the resulting image, the number of bits per convolution matrix value, the convolution matrix, performs the conversion directly. So, how the method of increase / decrease of clearness will look.

Sharpness
 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; } } 


BeforeAfter

Total


Almost no magic. Well, the magic numbers are found empirically, so you can use your own, the most appropriate ones instead.
Link to the demo application.

Source: https://habr.com/ru/post/268115/


All Articles