热度 6
2023-12-16 20:41
913 次阅读|
0 个评论
在上周,笔者收到了这本来自北京大学出版社的MATLAB科学计算入门书籍,笔者也是参照书中所写的例程写了些效果;先讲下笔者的情况,笔者现工作的领域为嵌入式AI软件开发工程师,在MATLAB上除了曾在大学大一那阵浅学了一段时间,而后再无接触过因此在读这本书之前,笔者正属于尚未入门的小白,但笔者一直对MATLAB颇有兴趣,便也是接由本次试读活动将MATLAB这一工具重新拾起,那也是在开篇先说总结,这本书无论是从内容深度还是浅显易懂上都是令人拍案叫绝的程度,以至于笔者仅仅只是浅读一阵便对MATLAB的编码方式了然于胸。 笔者既然是嵌入式与AI领域的从业者,那么本着看书先读目录的原则,笔者一下就锁定了两个关键章节-神经网络与图像处理,那么倘若你与笔者同属一个行业那么脑海中一定也同笔者一样浮现了如目标检测 手写识别 车牌识别等等非常经典的实现案例 那么咱们先来看图像处理的部分,在AI时代数据比算法更重要: 书中介绍了imread的图像读取方式与imshow的图片显示方式,咱们得先看到能不能正常读取才能座进一步处理,那么我们仿照实现一下: 那么可以看到imread接受一个路径的参数传入,imshow接受一个图像的参数传入,这与Python非常相像的,当然啦,仅仅是相像,还是有很多编码细节差异是相当大的,既然咱们已经可以读了,那咱们就试试简单的处理: 书中展示了一个rgb2gray(好接地气的函数名啊 )的函数用于实现将图像转换成灰度图的形式,效果如下: 嗯,非常的成功 再试试转二值化,它接受一个图像的传入和一个阈值的传入0-1区间,那我们给它个0.5试试: 文字上的提取非常干净利落,但是基板上就存在一些小问题了,咱们提高一下阈值: 不错,这次就非常干净了,后面我就不配书图了,会把文字都挤走显得难看,只会贴上程序运行与函数讲解: 那么既然实现了车牌的完美二值化,不将它提取出来做个车牌识别那不是太浪费了?那么怎么拆字符呢?我们都知道图像的行是第一个维度,列是第二个维度,对行的求和能得出图像水平上的投影,对列的求和能得出图在垂直方向上的投影,当我们得到了一个图像的投影,就可以找寻规律进行裁切了,那也是通过翻阅查找,得到了sum这个函数,这个函数不止能够接受数字类型的求和,还能传入图像类型的数据进行求和,所得的结果便是我们需要的投影数值,函数规则如下:sum(图像数据, 维度) 但是仅仅计算我们还看不到效果,在Python中有个第三方库matplotlib,这个库可以创建一系列可视化图表,那么在书中29页这有MATLAB自带的plot函数可以实现相同的功能,效果如下: 那么既然已经有了投影我们就可以找到规则并按照规则让其裁剪出我们需要的数据了: 其实也简单,图片中有很多从0到高值的上升沿和从高值骤降到0的下降沿,我们要给定一个规则,比方说我们能从100到0的终点,从0到100的起点就可以提取出文字的边缘了,判断方式如下: 这用了俩函数,一个diff一个find,diff呢是用来计算向量元素之间的差异或数组中相邻元素之间差值的,而find呢是在一个数组里面找到对应满足条件的函数 这下听懂了也听不懂了,说白了,咱们把一张图像的行和列都变成了一堆数组,事实上在MATLAB里面把所有读到的内容都可以当作一堆数组来进行处理(很有数学的风格,不愧是理科的”物理学圣剑”) 前面两个是水平投影的上升沿和下降沿,由于车牌上面的文字通常是等高居中的,因此只需要提取一个值即可,后面则有16个值,对应车牌上的一个点和7个字符的上升沿和下降沿,光说没演示谁懂啊!!!那么咱们还要判断它有没有选对,我们还要做一个事:画框,既然我们从上升沿和下降沿中得到了各个字符的边界,那么咱们只要根据这些值在图上对应的位置画上直线就能很清晰看到框没框对 当然啦,我没找到能在图中画线的函数,但是呢,咱们想一想,既然sum能将图片直接视为数值读入并计算求和,那么我们是不是可以直接在图中改对应区域的颜色数值呢,查了一下还真行! 哈哈,试错了几次还真就给我整成了,那咱们再把其他的都加上,有一个变量多个值的就用for循环(这跟Python还真是相似) 可以看到,除了我的车牌截图有点歪以外,裁剪的那是正正好好啊,那啥也甭说了,给它裁咯! 裁剪其实也简单跟刚刚将对应区域的数值改成白色一样,给它标注好区间就能直接下来咯,咱们先从简单的上下边缘来: 嗯,刚刚好,然后是字符的左右边缘,,咱们是8个字符16个边,对应之前设的变量:verticalRisingEdges和verticalFallingEdges各8个,那么我们只要取其一循环裁切即可: 在这里length是计算verticalRisingEdges的长度,8个数据对应循环8次,从1到8,并创建了一个图像窗口,根据长度显示在子图中,图中的第三个点还是有点太多余了,把它删掉: 这个函数就是把垂直方向的上升沿和下降沿的第三个数据设置为空 这样就好看多了 那么既然我们提取了字符就要做识别了,怎么识别,用CNN啊,怎么实现,先找数据定义网络模型啊,其实跟Python一样,在机器学习上MATLAB也是相当强大方便的,咱们先来找数据,这里我用的是开源数据集characterData,这个数据集是一堆车牌相关字符的二值化数据集,图片大小均为20x20 那么既然有了数据集咱们就把他们先读取进来,这里用到了imageDatastore函数,这是个图像数据读取器: 数据集读取进来了那就要分配训练集和验证集了: 还是28比例 那么数据集既然都有了,咱们也要定义个模型网络,写一写学习率这些参数,模型网络定义如下: 一个输入层 接受一个20 x 20 的图像传入 一个卷积层 hidden1_1,具有1个输入通道,28个输出通道,卷积核大小为5x5。 一个最大池化层 hidden1_2,池化窗口大小为2x2,步幅为1。 一个卷积层 hidden2_1,具有28个输入通道,32个输出通道,卷积核大小为3x3。 一个最大池化层 hidden2_2,池化窗口大小为2x2,步幅为1。 一个卷积层 hidden3,具有32个输入通道,32个输出通道,卷积核大小为3x3。 一个全连接层 hidden4,输入大小为321010,输出大小为65,使用softmax激活函数。 定义完了网络就能开始训练了,还要记得,训练完后保存模型与网络定义: 训练过程,这个训练过程的GUI可不是我实现的,MATLAB自带的,非常酷炫非常直观: 既然训练好并保存了,咱们还要加载模型并推理: 在推理之前,咱们还要对之前车牌截取的图片做一下处理,要知道我们的图可不符合模型需要的20 x 20的需求,倘若直接传入,则会大小不匹配,倘若直接压缩,那么识别精度就会骤降,这就失去价值了,最简单和最实用的办法就是,先扩再缩 先扩是目前咱们的图像是高大宽小: 将它的图像扩成这样: 然后再压缩成这样: 这就不会出先整个字符被压扁无法识别的情况了,代码如下: 既然图像数据处理好了,咱们就可以推理了: 可以看到正确识别了各个字符的属性,只要再加个字典匹配中文字符,这个车牌识别就算成功了 以上是笔者通过阅读《 MATLAB科学计算从入门到精通》后实践结果,该书笔者仅阅读了1个上午,便是在不断试错的情况下实现了这个车牌识别的基础Demo,可见这本书在对新手入门的指导作用还是相当有效用的,也是再次感谢北京大学出版社与面包板社区寄测该书于笔者,使得笔者能够入门掌握该工具的用法,为笔者日后工作添砖加瓦(不瞒各位说,学MATLAB就是防止失业,还能去当个教师教下MATLAB编程哈哈) 这里也是给出作者完整的源代码: 训练代码: % 设置图像文件夹路径 images_folder = '数据集路径'; % 读取图像文件夹列表 folders = dir(images_folder); folders = folders( & ~ismember({folders.name}, {'.', '..'})); % 初始化存储图像和标签的数组 X_train = ; % 遍历图像文件夹列表 for i = 1:length(folders) folder_name = folders(i).name; % 获取文件夹内的图像文件列表 image_files = dir(fullfile(images_folder, folder_name, '*.jpg')); % 遍历图像文件列表 for j = 1:length(image_files) % 读取和调整图像大小 img = imread(fullfile(images_folder, folder_name, image_files(j).name)); img = imresize(img, ); % 将图像添加到训练集 X_train = cat(2, X_train, img); % 将文件夹名作为标签 label = folder_name; % 将标签添加到标签数组 y_train = ; % 输出标签值以检查是否有异常 disp( ); end end % 转换标签为categorical类型 y_train = categorical(y_train); % 显示读取的图像数量和类别数量 disp( ); disp( ); % 定义网络结构 layers = ) convolution2dLayer(5, 28, 'Padding', 2, 'Name', 'hidden1_1') reluLayer('Name', 'relu1_1') maxPooling2dLayer(2, 'Stride', 1, 'Name', 'hidden1_2') convolution2dLayer(3, 32, 'Padding', 1, 'Name', 'hidden2_1') reluLayer('Name', 'relu2_1') maxPooling2dLayer(2, 'Stride', 1, 'Name', 'hidden2_2') convolution2dLayer(3, 32, 'Padding', 1, 'Name', 'hidden3') reluLayer('Name', 'relu3') fullyConnectedLayer(65, 'Name', 'hidden4') softmaxLayer('Name', 'softmax') classificationLayer('Name', 'output') ]; % 定义训练选项 options = trainingOptions('sgdm', ... 'MaxEpochs', 10, ... 'MiniBatchSize', 64, ... 'Shuffle', 'every-epoch', ... 'InitialLearnRate', 0.001, ... 'Plots', 'training-progress'); % 训练网络 net = trainNetwork(X_train, y_train, layers, options); 推理代码: % 加载保存的模型 loadedData = load('trainedModelInfo.mat'); % 获取神经网络模型和训练选项 loadedNet = loadedData.modelInfo.net; loadedOptions = loadedData.modelInfo.options; % 数据处理代码 image = imread('推理图片路径'); image = rgb2gray(image); binaryImage = imbinarize(x, 0.8); % 计算水平和垂直投影 horizontalProjection = sum(binaryImage, 2); verticalProjection = sum(binaryImage, 1); % 判断投影的上升沿和下降沿 3) == 1); 100) == -1); verticalRisingEdges = find(diff(verticalProjection <= 3) == 1); verticalFallingEdges = find(diff(verticalProjection <= 3) == -1); verticalRisingEdges(3) = ; figure; % 循环遍历裁剪区域 for i = 1:length(verticalRisingEdges) croppedImageY = binaryImage(horizontalRisingEdges:horizontalFallingEdges, :); croppedImageX = croppedImageY(:, verticalFallingEdges(i):verticalRisingEdges(i)); width = size(croppedImageX, 2); height = size(croppedImageX, 1); % 计算需要填充的额外像素数量 paddingSize = abs(width - height) / 2; % 如果宽度大于高度,向上下方向填充黑色像素 height paddedImage = padarray(croppedImageX, , 0, 'both'); % 如果高度大于宽度,向左右方向填充黑色像素 else paddedImage = padarray(croppedImageX, , 0, 'both'); end processedImage = imresize(paddedImage, ); image2_uint8 = uint8(processedImage) * 255; % 数据预处理 processedImage = imresize(image2_uint8, ); % 调整大小 % 使用训练好的模型进行预测 predictedLabel = classify(loadedNet, image2_uint8); subplot(1, length(verticalRisingEdges), i); imshow(processedImage); title(char(predictedLabel)); end