图像处理学习笔记-常规图片处理

510 阅读3分钟

概述

本篇博客主要记录如何使用OpenCV对图片进行常规的处理,包括如下内容

  • 对比度
  • 亮度
  • 色调
  • 饱和度
  • 色温
  • 高光
  • 阴影
  • 锐化
  • 暗角

代码示例

例子的地址

例子为CMake项目,目前只在MacOS上运行。运行项目中的Target OpenCVHelloWorld,可查看效果

例子使用的测试原图

crystal_carve.png

对比度和亮度

将图片转换到HSL空间,然后使用对比度和亮度对L分量进行重新计算,OpenCV中L分量的取值从0到255,只越大,像素的明度越高

L_new = L * 对比度 + 亮度

对应代码如下

auto hslVal = hslCopy.at<cv::Vec3b>(i,j); // 取出hsl空间的一个像素
int lval =  _contractScale * hslVal[2] + _lightnessOffset; // 按照公式计算新的L,这里_contractScale可以取0以上的浮点数,_lightnessOffset取0到255
if (lval > 255) {
    lval = 255;
}
if (lval < 0) {
    lval = 0;
}

image.png

色调

色调则是依赖HSL空间的H分量,OpenCV中H分量的取值从0到180,通过改变H值可以改变像素的颜色

auto hslVal = hslCopy.at<cv::Vec3b>(i,j);

int hVal = hslVal[0] + _hueOffset;
while (hVal > 180) {
    // 如果超过180,循环到小于180为止
    hVal -= 180;
}

image.png

饱和度

饱和度则是依赖HSL空间的S分量,OpenCV中S分量的取值从0到255,通过改变S值可以改变像素的饱和度

auto hslVal = hslCopy.at<cv::Vec3b>(i,j);

int sval = hslVal[1] + _saturationOffset;
if (sval > 255) {
    sval = 255;
}
if (sval < 0) {
    sval = 0;
}

image.png

色温

色温通过修改RGB中分量的占比实现,温度高,则提高R,G分量,温度低,则提高B分量

auto bgrVal = finalImg.at<cv::Vec3b>(i,j);

int bVal = bgrVal[0];
int gVal = bgrVal[1];
int rVal = bgrVal[2];

// 使用乘法,保证各个分量不会同步增长,导致全屏泛红(蓝色)
if (_temperatureScale > 0) {
    rVal *= 1.0 + _temperatureScale;
    gVal *= (1.0 + _temperatureScale * 0.4f);
} else {
    bVal *= 1.0f -_temperatureScale;
}
rVal = std::min(255, rVal);
gVal = std::min(255, gVal);
bVal = std::min(255, bVal);

finalImg.at<cv::Vec3b>(i,j) = cv::Vec3b{(unsigned char)bVal, (unsigned char)gVal, (unsigned char)rVal};

这里我让g分量的增长小于r,从而呈现出偏橙色的暖色效果

image.png

高光和阴影

为了单独改变高光和阴影,首先要确定高光和阴影的选区

  cv::cvtColor(originImg, highlightMask, cv::COLOR_BGR2GRAY);
  for(int i = 0; i < highlightMask.rows; i++) {
    for (int j = 0; j < highlightMask.cols; j++) {
      auto grayVal = highlightMask.at<unsigned char>(i, j);
      if (grayVal > 150) {
        highlightMask.at<unsigned char>(i, j) = 255;
      } else {
        float falloffFactor = highlightMask.at<unsigned char>(i, j) / 150.0f;
        falloffFactor = powf(falloffFactor, 2);
        highlightMask.at<unsigned char>(i, j) = (int)(falloffFactor * 255);
      }
    }
  }

  cv::cvtColor(originImg, shadowMask, cv::COLOR_BGR2GRAY);
  for(int i = 0; i < shadowMask.rows; i++) {
    for (int j = 0; j < shadowMask.cols; j++) {
      auto grayVal = shadowMask.at<unsigned char>(i, j);
      if (grayVal < 50) {
        shadowMask.at<unsigned char>(i, j) = 255;
      } else {
        float falloffFactor = (255 - shadowMask.at<unsigned char>(i, j)) / (255.0 - 50.0);
        falloffFactor = powf(falloffFactor, 2);
        shadowMask.at<unsigned char>(i, j) = (int)(falloffFactor * 255);
      }
    }
  }

通过上面的代码得到高光和阴影的2个mask,也就是2张表示高光和阴影区域的灰度图。为了边缘过渡平滑,不在区间内的按照梯度衰减,而不是都设置为0 接下来使用mask调整L值即可,配合对比度和亮度调整,整体如下

unsigned char highlightFactor = highlightMask.at<unsigned char>(i, j);
unsigned char shadowFactor = shadowMask.at<unsigned char>(i, j);
int lval =  _contractScale * hslVal[2] + _lightnessOffset + highlightFactor / 255.0 * _highlightOffset + shadowFactor / 255.0 * _shadowOffset;

highlightFactorshadowFactor控制是否使用对应的增益,_highlightOffset_shadowOffset控制增益的强度,可以取正值和负值

image.png

锐化

将原图和高斯模糊后的图进行相减,即可得到锐化的效果

// 得到高斯模糊后的图
cv::GaussianBlur(originImg, blurMask, {0, 0}, 5);

// 使用addWeighted进行相减
cv::addWeighted(finalImg, 1.0 + _sharpenOffset, blurMask, -_sharpenOffset, 0, finalImg);

_sharpenOffset可以取0到0.9的值,值越大,效果与明显。

image.png

暗角

暗角的原理是,根据像素点离中心的距离,减弱像素的明度,从而达到四个角变暗的效果

float distanceToCenter = sqrt(pow(i - middleRow, 2) + pow(j - middleCol, 2));
// 进行重映射,保证像素从距离50%之后才开始变暗,并且通过_cornerOffset来控制暗角的比例,为0则无暗角
float cornerFactor = 1.0 - std::max(distanceToCenter / radius * 2.0 - 1.0, 0.0) * _cornerOffset;
// lval就是明度L值
lval *= cornerFactor;

image.png

自然饱和度

自然饱和度相对于饱和度,会优先提升饱和度低的像素,具体算法参考了开源软件github.com/tannerhella…,代码如下,adjustment取值从-1到1

cv::Vec3b CVPixelOpUtil::calcNatureSaturation(cv::Vec3b rgbColor, float adjustment) {
  cv::Vec3b finalColor = rgbColor;

  float realAdj = -adjustment; // -1 ~ 1
  int avg = (rgbColor[0] + rgbColor[1] * 2 + rgbColor[2]) >> 2;
  int max = std::max({(int)rgbColor[0], (int)rgbColor[1] ,(int)rgbColor[2]});
  float finalAdj = (max - avg) / 127.0 * realAdj;
  if (finalColor[0] != max) finalColor[0] = cv::saturate_cast<unsigned char>(finalColor[0] + (max - finalColor[0]) * finalAdj);
  if (finalColor[1] != max) finalColor[1] = cv::saturate_cast<unsigned char>(finalColor[1] + (max - finalColor[1]) * finalAdj);
  if (finalColor[2] != max) finalColor[2] = cv::saturate_cast<unsigned char>(finalColor[2] + (max - finalColor[2]) * finalAdj);
  return finalColor;
}

对于人像,可以明显看出自然饱和度的优势

image.png