此程序为本人模式识别大作业,参考了网上的代码,并进行了一定的修改,希望对大家有所帮助!
此代码主要参考了以下文章:
https://blog.csdn.net/Einperson/article/details/106769799?spm=1001.2014.3001.5506
前面的大部分我都是以这篇文章为基础的,我在此写一些注释:
代码:
(1)先对手写体图像进行反色处理(即黑变白、白变黑),
(2)将此时的图像进行二值化处理(即将黑色变为1,白色变为0,也可能是相反的,这不重要,我偷个懒),
(3)是搜索整个图片中数字出现的位置(类似于图片裁剪,只留下手写字体的部分,可以大大降低图片数据量)
(4)将图片进行缩小,进一步减少数据量
%% sub function of pre-processing pic
function pic_preprocess = pic_preprocess(pic)
% 图片预处理子函数
%三原色图像反色处理
pic = 255-pic;
%设定阈值,将反色图像转成二值图像
pic = im2bw(pic,0.4) ;
%查找数字上所有像素点的行标y和列标x
[y,x] = find(pic == 1);
%截取包含完整数字的最小区域
pic_preprocess = pic (min(y):max(y),min(x):max(x));
%将截取的包含完整数字的最小区域图像转成20*20的标准化图像
pic_preprocess = imresize(pic_preprocess, [20, 20]);
2.主函数编写
2.1.载入数据
(1)uigetfile函数可以弹出一个窗口用于选择训练样本,运行程序后,可自动出现
(2)后面内容代码注释解释的很清楚,我不再多讲
注:FileName{k}(n)函数可用于提取训练集图片名称上第n位符号,因为我所用到的训练图片格式img001.png这种,所以取其第6位,大家要按照自己实际的情况进行改动。
%% 载入训练数据
%利用uigetfile函数交互式选取训练样本 ...是换行再度
[FileName,PathName,FilterIndex]=uigetfile(...
{'*.png';'*.bmp'},'请导入训练图片','*.png','MultiSelect','on');
if ~FilterIndex %如果用户取消,则返回上一步
return;
end
num_train=length(FileName); % Filename是一个字符串数组 图片的个数
TrainData=zeros(num_train,20*20); %生成训练样本,样本维度为20*20,由pic_preprocess函数决定
TrainLabel=zeros(num_train,1); %生成训练样本标签
for k=1:num_train
pic=imread([PathName,FileName{k}]);%逐个读入图片
%pic是1200*900*3的数组,其中1200*900是图片的原始分辨率,3是三原色
pic=pic_preprocess(pic); %将一幅图转为20*20个二进制数
TrainData(k,:)=double(pic(:)'); %单引号是将pic转一维数组,存放在TrainData的第K行
if str2double(FileName{k}(5))~=0 %如img010(即数字“9”的文件名)
TrainLabel(k)=str2double(FileName{k}(5))*10+str2double(FileName{k}(6))-1;
else %其他文件,如img001是数字“0”的文件名
TrainLabel(k)=str2double(FileName{k}(6))-1; %文件名中第6个字符是该图片的数字
end
end
2.2. 建立支持向量机!!!
之前参考的代码用到了gaSVMcgForClass函数,但是这个函数需要设置GA相关参数,本人小白一个,搞不懂这些东西就放弃了这个函数。
也有代码用到了svmtrain函数,由于本人的matlab为2019b,已经将此函数删除建议我用fitsvm。之后我又去查找fitsvm,之后发现它只能实现二分类,多分类问题只能通过建立多个两两二分类(建立二分之m方个,m为类别数)。对于手写体数字识别问题,就要50个实在是太麻烦了。
之后我又找到了fitcecoc函数,此函数可以用来实现SVM多分类问题,真的超级nice!找到关于这个函数的例子还是通过mathwork搜索得来的,其中举得例子就是分类iris鸢尾花、ionosphere电离层数据,这里我直接照搬了他们的代码,,,本人小白,真的不太会这里,不知道怎么改可以,只是发现它可以运行成功,并且分类效果不错就直接使用了
(1)templateSVM函数可以建立一个SVM模板函数,其参数值为默认值,试了下其默认核函数应该为线性核函数,想要具体了解可mathwork自行搜索。
(2)fitcecoc函数(训练集,训练标签,'Learners',t,'ClassNames',{这里填你要分类的那几种标签名,按顺序噢});关于fitcecoc函数可自行查阅mathwork进行学习。
通过查找资料发现这里训练的是ECOC模型,具体我也不懂,感兴趣的朋友可以自行学习。
(3)使用crossval函数可以实现10折交叉验证(10折交叉验证:将数据集分成平均分成互斥的十份,轮流将其中9份做训练1份做验证,10次的结果的均值作为对算法精度的估计,一般还需要进行多次10折交叉验证求均值),以测试算法准确性。
SVM——n倍交叉验证:
https://blog.csdn.net/weixin_46345400/article/details/124358046?spm=1001.2014.3001.5506
(4)通过kfoldLoss函数得到估计广义分类误差,误差越小说明分类器的泛化(泛化是指训练好的模型在前所未见的数据上的性能好坏)效果越好。
深入理解泛化:
https://blog.csdn.net/sc2079/article/details/103090727?ops_request_misc=&request_id=&biz_id=102&utm_term=%E6%B3%9B%E5%8C%96&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-103090727.142^v10^pc_search_result_control_group,157^v8^control&spm=1018.2226.3001.4187
%% 建立支持向量机
t = templateSVM('Standardize',1,'KernelFunction','linear'); %创建SVM模板t;
%templateSVM是fitcecoc函数中的SVM模板;
%standardize:数据标准化,可用help查看templateSVM其他参数
%训练该模型
Mdl = fitcecoc(TrainData,TrainLabel,'Learners',t,'ClassNames',{'0','1','2','3','4','5','6','7','8','9'});
%验证该模型
CVMdl = crossval(Mdl); %将模型进行交叉验证,平衡模型欠拟合和过拟合
%显示结果
oosLoss = kfoldLoss(CVMdl) %10折交叉验证得到的泛化误差 oosloss = 0.0750
2.3.载入测试样本
%% 载入测试样本
[FileName,PathName,FilterIndex]=uigetfile( ...
{'*.png';'*.bmp'},'请导入测试图片','*.png','MultiSelect','on');
if ~FilterIndex %如果用户取消,则返回上一步
return;
end
num_test=length(FileName); %Filename是一个字符串数组
TestData=zeros(num_test,20*20); %生成测试样本矩阵,维度为400
TestLabel=zeros(num_test,1); %生成测试样本标签矩阵
for k=1:num_test
pic=imread([PathName,FileName{k}]); %逐个读图
pic=pic_preprocess(pic); %将一幅图转为20*20个二进制数
TestData(k,:)=double(pic(:)'); %单引号是将pic转一维数组,存放在TrainData的第K行
if str2double(FileName{k}(5))~=0 %如img010(即数字“9”的文件名)
TestLabel(k)=str2double(FileName{k}(5))*10+str2double(FileName{k}(6))-1;
else
TestLabel(k)=str2double(FileName{k}(6))-1; %文件名中第6个字符是该图片的数字
end
end
2.4.提取Test测试集中图片名称(这一步是为了后面显示判断错误的图片名称,不需要可以删掉)
%% 对Test图片名称提取
fileFolder=fullfile('C:\Users\Test'); %引号内是需要遍历的路径,填绝对路径,然后保存在fileFolder
dirOutput=dir(fullfile(fileFolder,'*.png')); %引号内是文件的后缀,写'.png'则读取后缀为'.png'的文件
fileNames={dirOutput.name}; %将所有文件名,以矩阵形式按行排列,保存到fileNames中
2.5.对测试样本进行分类
predict函数的使用可以参考mathwork上官方解释,这里就不赘述了。
preTestLabel是通过SVM模型产生的预测标签,为cell型,如果要判断正确率就要与double型TestLabel进行比较,但是cell型不能直接与double型作比较,这里就是使用了preTestLabel=str2num(cell2mat(preTestLabel));来转换。
%% 对测试样本进行分类
preTestLabel = predict(Mdl,TestData);
preTestLabel=str2num(cell2mat(preTestLabel)); %将cell型preTestLabel转为double型,以供后面与测试样本标签进行比较
right =0; %正确数量
k=0; %错误数量
for i=1:numel(TestLabel)
if TestLabel(i)==preTestLabel(i) %统计正确数量
right =right+1;
else
k=k+1;
error_name(k)=fileNames(i); %记录错误文件名
end
end
test_number=numel(TestLabel)
right
accuracy =right/numel(TestLabel) %测试样本正确率 %accuracy = 0.9333
error_name
结果:
对于SVM模型的建立那里本人还不是很明白应该怎么做,如果有比较了解的朋友欢迎评论区里讲解一下,非常感谢!!