上学期的智能计算还有一个实验忘了总结了,下午刚刚才想起来。上一篇里面我们讨论了用模板匹配的方法进行手写数字识别,这一片介绍用贝叶斯方法进行手写数字的识别。语言依旧用的是c++,工具依旧是opencv。有关实验过程我参考的是《模式识别与智能计算  MATLAB技术实现》这本书,我用的是第三版,班里有的同学用的是第四版,我觉得在这一实验的讲解上,没有第三版讲解的清楚,公式表达也没有第三版直观明了,在给班里同学讲这个实验时候都用用的第三版的内容。其实原理上,无论哪一版都讲的比较清楚了,但是实验步骤这一块,第三版相当清楚,我们要用到的也就是这一块。关于贝叶斯公式的原理这里不在赘述,在书中或者网上都能查到,它其中蕴含了一个非常典型的数学思想,就是逆概思想。所谓逆概思想,就是P(A|Bi)P(A|Bi)P(Bi|A)P(Bi|A)揭示了两个相反方向的条件概率之间的转换关系。当一个方向的条件概率非常难求的时候,可以通过贝叶斯公式转换,转换到相反方向上求解。

它除了在智能计算里面弄够用到,在人工智能领域也有勇武之地,在人工智能领域,除了这里要用到的朴素贝叶斯,还有更加复杂的主观贝叶斯。

这里面的奥妙光看经验是感觉不出来的,必须要亲自去实验,去体会网上讲的例子,去推导公式才能理解。

那么作为一个实验,其实你不理解的很深其实也没什么关系,一样可以做好这个实验。老师的ppt写的也不是很清楚,反正我没看懂,而且有人按照ppt去做了,结果正确率不太高,我就直接按照书上做了

基本的实验步骤如下图所示:

看吧,其实有用的就上面这一点,短短一段文字,这个实验就完了,神奇吧!哈哈哈哈好了好了,其实,你乍一眼看上去,直接就想放弃了,什么乱七八糟的一堆符号?来我们一句一句的分析吧。

首先,第一步:

这一步是最简单的了,先验概率的计算方法就是,用每种事物的样品数,去除以一共有多少样品。本实验每个数字有100个样本,一共有10个数字也就是1000个样本。所以先验概率恒定为100/1000=0.1 。至此,第一步完成。

第二步:

这一步就比较难理解了。不过我们不用理解公式思想,我们只需要知道,公式让我们干什么就行了。首先要算的就是这个联合概率Pjwi,要算这个之前,还要先做一件事,就是把你的每个样品的图片分区。比如一张“5”的图片,我们需要把它分成一些子区域,比如5*510*10等等,你可以不用管它原来的像素是多少,因为opencv的函数会自动给我们插值。比如我们划分成10*10=100个子区域,那么我们就说一张图片,一个样本有100个分量。然后按照模板匹配的方法进行处理图片,即把图像进行二值化,这里的二值化不是把图片直接进行阈值分割变成像素为0或者255的图像,而是把它有像素不为0的像素值置为1,其余的像素仍为0.。也就是把图像0 1化。
这样你就拥有了每个样本100个由01组成的分量。 接下来算那个求和。那个求和求的是什么呢?

求和符号下面这个大X表示一个样本,这个样本属于某一种数字,在这个基础上,求和x(kj)j的意思是每个样本的第j个分量(第j个子区域),k的意思是,当前这种数字的第k个样本。当我要求一个联合概率的时候,已经确定了的是我要求哪一种数字和这种数字所有样本的第几个分量的联合概率。

比如说我要求数字“5”的第0个分量联合概率,也就是P0(w5),应该要把数字5100个样本每个样本的第0个(其实是第一个)分量加起来求和,然后除以数字5的样品数100。书上划分了25个子区域所以是024,我们划分了100个,所以应该是099.为什么这里要分别在分子分母上加1和加2呢,因为所有样本图像像素总有为0的,分子不加1,则后面算的类条件概率必会为0,这算出来的所有后验概率也会为0,显然这是不合适的。这里实际上是进行了一个拉普拉斯修正,有兴趣的同学可以专门去查一下这个,好好体会一下,而且有的地方用的不是连加而是连乘,本文就不赘述了,好好把实验步骤先搞清楚吧。

好了,目前为止,我们可以算出来每一种数字的每一个分量的联合概率。也就是说,我们可以形成一个10*100的矩阵,即10种数字,每个数字100个分量每个分量各自的概率,共1000个联合概率。好嘞,现在先不看中间的那个一堆,先看算类条件概率那,其实就是一个连乘对吧。即选定一种数字就能算出它的类条件概率,但是在这里有一个非常非常重要的一点不一样,就是这里有被测样本要参与进来了。好我们还是举例子,这时候来了一个被测样本,它也拥有10001序列,这时候,把这100个序列先和0100个分量的联合概率一一对应,一个盯一个懂吧,如果一个序列的值为1,那么它对应的概率不变,如果一个序列的值为0,那么它对应的概率就是要用1减去它所对应的概率,然后得到了一组由100个概率组成的新的一组概率,好,现在把他们连乘起来,这就是这个被测样本对应数字0的类条件概率,前面算新一组概率的过程就是书上步骤2中间那一堆。

按照这样的思路,把09所有的类条件概率都可以算出来。这样我们最后一步已经呼之欲出了。

看见没,最后一步算后验概率的所有需要的量其实已经算完了。首先,进来一个被测对象,分子上,先验概率,算了吧,类条件概率,上一步也算了吧。分母,09所有类条件概率,也算了吧。一除就完事了。奥不,还没完事呢。你需要吧分子上的类条件概率换9次,算出9个后验概率,要算出每种数组针对这个被测对象的后验概率。但只要这个被测对象不变,分母就不变,分子右边的类条件概率轮番上,分子左边先验概率则雷打不动,始终是0.1,换了被测对象它都不带变的,十分的与世无争,比较佛。

最后算出来9个后验概率后,概率最大那个对应的数字类别就是结果啦。完结撒花!

/*
本程序用于用贝叶斯分类器测试测试集test的数字识别率。用的数据集是从1000个全部训练集中抽取的100个数据,
*/
#include "cv.h"
#include "highgui.h"
#include<opencv2/opencv.hpp>
#include <stdio.h>
#include <stdlib.h>
#include <opencv\cv.hpp>
#include<sstream>
#include <iostream>
#include <fstream>
#include <Windows.h>
using namespace std;
using namespace cv;
//计算欧氏距离
int getDist(int test[100], int sample[100])
{

	int sum = 0;
	for (int i = 0; i < 100; i++)
	{
		sum += pow(test[i] - sample[i], 2);//这里省略开根号
	}
	return sum;
}
//对图像进行区域分割并字符串化图片区域,得到test数组,里面存放着一张图片的01序列
void getPXSum(Mat& src, int(&test)[100])
{
	int mark = 0, count = 0;//mark是划分的格子的序号,count是格子内计数像素个数
	//threshold(src, src, 100, 1, CV_THRESH_BINARY);
	while (mark < 100)//100.个格子10*10
	{
		for (int i = 0; i < 30; i++)
		{
			for (int j = 0; j < 30; j++)//一个格子里面有30*30个像素
			{
				if (src.at <uchar>(i + 30 * (mark / 10), j + 30 * (mark % 10)) > 200) /*Mat类中的at方法作用:用于获取图像矩阵某点的值或改变某点的值。对于单通道图像的使用方法:image.at<uchar>(i,j) = 255;对于RGB三通道图像的使用方法:image.at<Vec3b>(i,j)[0] =  255;image.at<Vec3b>(i,j)[1] =  255;image.at<Vec3b>(i,j)[2] = 255;*/
					count++;//mark/7是得出当前是第几行的格子
				//printf_s("%d",src.at <uchar>(i + 3 * (mark % 7), j + 3 * (mark / 7)));
			}
			//printf_s("\n");
		}
		if (count > 50) test[mark] = 1;//若格子里超过半数像素是亮的,则记这个格子为1
		else
		{
			test[mark] = 0;
		}
		//	printf_s("黑色像素占该3*3区域的%d/9\n", count);
		count = 0;
		mark++;
	}
}

//输出一张图片的字符串序列
void readNum(int(&test)[100])
{
	//ofstream codeo("E:\\code\\vs\\txtfile\\1.txt", ios::binary | ios::app);
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			//printf_s("%d", test[i * 7 + j]);//输出一张图片的字符串序列

			int buffer = test[i * 10 + j];

			//while (!codei.eof()) {

		}

		//printf_s("\n");
	}

	//printf_s("\n");

}

//统计所有列像素的总和
int getColSum(Mat src, int col)
{
	int sum = 0;
	int height = src.rows;
	int width = src.cols;
	for (int i = 0; i < height; i++)
	{
		sum = sum + src.at <uchar>(i, col);
	}
	return sum;
}
//统计所有行像素的总和
int getRowSum(Mat src, int row)
{
	int sum = 0;
	int height = src.rows;
	int width = src.cols;
	for (int i = 0; i < width; i++)
	{
		sum += src.at <uchar>(row, i);
	}
	return sum;
}
//上下切割
void cutTop(Mat& src, Mat& dstImg)
{
	int top, bottom;
	top = 0;
	bottom = src.rows;

	int i;
	for (i = 0; i < src.rows; i++)
	{
		int colValue = getRowSum(src, i);//统计所有行像素的总和
										 //cout <<i<<" th "<< colValue << endl;
		if (colValue > 0)//扫描直到行像素的总和大于0时,记下当前位置top
		{
			top = i;
			break;
		}
	}
	for (; i < src.rows; i++)
	{
		int colValue = getRowSum(src, i);//统计所有行像素的总和
										 //cout << i << " th " << colValue << endl;
		if (colValue == 0)//继续扫描直到行像素的总和等于0时,记下当前位置bottom
		{
			bottom = i;
			break;
		}
	}

	int height = bottom - top;
	Rect rect(0, top, src.cols, height);
	dstImg = src(rect).clone();
}

//左右切割
int cutLeft(Mat& src, Mat& leftImg, Mat& rightImg)
{
	int left, right;
	left = 0;
	right = src.cols;

	int i;
	for (i = 0; i < src.cols; i++)
	{
		int colValue = getColSum(src, i);//统计所有列像素的总和
										 //cout <<i<<" th "<< colValue << endl;
		if (colValue > 0)//扫描直到列像素的总和大于0时,记下当前位置left
		{
			left = i;
			break;
		}
	}
	if (left == 0)
	{
		return 1;
	}

	//继续扫描
	for (; i < src.cols; i++)
	{
		int colValue = getColSum(src, i);//统计所有列像素的总和
										 //cout << i << " th " << colValue << endl;
		if (colValue == 0)//继续扫描直到列像素的总和等于0时,记下当前位置right
		{
			right = i;
			break;
		}
	}
	int width = right - left;//分割图片的宽度则为right - left
	Rect rect(left, 0, width, src.rows);//构造一个矩形,参数分别为矩形左边顶部的X坐标、Y坐标,右边底部的X坐标、Y坐标(左上角坐标为0,0)
	leftImg = src(rect).clone();//实际上这句话进行了一个裁剪的功能

	Rect rectRight(right, 0, src.cols - right, src.rows);//分割后剩下的原图
	rightImg = src(rectRight).clone();
	cutTop(leftImg, leftImg);//上下切割
	return 0;
}

//test形参传引用,会保存数据,处理图片,并获得字符串
void readImg(Mat& src, int(&test)[100],int i)
{
	//imshow("原图", src);//显示原图片

	medianBlur(src, src, 3);//中值滤波去椒盐噪声
	//imshow("origin2", src);//显示去噪后图片

	//Mat element = getStructuringElement(MORPH_RECT, Size(2, 2));//返回指定形状和尺寸的结构元素,也就是生成一个卷积核
	//dilate(src, src, element);//膨胀
	//imshow("origin3", src);//显示膨胀后图片

	threshold(src, src, 100, 255, THRESH_BINARY);//二值化,100是阈值

	Mat rightImg, leftImg;
	cutLeft(src, leftImg, rightImg);
	char name[100];
	sprintf_s(name, "切割后图片%d",i );

	imshow(name, leftImg);//显示切割后图片

	resize(leftImg, leftImg, Size(300, 300), 0, 0, CV_INTER_LINEAR);
	//imshow("显示调整尺寸后图片", leftImg);//显示调整尺寸后图片

	stringstream ss;
	//ss << i;//此举将int类型转换为字符串类型

	//imwrite("E:\\code\\vs\\cvtest1\\minist\\moban\\" + ss.str() + ".bmp", leftImg);//streamstring在调用str()时,会返回临时的string对象。

	getPXSum(leftImg, test);//计算切割好的图片的序列,存到test里


}
//计算Pj(wi)矩阵,一个图片,属于数字i的条件下,图片第j个格子为1的概率
double Pwij(int j,int (&sample)[80][100])
{
	double sum = 0;//统计值为1的格子数
	for (int jj = 0;jj < 80;jj++)//jj是数字i的第jj个图片
	{
		//printf("%d", (sample[jj][j]));
		if (sample[jj][j] == 1)
		{
			sum=sum+1;
		}

	}
	//printf("\n%f ", sum);
	return (sum + 1) / (80 + 2);
}
//测试序列对应的,数字i所有向量的概率乘积,也就是类条件概率
double PXwi(int test[100], double(&p)[100])//第一个参数是待识别图片序列,第二个参数是当前计算类条件概率的数字类别的100个联合概率
{
	double sum = 1;
	for (int n = 0; n < 100; n++)
	{
		if (test[n] == 1)//若对应的序列值为1,则累乘这个概率
		{
			sum = sum * p[n];
		}
		else//否则累乘这个概率的反面
		{
			sum = sum * (1-p[n]);
		}
	}
	
	return sum;
}
//求后验概率
double PwiX( int i,int testX[100], double p[10][100])//第一个参数是尝试计算的数字类别,第二个参数是待测图片序列,第三个参数是p概率矩阵,调用类条件概率函数要用到
{
	double fenzi;
	fenzi = PXwi(testX, p[i])/10.0;//pwi是先验概率,定值,恒为十分之一
	//cout <<"fenzi:"<< fenzi<<endl;
	double sum = 0;
	for (int n = 0;n < 10;n++)
	{
		sum = sum + PXwi(testX, p[n]);
	}
	sum = sum / 10.0;//提公因数十分之一,也就是提出每次都乘的先验概率
	//cout << "fenmu:" << sum << endl;
	return fenzi/sum;
}
int main()
{


	ifstream codei("E:\\code\\vs\\txtfile\\2lb.txt", ios::binary);
	int sample1[10][80][101];//保存样本图片字符串,每个数字100副图,每幅图拥有49个字符的序列;
	int sample[10][80][100];
	char ch;
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 80; j++)
		{

			for (int k = 0; k < 101; k++)
			{

				codei.get(ch);

				if ((int)ch == 10)

				{
					k--;
					continue;
				}
				sample1[i][j][k] = (int)ch - 48;
				//cout << sample1[i][j][k];

			}
			//cout << endl;
		}

	}

	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 80; j++)
		{
			for (int k = 0; k + 1 < 101; k++)
			{
				sample[i][j][k] = sample1[i][j][k + 1];
			//	cout << sample[i][j][k];

			}
			//cout << endl;

		}

	}
	double p[10][100];
	for (int n=0;n < 10;n++)
	{
		for (int i=0; i < 100;i++)
		{
			
			 p[n][i]= Pwij(i, sample[n]);

		}
	}
	for (int n = 0;n < 10;n++)
	{
		//printf("数字%d", n);
		for (int i = 0; i < 100;i++)
		{

		//	printf("模板%d:%f ", i, p[n][i]);

		}
		//cout << endl;
	}
	int test[100];//保存测试图片字符串
	char name[100];
	
	
	for (int t1 = 0; t1 < 10; t1++)
	{
		printf("数字%d\n", t1);
		double Tu = 0;
		double Fa = 0;
		double jv = 0;
		printf("先验概率:%lf\n", 20.0 / 200.0);
		for (int t2 = 0; t2 < 20; t2++)
		{double maxp=0;
				double pp,ppp;
					int turenum=0;
					
				sprintf_s(name, "E:\\code\\vs\\cvtest1\\minist\\test-images\\%d_%d.bmp", t1, t2);
				Mat src = imread(name, CV_LOAD_IMAGE_GRAYSCALE);//读取的测试图片图片
				readImg(src, test,t1);//获得了src的序列
			for (int n = 0;n < 10;n++)
			{
				ppp = PXwi(test, p[n]);
			//	printf("%.3e ",ppp);
				pp = PwiX(n,test,p);
				
				//printf("数字%d:%e  ", n, pp);
				if (pp > maxp)
				{
					maxp = pp;
					turenum = n;
				}
			}
			//cout << endl ;
			printf("数字%d识别结果:%d,后验概率:%f\n", t1,turenum,maxp);
			if (maxp < 3.612960e-34)
			{
				printf("jv! ");
				jv++;
				continue;
				
			}
			else if (turenum == t1)
			{
				Tu++;
			}
			else
			{
				Fa++;
			}
		}
			printf("数字%d成功率为%.2f\n", t1, Tu / 20.0);
		printf("数字%d错误率为%.2f\n", t1, Fa / 20.0);
		printf("数字%d拒绝识别率为%.2f\n", t1, jv / 20.0);
		cout << endl;
	}
	
	
	waitKey(0);
	//getchar();
	return 0;
}

代码运行结果:


1 条评论

http://fastloto.com · 2023年4月20日 下午8:18

Thank you for information

回复 http://fastloto.com 取消回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注