素描图像提取方法

版权声明:本文为博主原创文章,转载请注明出处。 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

上几张效果图:



扫描二维码关注公众号,回复: 3759950 查看本文章





关于最后一个方法,上几张效果图:






猜你喜欢

转载自blog.csdn.net/kezunhai/article/details/49474731