实现原理
PS中的阴影命令是一种用于校正由强逆光而形成剪影的照片的方法。在用其他方式采光的图像中,这种调整也可用于使阴影区域变亮。要实现图像的阴影调整,首先要识别出阴影区;再通过对阴影区的色彩进行一定变换,使其达到提光或者暗化效果;最后也是最重要的,就是对阴影区和非阴影区的边缘作平滑处理。
下方介绍具体流程。
具体流程
1)读取识别图像的原图,并转灰度图,再归一化。
// 生成灰度图 cv::Mat gray = cv::Mat::zeros(input.size(), CV_32FC1); cv::Mat f = input.clone(); f.convertTo(f, CV_32FC3); vector<cv::Mat> pics; split(f, pics); gray = 0.299f*pics[2] + 0.587*pics[1] + 0.114*pics[0]; gray = gray / 255.f;
图1 灰度处理
2)确定阴影区。因为我们要识别阴影,所以thresh通过(1-gray)*(1-gray),得到的图像中原本暗的地方则为亮,取平均值当阈值,进行二值化得到掩膜mask。
// 确定阴影区 cv::Mat thresh = cv::Mat::zeros(gray.size(), gray.type()); thresh = (1.0f - gray).mul(1.0f - gray); // 取平均值作为阈值 Scalar t = mean(thresh); cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1); mask.setTo(255, thresh >= t[0]);
图2 阴影掩膜区
3)对掩膜区边缘进行平滑过渡。假设light为50,那么midrate的掩膜区值为1.5,黑色区为1,过渡区为1~1.5;bright的掩膜区为0.125,黑色区为0,过渡区为0~0.125。
// 参数设置 int max = 4; float bright = light / 100.0f / max; float mid = 1.0f + max * bright; // 边缘平滑过渡 cv::Mat midrate = cv::Mat::zeros(input.size(), CV_32FC1); cv::Mat brightrate = cv::Mat::zeros(input.size(), CV_32FC1); for (int i = 0; i < input.rows; ++i) { uchar *m = mask.ptr<uchar>(i); float *th = thresh.ptr<float>(i); float *mi = midrate.ptr<float>(i); float *br = brightrate.ptr<float>(i); for (int j = 0; j < input.cols; ++j) { if (m[j] == 255) { mi[j] = mid; br[j] = bright; } else { mi[j] = (mid - 1.0f) / t[0] * th[j]+ 1.0f; br[j] = (1.0f / t[0] * th[j])*bright; } } }
4)根据midrate和brightrate,进行阴影区提亮。对非阴影区而言,midrate都为1,brightrate都为0,即没有变化;对阴影区而言,midrate都为1.5,brightrate都为0.125,所以色彩数值均有所增加,带来了提亮效果;对边缘地区,midrate和brightrate起到了很好的过渡作用。
注意:temp要进行数值限制,假设temp大于1,则进行uchar处理后数值会因为类型原因产生突变,那么图像也就变成了五颜六色。
// 阴影提亮,获取结果图 cv::Mat result = cv::Mat::zeros(input.size(), input.type()); for (int i = 0; i < input.rows; ++i) { float *mi = midrate.ptr<float>(i); float *br = brightrate.ptr<float>(i); uchar *in = input.ptr<uchar>(i); uchar *r = result.ptr<uchar>(i); for (int j = 0; j < input.cols; ++j) { for (int k = 0; k < 3; ++k) { float temp = pow(float(in[3 * j + k]) / 255.f, 1.0f / mi[j])*(1.0 / (1 - br[j])); if (temp > 1.0f) temp = 1.0f; if (temp < 0.0f) temp = 0.0f; uchar utemp = uchar(255*temp); r[3 * j + k] = utemp; } } }
C++测试代码
#include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; cv::Mat Shadow(cv::Mat input, int light); int main() { cv::Mat src = imread("test2.jpg"); int light = 50; cv::Mat result = Shadow(src,light); imshow("original", src); imshow("result", result); waitKey(0); return 0; } // 图像阴影选取 cv::Mat Shadow(cv::Mat input, int light) { // 生成灰度图 cv::Mat gray = cv::Mat::zeros(input.size(), CV_32FC1); cv::Mat f = input.clone(); f.convertTo(f, CV_32FC3); vector<cv::Mat> pics; split(f, pics); gray = 0.299f*pics[2] + 0.587*pics[1] + 0.114*pics[0]; gray = gray / 255.f; // 确定阴影区 cv::Mat thresh = cv::Mat::zeros(gray.size(), gray.type()); thresh = (1.0f - gray).mul(1.0f - gray); // 取平均值作为阈值 Scalar t = mean(thresh); cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1); mask.setTo(255, thresh >= t[0]); // 参数设置 int max = 4; float bright = light / 100.0f / max; float mid = 1.0f + max * bright; // 边缘平滑过渡 cv::Mat midrate = cv::Mat::zeros(input.size(), CV_32FC1); cv::Mat brightrate = cv::Mat::zeros(input.size(), CV_32FC1); for (int i = 0; i < input.rows; ++i) { uchar *m = mask.ptr<uchar>(i); float *th = thresh.ptr<float>(i); float *mi = midrate.ptr<float>(i); float *br = brightrate.ptr<float>(i); for (int j = 0; j < input.cols; ++j) { if (m[j] == 255) { mi[j] = mid; br[j] = bright; } else { mi[j] = (mid - 1.0f) / t[0] * th[j]+ 1.0f; br[j] = (1.0f / t[0] * th[j])*bright; } } } // 阴影提亮,获取结果图 cv::Mat result = cv::Mat::zeros(input.size(), input.type()); for (int i = 0; i < input.rows; ++i) { float *mi = midrate.ptr<float>(i); float *br = brightrate.ptr<float>(i); uchar *in = input.ptr<uchar>(i); uchar *r = result.ptr<uchar>(i); for (int j = 0; j < input.cols; ++j) { for (int k = 0; k < 3; ++k) { float temp = pow(float(in[3 * j + k]) / 255.f, 1.0f / mi[j])*(1.0 / (1 - br[j])); if (temp > 1.0f) temp = 1.0f; if (temp < 0.0f) temp = 0.0f; uchar utemp = uchar(255*temp); r[3 * j + k] = utemp; } } } return result; }
测试效果
图1 原图
图2 light为50的效果图
图3 light为-50的效果图
从测试效果中可以看出,阴影区随light变化而产生亮度变化,当light为正值时,阴影区有明显提亮效果;反之,则变更暗。
如果函数有什么可以改进完善的地方,非常欢迎大家指出,一同进步何乐而不为呢~
如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!