实现浮雕效果的算子有很多,效果大同小异,不同算子的处理结果在细节上会有所差异。事实上,任何一阶差分算子都可用于实现浮雕效果,简单起见,这里使用算子[-1,1]。
代码如下
#include<iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp> using namespace std;int main()
{int rows, cols, i, j, tmp;int shift = 128, alpha = 1;cv::Mat img = cv::imread("D:\\timg.jpg");rows = img.rows;cols = img.cols;cv::Mat result(rows, cols - 1, CV_8UC3, cv::Scalar(0, 0, 0));for (i = 0; i < rows ; i++){cv::Vec3b* data1 = img.ptr<cv::Vec3b>(i);cv::Vec3b* data2 = result.ptr<cv::Vec3b>(i);for (j = 0; j < cols - 1; j++){tmp = (-data1[j][0] + data1[j + 1][0])*alpha + shift; //alpha决定雕刻"深度"data2[j][0] = tmp < 0 ? 0 : tmp>255 ? 255 : tmp; //限制像素值范围在0~255tmp = (-data1[j][1] + data1[j + 1][1])*alpha + shift;data2[j][1] = tmp < 0 ? 0 : tmp>255 ? 255 : tmp;tmp = (-data1[j][2] + data1[j + 1][2])*alpha + shift;data2[j][2] = tmp < 0 ? 0 : tmp>255 ? 255 : tmp;}}cv::imshow("result", result);cv::waitKey(10000);return 0;
}
注意限制像素值范围很重要,否则当像素值超出范围时,int型数据赋给uchar型数据会溢出,在结果图像中会产生杂色。
效果如下
原图:
图1 原图
效果图:
图2 浮雕效果图
上图整体呈现灰色,具有浮雕效果的立体感。边缘有略微的彩色,若要消除彩色,可将上图转为灰度图。调整代码中的alpha值,可改变雕刻"深度",增强或减弱立体感。
原理
浮雕效果的特征是具有一定的立体感,这是通过明暗对比实现的,观察图2,可以发现一块凸起的左右边缘总有一侧比背景暗,而另一侧比背景亮,从而形成一种光线从一侧照向另一侧的视觉效果,进而形成立体感。若消除这种明暗对比,则不会形成立体感,将代码作如下修改,对差分结果取绝对值,去除明暗对比
- tmp = (-data1[j][0] + data1[j + 1][0])*alpha + shift;
+ tmp = cv::abs(-data1[j][0] + data1[j + 1][0])*alpha + shift;- tmp = (-data1[j][1] + data1[j + 1][1])*alpha + shift;
+ tmp = cv::abs(-data1[j][1] + data1[j + 1][1])*alpha + shift;- tmp = (-data1[j][2] + data1[j + 1][2])*alpha + shift;
+ tmp = cv::abs(-data1[j][2] + data1[j + 1][2])*alpha + shift;
得到如下效果
图3 平面效果
可见,是差分算子决定了处理结果具有立体感。图像中颜色相近区域进行差分处理后,其值接近0,加上shift后像素各通道值均接近shift,因而呈现灰色,而边缘处值有较大变化,因此可能会呈现彩色,且亮度与背景不同,从而形成物体轮廓,边缘的明暗对比形成立体感,从而呈现浮雕效果。下图示意了处理过程
图4 原图 图5 差分图
图7 结果图
因为差分之后会存在负值,负值无法显示,因此需要一个基底衬托,一般取最大值的一半,即128,但不是绝对的。这就是图7中shift的作用,使暗边缘得以显示。
应用
图6到图7的过程可以看成是在一幅单色基底图像上叠加上差分图像,基底图像每个像素每个通道值均为shift,那么基底必须是单色图像吗,如果换成其他图像会呈现什么效果呢?下面我们尝试把一幅图像的差分图像叠加到另一幅图像上,使用图1作为基底,对图8作差分后叠加到基底上,看看会出现什么效果。
图8
代码如下
#include<iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp> using namespace std;int main()
{int rows, cols, i, j, tmp;int alpha = 1;cv::Mat img1 = cv::imread("D:\\seal.jpg"); //已知seal.jpg宽高均小于timg.jpgcv::Mat img2 = cv::imread("D:\\timg.jpg"); //基底图像rows = img1.rows;cols = img1.cols;for (i = 0; i < rows ; i++){cv::Vec3b* data1 = img1.ptr<cv::Vec3b>(i);cv::Vec3b* data2 = img2.ptr<cv::Vec3b>(i);for (j = 0; j < cols - 1; j++){tmp = (-data1[j][0] + data1[j + 1][0])*alpha + data2[j][0];data2[j][0] = tmp < 0 ? 0 : tmp>255 ? 255 : tmp;tmp = (-data1[j][1] + data1[j + 1][1])*alpha + data2[j][1];data2[j][1] = tmp < 0 ? 0 : tmp>255 ? 255 : tmp;tmp = (-data1[j][2] + data1[j + 1][2])*alpha + data2[j][2];data2[j][2] = tmp < 0 ? 0 : tmp>255 ? 255 : tmp;}}cv::imshow("result", img2);cv::waitKey(100000);return 0;
}
效果如下图所示,在图1左上角敲了个钢印。
图9 钢印效果
仔细观察可以发现,图9中的钢印中间部分不明显,这是由于只计算了x方向的差分,而印章中间部分在x方向变化小,因此差分值较小。如果叠加上y方向的差分图,就会比较明显。