版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/kezunhai/article/details/49474731
在图像特效或应用中,素描是一种常见的特效,本文简单介绍几种常见的素描算法。首先,分享两个opencv中Mat类型的像素数据读取函数:
inline uchar getPixel(const Mat& img, int row, int col, int ch)
{
return ((unsigned char*)img.data + img.step*row)[col*img.channels()+ch];
}
inline void setPixel(Mat& img, int row, int col, int ch,uchar val)
{
((unsigned char*)img.data + img.step*row)[col*img.channels()+ch] = val;
}
1)、中心像素与周围八像素比较(或与右下像素比较),原理很简单,直接上代码:
// 素描操作
void doSketch1(const Mat& src, Mat& dst, int thresh/* =20 */)
{
if ( dst.empty())
dst.create(src.rows, src.cols, src.type());
else
{
dst.release();
dst.create(src.rows, src.cols, src.type());
}
dst = cv::Scalar::all(0);
int height = src.rows;
int width = src.cols;
int chns = src.channels();
int border = 1;
int i, j, k;
for ( i=border; i<height-border; i++)
{
unsigned char* dstData = (unsigned char*)dst.data + dst.step*i;
for ( j=border; j<width-border; j++)
{
for ( k=0; k<chns; k++)
{
int sum = 8*getPixel(src, i, j, k) - getPixel(src, i-1, j-1, k) - getPixel(src, i-1, j, k)
- getPixel(src, i-1, j+1, k) - getPixel(src, i, j-1, k) - getPixel(src, i, j+1, k)
- getPixel(src, i+1, j-1, k) - getPixel(src, i+1, j, k) - getPixel(src, i+1, j+1, k) ;
dstData[j*chns+k] = saturate_cast<uchar>(sum*(1+thresh/100.0)) ;
// 或右下像素
//int sum = getPixel(img, i, j, k) - getPixel(img, i+1, j+1,k);
//if ( sum>thresh/100.0*255)
// dstData[j*chns+k] = (uchar)255;
// //dstData[j*chns+k] = saturate_cast<uchar>(sum) ;
//else
// dstData[j*chns+k] = (uchar)0;
}
}
}
}
2)、对灰度图像取反==>高斯低通滤波==>颜色简单融合
百度查素描,基本上是这种方法,原理也很简单,直接上代码:
//对灰度图像取反,然后高斯低通滤波,然后使用公式 C = MIN( A +(A×B)/(255-B),255) 颜色减淡
void doSketch2(const Mat& src, Mat& dst, int thresh/* =20 */)
{
Mat gray;
if ( src.channels()!=1)
cvtColor(src, gray, CV_BGR2GRAY);
else
src.copyTo(gray);
if ( !dst.empty())
{
dst.release();
dst.create(src.rows, src.cols, CV_8UC1);
}
else
dst.create(src.rows, src.cols, CV_8UC1);
dst = cv::Scalar::all(0);
Mat mInvert;
bitwise_not(src, mInvert);
Mat mGauss;
GaussianBlur(mInvert, mGauss, Size(3,3),0,0);
int rows = src.rows, cols = src.cols;
double dGauss, dSrc;
for ( int y = 0; y<rows; y++)
{
uchar* srcPtr = (uchar*)src.data + src.step*y;
uchar* gaussPtr = (uchar*)mGauss.data + mGauss.step*y;
uchar* dstPtr = (uchar*)dst.data + dst.step*y;
for ( int x=0; x<cols; x++)
{
dSrc = (double)srcPtr[x];
dGauss = (double)gaussPtr[x];
int iValue = (int)(dSrc+dSrc*dGauss/(256-dGauss));
//dstPtr[x] = saturate_cast<uchar>(dSrc+dSrc*dGauss/(256-dGauss));
if ( iValue>255)
dstPtr[x]=255;
else if ( iValue<0)
dstPtr[x]=0;
else
dstPtr[x] = iValue;
}
}
}
3)直接用梯度幅值来近似,代码如下:
void doSketch3(const Mat& src, Mat& dst, int thresh/* =20 */)
{
Mat gray;
if ( src.channels()!=1)
cvtColor(src, gray, CV_BGR2GRAY);
else
src.copyTo(gray);
if ( !dst.empty())
{
dst.release();
dst.create(src.rows, src.cols, CV_8UC1);
}
else
dst.create(src.rows, src.cols, CV_8UC1);
dst = cv::Scalar::all(0);
Mat sobleX,sobleY;
cv::Sobel(gray,sobleX,CV_16S,1,0);
cv::Sobel(gray,sobleY,CV_16S,0,1);
cv::Mat sobel;
//compute the L1 norm
sobel= abs(sobleX)+abs(sobleY);
double sobmin, sobmax;
cv::minMaxLoc(sobel,&sobmin,&sobmax);
cv::normalize(sobel, dst, 0,255, NORM_MINMAX, CV_8U);
//sobel.convertTo(dst,CV_8U,-255./sobmax,255);
}
4)该种方法是当前效果比较理想的方法,参考论文:Combining Sketch and Tone for Pencil Drawing Production 该方法的详细内容可以参考:
① http://www.cnblogs.com/Imageshop/p/4285566.html
② http://www.cse.cuhk.edu.hk/~leojia/projects/pencilsketch/pencil_drawing.htm
上几张效果图:
关于最后一个方法,上几张效果图: