妙妙小豆子

胆子大一点,想开一点。长风破浪会有时,直挂云帆济沧海

车牌定位及分割 金牌收录

程序简介

车牌定位及分割,涉及到转换灰度,高斯平滑,中值滤波,边缘检测,形态学滤波,轮廓查找。对于原作中存在不能识别某些环境下的车牌;车尾部有一串拼音(有的车有),很容易框住好几个等问题,进行了改进。

原作中的具体步骤:
1、将采集到的彩色车牌图像转换成灰度图。
2、灰度化的图像利用高斯平滑处理后,再对其进行中值滤波。
3、使用 Sobel 算子对图像进行边缘检测。
4、对二值化的图像进行腐蚀,膨胀,开运算,闭运算的形态学组合变换。
5、对形态学变换后的图像进行轮廓查找,根据车牌的长宽比提取车牌。

如果能用彩色车牌进行分析会更好。
他代码翻译到我代码中的中值滤波的核边长是 2。
高斯平滑和中值滤波会使边缘检测的结果不理想(尤其是后者),如果拍到砖面,识别结果不好,程序演示中直接跳过了。
他的边缘检测是仅进行纵向检测,所以识别旋转倾斜后的车牌会出现问题。
他形态学滤波的步骤是膨胀一次,腐蚀一次,膨胀三次,程序演示中删掉了最后两次膨胀(因为删除了平滑和滤波,边缘检测更符合实际尺寸)。
我轮廓查找算法用的是消消乐的判定算法,和他用的轮廓查找算法不一样,所以结果不完全一致。
他提取车牌的方式是面积和长宽比,我只用了面积。用长宽比也就彻底无法识别旋转倾斜后的车牌了。

单步操作可以运行后按各个按钮尝试即可,主要影响结果的是形态学滤波的操作顺序和它的核大小,轮廓检测的面积阈值。
完整操作直接改代码中完整进程函数:completeprocessing(),运行后按完整进程按钮即可。
识别旋转九十度的图片,需要改边缘检测的检测模式为横向,形态学滤波内核的长宽对换。

更新:

  1. 加载图片按钮更新,调用系统自带的 api,显示一个“打开文件”的对话框。并添加到参考资料里了。
  2. 将测试图片打包上传,放到代码后面了。

程序执行效果

完整源代码

////////////////////////////////////////
// 程序:车牌定位及分割
// 作者:Gary
// 编译环境:Visual C++ 2010,EasyX_20211109
// 编写日期:2022-4-29

#include <graphics.h>
#include <string>
#include <ShlObj.h>

static HWND hOut;						// 画布

// 图像处理
int img_m[1000][1000];
int img_b[1000][1000];

// 定义一个结构体,按钮
struct Node1
{
	int posx1, posy1, posx2, posy2;		// 坐标
	LPCTSTR text;						// 文字
};

// 定义一个类
class Gary
{
public:
	void carry();				// 主进程
	void initialization();		// 初始化
	void move();				// 窗口主视角
	void draw();				// 绘制函数
	void completeprocessing();	// 完整处理函数	

	IMAGE* gray(IMAGE* img_input);													// 灰度化
	IMAGE* gaussian_smoothing_filtering(IMAGE* img_input);							// 高斯滤波
	IMAGE* median_filtering(IMAGE* img_input, int num_range);						// 中值滤波
	IMAGE* edge_detection(IMAGE* img_input, int num_mode, int num_threshold);		// 边缘检测
	IMAGE* morphological_filtering(IMAGE* img_input, int num_mode, int a, int b);	// 形态学滤波
	IMAGE* contours_finding(IMAGE* img_input, int num_threshold);					// 轮廓查找

	int num_button;			// 按钮数量
	int num_length;			// 图片长度
	int num_height;			// 图片宽度
	int exit_carry;			// 进程控制
	int exit_move;			// 进程控制
	IMAGE* img_output;		// 输出图像
	IMAGE* img_temporary;	// 临时图像
	TCHAR address_load[30];	// 加载地址
	TCHAR address_save[30];	// 保存地址
	Node1 boxm[30];			// 按钮
};


// 形态学滤波,输入图像,滤波模式,卷积核长度,卷积核宽度
IMAGE* Gary::morphological_filtering(IMAGE* img_input, int num_mode, int a, int b)
{
	// 加载输入图像
	putimage(0, 0, img_input);
	// 处理
	TCHAR s[15];
	int i, j, k;
	int k1, k2;
	// 存储
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			img_b[i][j] = getpixel(i, j) / 256 / 256;
		}
	}
	// 图遍历
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			k = 0;
			// 对白点进行腐蚀操作
			if(img_b[i][j] > 200 && num_mode == 0)
			{
				k = 1;
			}
			// 对黑点进行膨胀操作
			else if(img_b[i][j] < 10 && num_mode == 1)
			{
				k = 1;
			}
			// 核遍历
			for(k1 = i - a / 2; k1 < i + a / 2; k1++)
			{
				for(k2 = j - b / 2; k2 < j + b / 2; k2++)
				{
					if(0 <= k1 && k1 < num_length && 0 <= k2 && k2 < num_height)
					{
						// 核内有零则为零,实现腐蚀
						if(img_b[k1][k2] < 10 && num_mode == 0)
						{
							k = 0;
						}
						// 核内有一则为一,实现膨胀
						else if(img_b[k1][k2] > 200 && num_mode == 1)
						{
							k = 0;
						}
					}
					if(k == 0)
					{
						break;
					}
				}
				if(k == 0)
				{
					break;
				}
			}
			// 二值化
			img_m[i][j] = ( num_mode == 0 ? ( ( k == 0 ? 0 : 255 ) ) : ( ( k == 0 ? 255 : 0 ) ) );
		}
		// 延迟,优化空间,避免太快,更好做演示
		// Sleep(1);
		// 进度显示
		_stprintf_s(s, _T("%0.1d"), i * 100 / num_length);
		outtextxy(boxm[6].posx1 + ( boxm[6].posx2 - boxm[6].posx1 ) / 2 - textwidth(boxm[6].text) / 2, boxm[6].posy1 + 30, s);
	}
	draw();

	// 新建输出图像
	img_output = new IMAGE(num_length, num_height);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(BLACK);
	cleardevice();
	// 绘制
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			putpixel(i, j, RGB(img_m[i][j], img_m[i][j], img_m[i][j]));
		}
	}
	SetWorkingImage();
	// 绘制
	putimage(0, 0, img_output);
	// 返回输出图像
	return img_output;
}

// 边缘检测,输入图像,检测模式,阈值
IMAGE* Gary::edge_detection(IMAGE* img_input, int num_mode, int num_threshold)
{
	// 加载输入图像
	putimage(0, 0, img_input);
	// 处理
	int i, j, k, k1, k2;
	TCHAR s[15];
	// 存储
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			img_b[i][j] = getpixel(i, j) / 256 / 256;
		}
	}
	// 处理
	for(i = 1; i < num_length - 1; i++)
	{
		for(j = 1; j < num_height - 1; j++)
		{
			k1 = 0; k2 = 0;
			// 横向
			k1 = ( -1 * img_b[i - 1][j - 1] +
				-2 * img_b[i][j - 1] +
				-1 * img_b[i + 1][j - 1] +
				0 * img_b[i - 1][j] +
				0 * img_b[i][j] +
				0 * img_b[i + 1][j] +
				1 * img_b[i - 1][j + 1] +
				2 * img_b[i][j + 1] +
				1 * img_b[i + 1][j + 1] );
			// 纵向
			k2 = ( -1 * img_b[i - 1][j - 1] +
				-2 * img_b[i - 1][j] +
				-1 * img_b[i - 1][j + 1] +
				0 * img_b[i][j - 1] +
				0 * img_b[i][j] +
				0 * img_b[i][j + 1] +
				1 * img_b[i + 1][j - 1] +
				2 * img_b[i + 1][j] +
				1 * img_b[i + 1][j + 1] );

			k1 = abs(k1) / 2;
			k2 = abs(k2) / 2;

			switch(num_mode)
			{
			case 0:k = k1; break;
			case 1:k = k2; break;
			case 2:k = ( k1 + k2 ) / 2; break;
			default:break;
			}
			// 二值化,与阈值比较
			img_m[i][j] = k > num_threshold ? 255 : 0;
		}
		// 延迟,优化空间,避免太快,更好做演示
		// Sleep(1);
		// 进度显示
		_stprintf_s(s, _T("%0.1d"), i * 100 / num_length);
		outtextxy(boxm[5].posx1 + ( boxm[5].posx2 - boxm[5].posx1 ) / 2 - textwidth(boxm[5].text) / 2, boxm[5].posy1 + 30, s);
	}
	draw();

	// 新建输出图像
	img_output = new IMAGE(num_length, num_height);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(WHITE);
	cleardevice();
	// 绘制
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			putpixel(i, j, RGB(img_m[i][j], img_m[i][j], img_m[i][j]));
		}
	}
	SetWorkingImage();
	// 绘制
	putimage(0, 0, img_output);
	// 返回输出图像
	return img_output;
}

// 中值滤波函数,卷积核尺度
IMAGE* Gary::median_filtering(IMAGE* img_input, int num_range)
{
	// 加载输入图像
	putimage(0, 0, img_input);
	// 处理
	TCHAR s[15];
	int i, j, k, t;
	int T = num_range;
	int a, b;
	int g[100];
	// 存储
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			img_b[i][j] = getpixel(i, j) / 256 / 256;
		}
	}
	// 滤波
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			t = 0;
			// 取 (2*T+1)^2 格的像素值
			for(a = -T; a <= T; a++)
			{
				for(b = -T; b <= T; b++)
				{
					if(0 <= i + a && i + a < num_length && 0 <= j + b && j + b < num_height)
					{
						g[t] = getpixel(i + a, j + b) / 256 / 256;
						t++;
					}
				}
			}
			// 冒泡排序
			for(a = 0; a < t - 1; a++)
			{
				for(b = t - 1; b > a; b--)
				{
					if(g[b] < g[b - 1])
					{
						k = g[b];
						g[b] = g[b - 1];
						g[b - 1] = k;
					}
				}
			}
			t = t / 2;
			// 取中值作为新像素值并绘制
			img_m[i][j] = g[t];
		}
		// 进度显示
		_stprintf_s(s, _T("%0.1d"), i * 100 / num_length);
		outtextxy(boxm[4].posx1 + ( boxm[4].posx2 - boxm[4].posx1 ) / 2 - textwidth(boxm[4].text) / 2, boxm[4].posy1 + 30, s);
	}
	draw();

	// 新建输出图像
	img_output = new IMAGE(num_length, num_height);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(BLACK);
	cleardevice();
	// 绘制
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			putpixel(i, j, RGB(img_m[i][j], img_m[i][j], img_m[i][j]));
		}
	}
	SetWorkingImage();
	// 绘制
	putimage(0, 0, img_output);
	// 返回输出图像
	return img_output;
}

// 高斯滤波函数
IMAGE* Gary::gaussian_smoothing_filtering(IMAGE* img_input)
{
	// 加载输入图像
	putimage(0, 0, img_input);
	// 处理
	TCHAR s[15];
	int i, j, k;
	// 存储
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			img_b[i][j] = getpixel(i, j) / 256 / 256;
		}
	}
	// 滤波
	for(i = 1; i < num_length - 1; i++)
	{
		for(j = 1; j < num_height - 1; j++)
		{
			k = ( 1 * img_b[i - 1][j - 1] +
				2 * img_b[i][j - 1] +
				1 * img_b[i + 1][j - 1] +
				2 * img_b[i - 1][j] +
				4 * img_b[i][j] +
				2 * img_b[i + 1][j] +
				1 * img_b[i - 1][j + 1] +
				2 * img_b[i][j + 1] +
				1 * img_b[i + 1][j + 1] )
				/ 16;
			img_m[i][j] = k;
		}
		// 延迟,优化空间,避免太快,更好做演示
		// Sleep(1);
		// 进度显示
		_stprintf_s(s, _T("%0.1d"), i * 100 / num_length);
		outtextxy(boxm[3].posx1 + ( boxm[3].posx2 - boxm[3].posx1 ) / 2 - textwidth(boxm[3].text) / 2, boxm[3].posy1 + 30, s);
	}
	draw();

	// 新建输出图像
	img_output = new IMAGE(num_length, num_height);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(BLACK);
	cleardevice();
	// 绘制
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			putpixel(i, j, RGB(img_m[i][j], img_m[i][j], img_m[i][j]));
		}
	}
	SetWorkingImage();
	// 显示
	putimage(0, 0, img_output);
	// 返回输出图像
	return img_output;
}

// 灰度化函数
IMAGE* Gary::gray(IMAGE* img_input)
{
	// 新建输出图像
	img_output = new IMAGE(num_length, num_height);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(WHITE);
	cleardevice();
	// 加载输入图像
	putimage(0, 0, img_input);
	// 处理
	int i, j;
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			putpixel(i, j, RGBtoGRAY(getpixel(i, j)));
		}
	}
	SetWorkingImage();
	// 显示
	putimage(0, 0, img_output);
	// 返回输出图像
	return img_output;
}

// 轮廓查找函数,阈值
IMAGE* Gary::contours_finding(IMAGE* img_input, int num_threshold)
{
	// 加载输入图像
	putimage(0, 0, img_input);
	// 处理
	TCHAR s[15];
	int i, j, k, flag;
	struct Node1
	{
		int num_box;
		int posx, posy;
	};
	Node1 box[4];
	struct Node2
	{
		int num_box;
	};
	Node2 eat[10000];
	POINT pts[4];
	// 存储
	for(i = 0; i < num_length; i++)
	{
		for(j = 0; j < num_height; j++)
		{
			img_b[i][j] = getpixel(i, j) / 256 / 256;
			img_b[i][j] = ( img_b[i][j] > 200 ? 1 : 0 );
		}
	}

	// 新建输出图像
	img_output = new IMAGE(num_length, num_height);
	// 初始化处理区域
	SetWorkingImage(img_output);
	setbkcolor(WHITE);
	cleardevice();
	SetWorkingImage();
	// 寻找
	// 遍历	
	for(j = 1; j < num_height - 1; j++)
	{
		for(i = 1; i < num_length - 1; i++)
		{
			// 有亮点,通过连通域寻找连续区域
			if(img_b[i][j] == 1)
			{
				// 改点,避免重复
				img_b[i][j] = 0;
				// 范围参数初始化,左上右下点
				box[0].num_box = i + j * num_length;
				box[1].num_box = i + j * num_length;
				box[2].num_box = i + j * num_length;
				box[3].num_box = i + j * num_length;
				// 循环参数初始化
				k = 0;
				flag = 1;
				// 三消类算法
				eat[0].num_box = i + j * num_height;
				while(k != flag)
				{
					// 上下左右
					// 上
					if(img_b[eat[k].num_box % num_length][eat[k].num_box / num_length - 1] == 1)
					{
						img_b[eat[k].num_box % num_length][eat[k].num_box / num_length - 1] = 0;
						eat[flag].num_box = eat[k].num_box - num_length;
						flag++;
						if(flag == 10000)flag = 0;
					}
					// 下
					if(img_b[eat[k].num_box % num_length][eat[k].num_box / num_length + 1] == 1)
					{
						img_b[eat[k].num_box % num_length][eat[k].num_box / num_length + 1] = 0;
						eat[flag].num_box = eat[k].num_box + num_length;
						flag++;
						if(flag == 10000)flag = 0;
					}
					// 左
					if(img_b[eat[k].num_box % num_length - 1][eat[k].num_box / num_length] == 1)
					{
						img_b[eat[k].num_box % num_length - 1][eat[k].num_box / num_length] = 0;
						eat[flag].num_box = eat[k].num_box - 1;
						flag++;
						if(flag == 10000)flag = 0;
					}
					// 右
					if(img_b[eat[k].num_box % num_length + 1][eat[k].num_box / num_length] == 1)
					{
						img_b[eat[k].num_box % num_length + 1][eat[k].num_box / num_length] = 0;
						eat[flag].num_box = eat[k].num_box + 1;
						flag++;
						if(flag == 10000)flag = 0;
					}
					// 改范围
					if(eat[k].num_box > box[1].num_box)
					{
						box[1].num_box = eat[k].num_box;
					}
					if(eat[k].num_box % num_length < box[2].num_box % num_length)
					{
						box[2].num_box = eat[k].num_box;
					}
					if(eat[k].num_box % num_length > box[3].num_box % num_length)
					{
						box[3].num_box = eat[k].num_box;
					}
					k++;
					if(k == 10000)k = 0;
				}
				pts[0].x = box[2].num_box % num_length;
				pts[0].y = box[0].num_box / num_length;

				pts[2].x = box[3].num_box % num_length;
				pts[2].y = box[1].num_box / num_length;

				pts[1].x = pts[0].x;
				pts[1].y = pts[2].y;

				pts[3].x = pts[2].x;
				pts[3].y = pts[0].y;
				// 算面积,与阈值比较
				k = abs(pts[0].x - pts[2].x) * abs(pts[0].y - pts[2].y);
				if(k >= num_threshold)
				{
					SetWorkingImage(img_output);
					// 参数
					setfillcolor(BLACK);
					setlinestyle(PS_SOLID, 3);
					setlinecolor(GREEN);
					// 绘制
					fillpolygon(pts, 4);
					setlinestyle(PS_SOLID, 1);
					SetWorkingImage();
				}
			}
		}
		// 延迟,优化空间,避免太快,更好做演示
		// Sleep(1);
		// 进度显示
		_stprintf_s(s, _T("%0.1d"), j * 100 / num_length);
		outtextxy(boxm[7].posx1 + ( boxm[7].posx2 - boxm[7].posx1 ) / 2 - textwidth(boxm[7].text) / 2, boxm[7].posy1 + 30, s);
	}
	draw();

	// 加载原图
	SetWorkingImage(img_output);
	loadimage(img_temporary, address_load, num_length, num_height);
	putimage(0, 0, img_temporary, SRCPAINT);
	SetWorkingImage();
	// 绘制
	putimage(0, 0, img_output);
	// 返回
	return img_output;
}

// 完整进程函数
void Gary::completeprocessing()
{
	// 灰度化
	img_temporary = gray(img_temporary);
	// 高斯滤波
	// img_temporary = gaussian_smoothing_filtering(img_temporary);
	// 中值滤波
	// img_temporary = median_filtering(img_temporary, 1);
	// 边缘检测
	img_temporary = edge_detection(img_temporary, 1, 100);
	// 形态学滤波
	img_temporary = morphological_filtering(img_temporary, 1, 10, 2);
	img_temporary = morphological_filtering(img_temporary, 0, 8, 6);
	img_temporary = morphological_filtering(img_temporary, 1, 10, 2);
	// img_temporary = morphological_filtering(img_temporary, 1, 10, 2);
	// img_temporary = morphological_filtering(img_temporary, 1, 10, 2);
	// 轮廓检测
	img_temporary = contours_finding(img_temporary, 4000);
}

// 主进程函数
void Gary::carry()
{
	// 窗口定义
	hOut = initgraph(701, 401);
	SetWindowText(hOut, _T("车牌定位及分割"));
	// 背景绘制
	setbkcolor(WHITE);
	cleardevice();
	// 进程控制
	exit_carry = 0;
	while(exit_carry == 0)
	{
		initialization();
		move();
	}
	closegraph();
}

// 绘制函数
void Gary::draw()
{
	TCHAR s[15];
	int i, j;

	// 主界面
	setlinecolor(BLACK);
	setfillcolor(WHITE);
	setlinestyle(PS_SOLID, 1);
	fillrectangle(401, 0, 700, 400);

	// 根据按钮数量绘制	
	settextcolor(BLACK);
	for(i = 0; i < num_button; i++)
	{
		// 边框
		fillrectangle(boxm[i].posx1, boxm[i].posy1, boxm[i].posx2, boxm[i].posy2);
		// 文字
		outtextxy(boxm[i].posx1 + ( boxm[i].posx2 - boxm[i].posx1 ) / 2 - textwidth(boxm[i].text) / 2, boxm[i].posy1 + 10, boxm[i].text);
	}

	// 设置参数
	setbkcolor(WHITE);
	settextcolor(BLACK);
	setlinecolor(BLACK);

	// 变量绘制
	j = 30;
	// 图片长度:num_length
	i = 0;
	_stprintf_s(s, _T("%0.1d"), num_length);
	outtextxy(boxm[i].posx1 + ( boxm[i].posx2 - boxm[i].posx1 ) / 2 - textwidth(boxm[i].text) / 2, boxm[i].posy1 + j, s);

	// 图片宽度:num_height
	i = 1;
	_stprintf_s(s, _T("%0.1d"), num_height);
	outtextxy(boxm[i].posx1 + ( boxm[i].posx2 - boxm[i].posx1 ) / 2 - textwidth(boxm[i].text) / 2, boxm[i].posy1 + j, s);

	// 加载地址
	i = 9;
	outtextxy(boxm[i].posx1 + ( boxm[i].posx2 - boxm[i].posx1 ) / 2 - textwidth(boxm[i].text) / 2, boxm[i].posy1 + j, address_load);

	// 保存地址
	i = 10;
	outtextxy(boxm[i].posx1 + ( boxm[i].posx2 - boxm[i].posx1 ) / 2 - textwidth(boxm[i].text) / 2, boxm[i].posy1 + j, address_save);
}

// 初始化函数
void Gary::initialization()
{
	int i;
	// 参数初始化
	num_length = 400;
	num_height = 400;

	TCHAR s1[30] = _T("t0.png");
	for(i = 0; i < 30; i++) { address_load[i] = s1[i]; }

	TCHAR s2[30] = _T("image.png");
	for(i = 0; i < 30; i++) { address_save[i] = s2[i]; }

	// 按钮的初始化
	num_button = 12;

	// 坐标
	for(i = 0; i < num_button; i++)
	{
		boxm[i].posx1 = 405 + 100 * ( i % 3 );
		boxm[i].posy1 = 15 + 100 * ( i / 3 );
		boxm[i].posx2 = 495 + 100 * ( i % 3 );
		boxm[i].posy2 = 85 + 100 * ( i / 3 );
	}

	// 内容
	boxm[0].text = _T("图像长度");		boxm[1].text = _T("图像宽度");		boxm[2].text = _T("灰度化");
	boxm[3].text = _T("高斯滤波");		boxm[4].text = _T("中值滤波");		boxm[5].text = _T("边缘检测");
	boxm[6].text = _T("形态学滤波");	boxm[7].text = _T("轮廓检测");		boxm[8].text = _T("完整处理");
	boxm[9].text = _T("加载图片");		boxm[10].text = _T("保存图片");	boxm[11].text = _T("退出");

	// 第一次加载
	img_temporary = new IMAGE(num_length, num_height);
	loadimage(img_temporary, address_load, num_length, num_height);
	// 图片显示
	putimage(0, 0, img_temporary);

	// 第一次绘制
	draw();
}


// 窗口主视角函数,获取用户操作
void Gary::move()
{
	TCHAR ss[15];
	// 鼠标定义
	ExMessage m;
	int i;
	int a, b;
	exit_move = 0;
	while(exit_move == 0)
	{
		// 鼠标信息
		if(peekmessage(&m, EM_MOUSE | EM_KEY))
		{
			// 左键单击判断
			if(m.message == WM_LBUTTONDOWN)
			{
				// 判断是否点击了有效按钮
				for(i = 0; i < num_button; i++)
				{
					if(m.x > boxm[i].posx1 && m.y > boxm[i].posy1 && m.x < boxm[i].posx2 && m.y < boxm[i].posy2)
					{
						break;
					}
				}

				// 点击矩形按钮
				switch(i)
				{
					// 图像长度:num_length
				case 0:
				{
					// 输入
					InputBox(ss, 10, _T("输入图像长度(20 ~ 400)"));
					_stscanf_s(ss, _T("%d"), &i);
					if(i >= 20 && i <= 400)
					{
						// 边长
						num_length = i;
					}
					else { MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK); }
					break;
				}
				// 图像宽度:num_height
				case 1:
				{
					// 输入
					InputBox(ss, 10, _T("输入图像宽度(20 ~ 400)"));
					_stscanf_s(ss, _T("%d"), &i);
					if(i >= 20 && i <= 400)
					{
						// 边长
						num_height = i;
					}
					else { MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK); }
					break;
				}
				// 灰度化
				case 2:
				{
					img_temporary = gray(img_temporary);
					break;
				}
				// 高斯滤波
				case 3:
				{
					img_temporary = gaussian_smoothing_filtering(img_temporary);
					break;
				}
				// 中值滤波
				case 4:
				{
					// 输入
					InputBox(ss, 10, _T("输入卷积核宽度(1 ~ 3)"));
					_stscanf_s(ss, _T("%d"), &i);
					if(i >= 1 && i <= 3)
					{
						img_temporary = median_filtering(img_temporary, i);
					}
					else { MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK); }
					break;
				}
				// 边缘检测
				case 5:
				{
					// 输入
					InputBox(ss, 10, _T("输入检测模式 0.横向 1.纵向 2.全检测"));
					_stscanf_s(ss, _T("%d"), &i);
					if(i >= 0 && i <= 2)
					{
						InputBox(ss, 10, _T("输入阈值(0 ~ 255)"));
						_stscanf_s(ss, _T("%d"), &a);
						if(a >= 0 && a <= 255)
						{
							img_temporary = edge_detection(img_temporary, i, a);
						}
						else { MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK); }
					}
					else { MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK); }
					break;
				}
				// 形态学滤波
				case 6:
				{
					// 输入
					InputBox(ss, 10, _T("输入滤波模式 0.腐蚀 1.膨胀"));
					_stscanf_s(ss, _T("%d"), &i);
					if(i >= 0 && i <= 1)
					{
						InputBox(ss, 10, _T("输入长度(2 ~ 50)"));
						_stscanf_s(ss, _T("%d"), &a);
						if(a >= 2 && a <= 50)
						{
							InputBox(ss, 10, _T("输入宽度(2 ~ 50)"));
							_stscanf_s(ss, _T("%d"), &b);
							if(b >= 2 && b <= 50)
							{
								img_temporary = morphological_filtering(img_temporary, i, a, b);
							}
							else { MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK); }
						}
						else { MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK); }
					}
					else { MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK); }
					break;
				}
				// 轮廓检测
				case 7:
				{
					// 输入
					InputBox(ss, 10, _T("输入面积阈值(1 ~ 100000)"));
					_stscanf_s(ss, _T("%d"), &i);
					if(i >= 1 && i <= 100000)
					{
						img_temporary = contours_finding(img_temporary, i);
					}
					else { MessageBox(hOut, _T("输入错误,不在范围内"), _T("来自小豆子的提醒"), MB_OK); }
					break;
				}
				// 完整处理
				case 8:
				{
					completeprocessing();
					break;
				}
				// 加载图片
				case 9:
				{
					i = MessageBox(hOut, _T("是否默认地址"), _T("来自小豆子的提醒"), MB_YESNO);
					if(i == 7)
					{
						// 打开文件对话框
						OPENFILENAME ofn;
						// 保存文件完整路径
						TCHAR szFileName[MAX_PATH] = { 0 };
						// 设置过滤条件
						TCHAR szFilter[] = TEXT("PNG Files (*.png)\0*.png\0") \
							TEXT("All Files (*.*)\0*.*\0\0");
						ZeroMemory(&ofn, sizeof(ofn));
						// 保存文件完整路径
						ofn.lpstrFile = szFileName;
						ofn.nMaxFile = MAX_PATH;
						// 保存文件名
						ofn.lpstrFileTitle = address_load;
						ofn.nMaxFileTitle = MAX_PATH;
						ofn.lpstrFilter = szFilter;
						// 默认扩展名
						ofn.lpstrDefExt = _T("png");
						ofn.lpstrTitle = NULL;
						ofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST;
						ofn.lStructSize = sizeof(OPENFILENAME);
						// 拥有该对话框的窗口句柄
						ofn.hwndOwner = hOut;
						GetOpenFileName(&ofn);
					}
					img_temporary = new IMAGE(num_length, num_height);
					// 加载
					loadimage(img_temporary, address_load, num_length, num_height);
					// 主界面
					setlinecolor(BLACK);
					setfillcolor(WHITE);
					setlinestyle(PS_SOLID, 1);
					fillrectangle(0, 0, 400, 400);
					// 图片显示
					putimage(0, 0, img_temporary);
					break;
				}
				// 保存图片
				case 10:
				{
					i = MessageBox(hOut, _T("是否默认地址"), _T("来自小豆子的提醒"), MB_YESNO);
					if(i == 7)
					{
						InputBox(address_save, 10, _T("输入图片相对位置及名称"));
					}
					// 保存
					saveimage(address_save, img_temporary);
					break;
				}
				// 退出
				case 11:
				{
					exit_move = 1;
					exit_carry = 1;
					break;
				}
				default:break;
				}
				draw();
			}
		}
	}
}

// 主函数
int main(void)
{
	Gary G;
	G.carry();
	return 0;
}

所用素材

测试素材

参考资料

Python 实现车牌定位及分割

Sobel 算子的理解

计算机视觉基础-形态学滤波

评论 (2) -

  • 好像有点问题,测试素材也识别失败了
    • 非常不好意思,代码已经修改了,现在应该能正常识别了。失败的原因是完整进程中边缘检测函数用的是横向,而测试素材应该是用纵向。我想是我疏忽了,我最后一次使用是用了旋转后的图像,测试时将纵向改成了横向,才造成了这个问题。

添加评论