简单

行远必自迩,登高必自卑

素描算法(图像处理)

素描算法介绍

素描算法其实就是几种简单的图像处理算法,对同一张图片进行处理后,产生的一种类似素描的算法。这里简单的描述一下他的原理。

  1. 彩色图像进行类似直方图均衡化处理,增强图像的对比度,使得图像的轮廓更加分明。
  2. 将步骤 1 处理后的彩色图像进行灰度处理得到图像 gray。
  3. 将得到的图像 gray 复制一份得到图像 gray1。
  4. 将图像 gray 的像素进行取反,得到负片效果。
  5. 对步骤 4 获得的图像进行高斯滤波处理。这里高斯滤波处理次数不同,最后的效果也不同。处理得到图像 guassian。
  6. 图像淡化生成素描图像 sketch。

处理的图像效果

图1 原图

图2 素描算法显示

源码

///////////////////////////////////////////////////
// 程序名称:图像处理——素描算法
// 编译环境:Mictosoft Visual Studio 2013, EasyX_20200315(beta)
// 作  者:luoyh <2864292458@qq.com>
// 最后修改:2021-11-2
//

#include<graphics.h>
#include<conio.h>
#include<stdio.h>
#include<cmath>

const double PI = acos(-1.0);
const double delta = 1.5;
const int pic_Dis = 3;
double P[pic_Dis][pic_Dis];
void GetP();						// 获取高斯滤波算子

class Algorithm
{
public:
	Algorithm(IMAGE *img, int width, int heigth);
	void Histogram();				// 类似直方图均衡化
	void Gray();					// 灰度处理
	void Film();					// 负片处理
	void Blur();					// 高斯模糊化处理
	void Sketch();					// 素描算法

private:
	IMAGE *pimg;
	int WIDTH;
	int HEIGHT;
};

Algorithm::Algorithm(IMAGE *img, int width, int height)
{
	pimg = img;
	WIDTH = width;
	HEIGHT = height;
};

void Algorithm::Film()
{
	DWORD* pMem = GetImageBuffer(pimg);
	for (int i = pimg->getwidth() * pimg->getheight() - 1; i >= 0; i--)
		pMem[i] = (~pMem[i]) & 0x00FFFFFF;
}

void Algorithm::Sketch()		// 素描
{
	IMAGE *gray, *guassian;
	Histogram();				// 直方图
	Gray();						// 灰度处理
	gray = new IMAGE();
	guassian = new IMAGE();
	*gray = *pimg;
	Film();						//底片处理

	// 这里需要考虑的是模糊化处理的次数
	for (int i = 0; i < 10; i++)
	{
		Blur();  //模糊算法
	}
	*guassian = *pimg;
	*pimg = *gray;
	DWORD*	pMem = GetImageBuffer(pimg);
	DWORD* Gray = GetImageBuffer(gray);
	DWORD* Guassian = GetImageBuffer(guassian);

	int R, G, B;
	int RR, GG, BB;
	for (int i = WIDTH * HEIGHT - 1; i >= 0; i--)
	{
		R = GetRValue(Gray[i]);
		B = GetBValue(Gray[i]);
		G = GetGValue(Gray[i]);
		RR = GetRValue(Guassian[i]);
		BB = GetBValue(Guassian[i]);
		GG = GetGValue(Guassian[i]);
		int r = min(R + (R*RR) / (255 - RR), 255);
		int g = min(G + (G*GG) / (255 - GG), 255);
		int b = min(B + (B*BB) / (255 - BB), 255);
		pMem[i] = RGB(r, g, b);
	}
	delete (gray);
	delete(guassian);
}


void Algorithm::Histogram()		// 类似直方图
{
	DWORD*	p_data = GetImageBuffer(pimg);
	int height = HEIGHT;
	int wide = WIDTH;
	int pr[256], pg[256], pb[256];
	int size = HEIGHT * WIDTH;
	for (int p = 0; p <= 255; p++)
	{
		int nr = 0, ng = 0, nb = 0;
		for (int j = 0; j < height; j++)
		{
			for (int i = 0; i < wide; i++)
			{
				if (p == GetRValue(p_data[j * wide + i]))
				{
					nr++;
				}
				if (p == GetGValue(p_data[j * wide + i]))
				{
					ng++;
				}
				if (p == GetBValue(p_data[j * wide + i]))
				{
					nb++;
				}
			}
		}
		pr[p] = nr;
		pg[p] = ng;
		pb[p] = nb;
	}
	double sr[256] = { 0 }, sg[256] = { 0 }, sb[256] = { 0 };
	double aver = 0;
	double aveg = 0;
	double aveb = 0;
	bool T = true;
	bool K = true;
	bool P = true;
	int numr, numg, numb;
	for (int i = 0; i <= 255; i++)
	{
		if (T)
		{
			aver += pr[i] / double(size);
			sr[i] = aver;
			if (aver == 1)
			{
				numr = i;
				T = false;
			}
		}
		if (K)
		{
			aveg += pg[i] / double(size);
			sg[i] = aveg;
			if (aveg == 1)
			{
				numg = i;
				K = false;
			}
		}
		if (P)
		{
			aveb += pb[i] / double(size);
			sb[i] = aveb;
			if (aveb == 1)
			{
				numb = i;
				P = false;
			}
		}
	}
	int ssr[256] = { 0 }, ssg[256] = { 0 }, ssb[256] = { 0 };
	int rx = 0, gx = 0, bx = 0;
	for (int i = 0; i <= 255; i++)
	{
		if (i == 0)
		{
			ssr[i] = 0;
			ssg[i] = 0;
			ssb[i] = 0;
		}
		ssr[i] = (int)(255 * sr[i]);
		ssg[i] = (int)(255 * sg[i]);
		ssb[i] = (int)(255 * sb[i]);
	}

	int r = 0, g = 0, b = 0;
	double myr = 0, myg = 0, myb = 0;
	for (int j = 0; j < height; j++)
	{
		for (int i = 0; i < wide; i++)
		{
			myr = ssr[GetRValue(p_data[j * wide + i])];
			myg = ssg[GetGValue(p_data[j * wide + i])];
			myb = ssb[GetBValue(p_data[j * wide + i])];
			p_data[j * wide + i] = RGB(myr, myg, myb);
		}
	}
};

void Algorithm::Gray()
{
	DWORD *p = GetImageBuffer(pimg);
	COLORREF c;
	for (int i = pimg->getwidth() * pimg->getheight() - 1; i >= 0; i--)
	{
		c = BGR(p[i]);
		c = (GetRValue(c) * 299 + GetGValue(c) * 587 + GetBValue(c) * 114 + 500) / 1000;
		p[i] = RGB(c, c, c);		// 由于是灰度值,无需再执行 BGR 转换
	}
}

void Algorithm::Blur()
{
	GetP();
	BYTE *R;
	BYTE *G;
	BYTE *B;
	int size = HEIGHT * WIDTH;
	R = (BYTE*)calloc(size,sizeof(BYTE));
	G = (BYTE*)calloc(size, sizeof(BYTE));
	B = (BYTE*)calloc(size, sizeof(BYTE));

	DWORD* pMem = GetImageBuffer(pimg);

	for (int i = 0; i < size; i++)
	{
		R[i] = GetRValue(pMem[i]);
		G[i] = GetGValue(pMem[i]);
		B[i] = GetBValue(pMem[i]);
	}

	for (int i = WIDTH; i < WIDTH * (HEIGHT - 1); i++)
	{
		if (i % 500 != 0 || (i + 1) % 500 != 0)
		{
			pMem[i] = RGB(R[i - (WIDTH + 1)] * P[0][0] + R[i - WIDTH] * P[0][1] + R[i - (WIDTH - 1)] * P[0][2] + R[i - 1] * P[1][0] + R[i] * P[1][1] + R[i + 1] * P[1][2] + R[i + (WIDTH - 1)] * P[2][0] + R[i + WIDTH] * P[2][1] + R[i + (WIDTH + 1)] * P[2][2],
				G[i - (WIDTH + 1)] * P[0][0] + G[i - WIDTH] * P[0][1] + G[i - (WIDTH - 1)] * P[0][2] + G[i - 1] * P[1][0] + G[i] * P[1][1] + G[i + 1] * P[1][2] + G[i + (WIDTH - 1)] * P[2][0] + G[i + WIDTH] * P[2][1] + G[i + (WIDTH + 1)] * P[2][2],
				B[i - (WIDTH + 1)] * P[0][0] + B[i - WIDTH] * P[0][1] + B[i - (WIDTH - 1)] * P[0][2] + B[i - 1] * P[1][0] + B[i] * P[1][1] + B[i + 1] * P[1][2] + B[i + (WIDTH - 1)] * P[2][0] + B[i + WIDTH] * P[2][1] + B[i + (WIDTH + 1)] * P[2][2]);
		}
	}
	free(R);
	free(G);
	free(B);
	R = G = B = NULL;
}

int main()
{
	int	width = 0;
	int	height = 0;
	IMAGE g_img;
	loadimage(&g_img, _T("src.jpg"));	// 加载图片
	width = g_img.getwidth();
	height = g_img.getheight();
	initgraph(width, height);
	putimage(0, 0, &g_img);
	_getch();
	Algorithm myA(&g_img, width, height);
	myA.Sketch();
	putimage(0, 0, &g_img);
	_getch();
	return 0;
}

void GetP()
{
	for (int i = 0; i < pic_Dis; i++)
	{
		for (int j = 0; j < pic_Dis; j++)
		{
			int d = (i - 1) * (i - 1) + (j - 1) * (j - 1);
			double x = 1 / (2 * PI * delta);
			P[i][j] = x * exp(-d / (2 * delta));
		}
	}

	double pn = 0;

	for (int i = 0; i < pic_Dis; i++)
	{
		for (int j = 0; j < pic_Dis; j++)
		{
			pn += P[i][j];
		}
	}

	for (int i = 0; i < pic_Dis; i++)
	{
		for (int j = 0; j < pic_Dis; j++)
		{
			P[i][j] = P[i][j] / pn;
		}
	}
}
分享到

评论 (4) -

  • 楼主你好,我想问一下素描的流程是不是这样的,A原图(灰度)——>B(负片)——>C(高斯模糊)——>D,E最终 = MIN(B+(B*D)/(255-D),255)。我按这个流程来素描的线条没有楼主这张这么黑,照片整体发亮,黑白对比感觉不够明显。
    • 关于这个情况,我是先将图片进行了一个类似直方图均衡化得处理,然后按照你说的步骤处理。类似直方图均衡化可以使得图片轮廓更加明显。影响处理结果的还有模糊化处理的次数,每张图片的最佳处理次数需要自己尝试。
      • 确实,写了类似直方图均衡化的函数忘了调用,加上以后确实就黑了不少。另外想问一下楼主,在高斯模糊的时候不是有一个标准差嘛,这个怎么取啊,我是像素点为中心点5*5模糊的。谢谢楼主。
        • 我以前用的是均值滤波处理 3 * 3 的,但是均值滤波有个弊端,会使图像里产生一些小的颜色块。高斯滤波可以避免一些类似问题,关于它的标准差,你取 1.5 也行,0.5 也可以,多试试就知道了。还有算子的大小,用 3 * 3 的就够了,因为图像是经过多次高斯滤波,用 5 * 5 的反而更麻烦。

添加评论