OpenCV 入门(四)—— 车牌号识别

OpenCV 入门系列:

OpenCV 入门(一)—— OpenCV 基础
OpenCV 入门(二)—— 车牌定位
OpenCV 入门(三)—— 车牌筛选
OpenCV 入门(四)—— 车牌号识别
OpenCV 入门(五)—— 人脸识别模型训练与 Windows 下的人脸识别
OpenCV 入门(六)—— Android 下的人脸识别
OpenCV 入门(七)—— 身份证识别

本节是车牌识别的最后一部分 —— 车牌字符识别,从一个完整的车牌图片到识别出车牌上的字符大致需要如下几步:

  • 预处理:将车牌图片灰度化、二值化,并去除识别时的干扰因素,比如车牌铆钉
  • 字符分割:将整个车牌图片按照每个字符分割成 7 个单独的字符图片保存到集合中
  • 字符识别:使用经过训练的数字、英文字符、中文字符的特征集合对字符图片进行识别,得到最终的车牌号

下面详解以上步骤。

1、预处理

图像识别的预处理工作一般都是灰度化、二值化这些图像“降噪”处理,当然我们这里还有一个特殊的处理,就是要祛除车牌图像上的铆钉,它是对识别准确度影响较大的一个因素。

1.1 AnnPredictor 预处理

新建一个类 AnnPredictor 用于进行字符识别:

#ifndef ANNPREDICTOR_H
#define ANNPREDICTOR_H#define ANNPREDICTOR_DEBUG#include <opencv2/opencv.hpp>
#include <string>
#include <opencv2/ml.hpp>using namespace std;
using namespace cv;
using namespace ml;class AnnPredictor {
public:AnnPredictor(const char* ann_model, const char* ann_zh_model);~AnnPredictor();// 字符识别string predict(Mat plate);private:// 用于数字和英文字符识别Ptr<ANN_MLP> ann;// 用于中文字符识别Ptr<ANN_MLP> ann_zh;// ANN 的 HOG 特征HOGDescriptor* annHog = nullptr;// 与 SvmPredictor 中的函数相同void getHOGFeatures(HOGDescriptor* svmHog, Mat src, Mat& dst);// 去除铆钉bool clearRivet(Mat &plate);// 验证单个字符尺寸bool verifyCharSize(Mat src);// 获取城市汉字字符在排序后的矩形集合中的索引int getCityIndex(vector<Rect> rects);// 获取中文字符所在的矩形void getChineseRect(Rect cityRect, Rect& chineseRect);// 识别车牌字符保存到 str_plate 中void predict(vector<Mat> plateCharMats, string& str_plate);// 汉字字符集合static string ZHCHARS[];// 数字与英文字符集合static char CHARS[];
};#endif // !ANNPREDICTOR_H

这样在 LicensePlateRecognizer 中调用 predict() 即可获取到车牌字符串:

LicensePlateRecognizer::LicensePlateRecognizer(const char* svm_model, const char* ann_model, const char* ann_zh_model)
{...annPredictor = new AnnPredictor(ann_model, ann_zh_model);
}LicensePlateRecognizer::~LicensePlateRecognizer()
{...if (annPredictor){delete annPredictor;annPredictor = nullptr;}
}string LicensePlateRecognizer::recognize(Mat src)
{// 1.车牌定位,使用 Sobel 算法定位// 2.精选车牌定位得到的候选车牌图,找到最有可能是车牌的图...// 3.对车牌图进行字符识别string str_plate = annPredictor->predict(plate);plate.release();return str_plate;
}

predict() 内,先进行预处理,像灰度化和二值化这些操作前面已出现过多次就不再赘述了:

string AnnPredictor::predict(Mat plate)
{// 1.预处理 // 1.1 灰度化Mat gray;cvtColor(plate, gray, COLOR_BGR2GRAY);// 1.2 二值化(非黑即白,对比更强烈)Mat shold;threshold(gray, shold, 0, 255, THRESH_OTSU + THRESH_BINARY);// 1.3 去铆钉if (!clearRivet(shold)){return string("未识别到车牌");}...
}

主要说一下如何去掉车牌图片上的铆钉。

1.2 去掉车牌上的铆钉

二值化后的图片仍能看到车牌上的铆钉,这是影响识别准确程度的一个干扰因素:

去柳钉前

因此我们要去掉它,去掉后的效果:

去柳钉后

去除铆钉的思路是,对车牌图像进行逐行扫描,如果这一行是铆钉,那么颜色跳变的次数应该为 4 次,远远少于正常字符的颜色跳变次数:

2024-4-4.去柳钉前颜色跳变示意图1

上面红线是扫描到铆钉的行,先是黑色,扫描到铆钉变为白色,离开铆钉再变为黑色,第二颗铆钉重复上述过程,因此有 4 次黑白之间的颜色跳变。而第二条红线扫描到正常字符,跳变次数远远大于 4,我们就用这个思路去除铆钉:

/**
* 通过一行的颜色跳变次数判断是否扫描到了铆钉,
* 一行最小跳变次数为 12,最大为 12 + 8 * 6 = 60。
* 如果该行是铆钉行,则将该行所有像素都涂成黑色(像素值为 0)
*/
bool AnnPredictor::clearRivet(Mat &plate)
{// 1.逐行扫描统计颜色跳变次数保存到集合中int minChangeCount = 12;vector<int> changeCounts;int changeCount;for (int i = 0; i < plate.rows; i++){for (int j = 0; j < plate.cols - 1; j++){int pixel_front = plate.at<char>(i, j);int pixel_back = plate.at<char>(i, j + 1);if (pixel_front != pixel_back){changeCount++;}}changeCounts.push_back(changeCount);changeCount = 0;}// 2.计算字符高度,即满足像素跳变次数的行数int charHeight = 0;for (int i = 0; i < plate.rows; i++){if (changeCounts[i] >= 12 && changeCounts[i] <= 60){charHeight++;}}// 3.判断字符高度 & 面积占整个车牌的高度 & 面积的百分比,排除不符合条件的情况// 3.1 高度占比小于 0.4 则认为无法识别float heightPercent = float(charHeight) / plate.rows;if (heightPercent <= 0.4){return false;}// 3.2 面积占比小于 0.15 或大于 0.5 则认为无法识别float plate_area = plate.rows * plate.cols;// countNonZero 返回非 0 像素点(即白色)个数,或者自己遍历找像素点为 255 的个数也可float areaPercent = countNonZero(plate) * 1.0 / plate_area;// 小于 0.15 就是蓝背景白字车牌确实达不到识别标准,大于 0.5 是因为// 黄背景黑子二值化会把背景转化为白色,由于前面的处理逻辑只能处理// 蓝背景车牌,所以黄色车牌的情况也直接认为不可识别if (areaPercent <= 0.15 || areaPercent >= 0.5){return false;}// 4.将小于最小颜色跳变次数的行全部涂成黑色for (int i = 0; i < plate.rows; i++){if (changeCounts[i] < minChangeCount){for (int j = 0; j < plate.cols; j++){plate.at<char>(i, j) = 0;}}}return true;
}

2、字符分割

接下来开始分割字符,主要可以分为三部分:

  1. 找到各个字符的轮廓,并生成轮廓对应的图片
  2. 对汉字字符的轮廓图片进行特殊处理
  3. 将所有 7 个字符的轮廓图片保存到集合中为字符识别做准备

下面详解。

2.1 生成字符轮廓图片

先通过寻找轮廓的函数 findContours() 找到轮廓,生成轮廓矩形集合 vec_ann_rects:

string AnnPredictor::predict(Mat plate)
{// 1.预处理 ...// 2.字符分割// 2.1 找轮廓// vector<Point>是点的集合,可以连成线,线的集合就是轮廓了vector<vector<Point>> contours;findContours(shold, // 输入的图像contours, // 轮廓,接收结果RETR_EXTERNAL, // 轮廓检索模式:外轮廓CHAIN_APPROX_NONE // 轮廓近似算法模式:不进行轮廓近似,保留所有的轮廓点);vector<Rect> vec_ann_rects;// 在原图上克隆一个用来画矩形Mat src_clone = plate.clone();for each (vector<Point> points in contours) {Rect rect = boundingRect(points);Mat rectMat = shold(rect);// rectangle(src_clone, rect, Scalar(0, 0, 255));// 尺寸判断,符合规格的放入 vec_sobel_rects 集合中if (verifyCharSize(rectMat)) {vec_ann_rects.push_back(rect);}}...
}

遍历 vec_ann_rects 生成轮廓矩形并生成与矩形对应的图片,这就是分割的字符图片。当然,在将它们存入集合前你需要过滤一下,因为不是所有轮廓都刚好是一个完整的字符。比如“渝”字,由于汉字相比于英文字符和数字,结构复杂,因此无法识别为整个字,而是识别出“渝”字中点或者某个局部部分:

2024-4-8.字符找轮廓效果图2

因为我们导出的车牌宽度只有 136 像素,所以放大后并不清晰,但是能看出来,英文字母和数字的轮廓只有一个,而“渝”的轮廓有多个。面对这种情况,我们先用 verifyCharSize() 进行尺寸校验,将不符合规格的字符图片过滤掉:

bool AnnPredictor::verifyCharSize(Mat src)
{// 最理想情况 车牌字符的标准宽高比float aspect = 45.0f / 90.0f;// 当前获得矩形的真实宽高比float realAspect = (float)src.cols / (float)src.rows;// 最小的字符高float minHeight = 10.0f;// 最大的字符高float maxHeight = 35.0f;// 1、判断高符合范围  2、宽、高比符合范围// 最大宽、高比 最小宽高比float error = 0.7f;float maxAspect = aspect + aspect * error;//0.85float minAspect = 0.05f;int plate_area = src.cols * src.rows;float areaPercent = countNonZero(src) * 1.0 / plate_area;if (areaPercent <= 0.8 && realAspect >= minAspect && realAspect <= maxAspect&& src.rows >= minHeight &&src.rows <= maxHeight) {return true;}return false;
}

汉字被识别为多个部分,无法通过 verifyCharSize() 的校验,会被过滤掉。也就是说,这一步中,我们只获取了英文和数字图片,汉字图片暂时还未获取。

2.2 获取汉字轮廓

思路是,先定位到汉字后面表示城市的那一位英文字符,再向左侧推导获取汉字轮廓。

首先,根据图片的横坐标对 vec_ann_rects 集合内的字符图片进行从左至右的排序:

string AnnPredictor::predict(Mat plate)
{...// 2.2 对矩形轮廓从左至右排序sort(vec_ann_rects.begin(), vec_ann_rects.end(), [](const Rect& rect1, const Rect& rect2) {return rect1.x < rect2.x;});...
}

然后获取到表示城市字符的轮廓索引:

string AnnPredictor::predict(Mat plate)
{...// 2.3 获取城市字符轮廓的索引int cityIndex = getCityIndex(vec_ann_rects);...
}

getCityIndex() 获取城市字符索引的思路是:车牌上共有 7 个字符,那么城市字符位于第 2 位,该字符中间横坐标一定在车牌水平方向的 1/7 ~ 2/7 之间:

/**
* 寻找城市字符(7 位字符中的第 2 位)轮廓索引
*/
int AnnPredictor::getCityIndex(vector<Rect> rects)
{int cityIndex = 0;for (int i = 0; i < rects.size(); i++){Rect rect = rects[i];int midX = rect.x + rect.width / 2;// 如果字符水平方向中点坐标在整个车牌水平坐标的// 1/7 ~ 2/7 之间,就认为是目标索引。136 是我们// 训练车牌使用的素材的车牌宽度if (midX < 136 / 7 * 2 && midX > 136 / 7){cityIndex = i;break;}}return cityIndex;
}

获取城市字符索引后,可以根据其横坐标推断出汉字字符的轮廓:

string AnnPredictor::predict(Mat plate)
{...// 2.4 推导汉字字符的轮廓Rect chineseRect;getChineseRect(vec_ann_rects[cityIndex], chineseRect);...
}

getChineseRect() 会将推断出的矩形保存到 chineseRect 中:

/**
* 通过城市字符的矩形,确定汉字字符的矩形
*/
void AnnPredictor::getChineseRect(Rect cityRect, Rect& chineseRect)
{// 把宽度稍微扩大一点以包含完整的汉字字符// 还有一层理解,就是汉字与城市字符之间的空隙也要计算进去float width = cityRect.width * 1.15;// 城市轮廓矩形的横坐标int x = cityRect.x;// 用城市矩形的横坐标减去汉字宽度得到汉字矩形的横坐标int newX = x - width;chineseRect.x = newX > 0 ? newX : 0;chineseRect.y = cityRect.y;chineseRect.width = width;chineseRect.height = cityRect.height;
}

2.3 保存所有字符图片

最后将 7 个字符的图片保存到 plateCharMats 集合中等待字符识别:

string AnnPredictor::predict(Mat plate)
{...// 2.5 将字符图像保存到集合中// 先保存汉字字符图像vector<Mat> plateCharMats;plateCharMats.push_back(shold(chineseRect));// 再获取汉字之后的 6 个字符并保存int count = 6;if (vec_ann_rects.size() < 6){return string("未识别到车牌");}for (int i = cityIndex; i < vec_ann_rects.size() && count; i++, count--){plateCharMats.push_back(shold(vec_ann_rects[i]));}...
}

3、字符识别

3.1 识别过程

调用 predict() 传入字符图片集合 plateCharMats,识别的字符结果保存在 str_plate 中:

string AnnPredictor::predict(Mat plate)
{...// 3.字符识别string str_plate;predict(plateCharMats, str_plate);for (Mat m : plateCharMats) {m.release();}// 4.释放 Matgray.release();shold.release();src_clone.release();return str_plate;
}

predict() 内遍历 plateCharMats,提取 HOG 特征后对汉字和数字英文分开识别,注意 ZHCHARS 与 CHARS 内的字符顺序要和训练样本存放顺序相同:

string AnnPredictor::ZHCHARS[] = { "川", "鄂", "赣", "甘", "贵", "桂", "黑", "沪", "冀", "津", "京", "吉", "辽", "鲁", "蒙", "闽", "宁", "青", "琼", "陕", "苏", "晋", "皖", "湘", "新", "豫", "渝", "粤", "云", "藏", "浙" };
char AnnPredictor::CHARS[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };void AnnPredictor::predict(vector<Mat> plateCharMats, string& result)
{for (int i = 0; i < plateCharMats.size(); i++){Mat mat_plate_char = plateCharMats[i];// 提取 HOG 特征Mat features;getHOGFeatures(annHog, mat_plate_char, features);Mat sample = features.reshape(1, 1);Mat response;Point maxLoc;Point minLoc;if (i){// 字母和数字ann->predict(sample, response);minMaxLoc(response, 0, 0, &minLoc, &maxLoc);int index = maxLoc.x;result += CHARS[index];}else{// 汉字ann_zh->predict(sample, response);minMaxLoc(response, 0, 0, &minLoc, &maxLoc);int index = maxLoc.x;result += ZHCHARS[index];}}
}

至此代码结束,先来看一下效果:

2024-4-8.最终识别结果1

对于【渝G 83666】的识别结果为【渝G 80666】,错了一位,这与识别的算法,还有训练样本的数量都有关系。总的来说,Demo 提供了一种车牌识别的思路,但是准确度还是有限的。

3.2 样本制作

最后来说说样本是如何制作的。

识别车牌字符,需要所有字符训练的特征集合,即首位的汉字共 31 个字符、第二位英文字符 24 个(刨除 I 和 O 两个容易被误识别为 1 和 0)、以及后续位数中需要用到的 10 个数字。我们将训练样本分为两类:英文字符和数字的训练样本放入 ann 文件夹,汉字字符放入 ann_zh 文件夹。两个文件夹内都需要对字符进行编号:

  • ann 中 0 号文件夹是数字 0 的训练素材,1 号文件夹是数字 1 的训练素材,以此类推,总共是 10 + 24 = 24 个文件夹
  • ann_zh 中 1 号文件夹是“川”字的训练素材,2 号文件夹是“鄂”字的训练素材,共计 31 个文件夹

这些字符的编号顺序需要记录在一个额外的文档中,内容如下:

"川", "鄂", "赣", "甘", "贵", "桂", "黑", "沪", "冀", "津", "京", "吉", "辽", "鲁", "蒙", "闽", "宁", "青", "琼", "陕", "苏", "晋", "皖", "湘", "新", "豫", "渝", "粤", "云", "藏", "浙"
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'

训练后可以得到两个特征集合文件 ann.xml(数字和英文字符)和 ann_zh.xml(汉字字符),正是初始化 AnnPredictor 的 ann 和 ann_zh 所加载的文件。目录结构如下:

2024-4-8.车牌字符样本训练目录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/8886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【论文阅读】VMamba: Visual State Space Model

VMamba:视觉状态空间模型 code&#xff1a;https://github.com/MzeroMiko/VMamba Background ​ CNN拥有线性复杂度因而可以处理高分辨率的图像&#xff0c;而ViT在拟合能力方面超过了CNN&#xff0c;但ViT是二次复杂度&#xff0c;在处理高分辨率图像时计算开销较大。ViT通过…

Wappalyzer指纹识别下载安装使用教程,图文教程(超详细)

「作者简介」&#xff1a;2022年北京冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础对安全知识体系进行总结与归纳&#xff0c;著作适用于快速入门的 《网络安全自学教程》&#xff0c;内容涵盖系统安全、信息收集等…

Windows平台通过MobaXterm远程登录安装在VMware上的Linux系统(CentOS)

MobaXterm是一个功能强大的远程计算工具&#xff0c;它提供了一个综合的远程终端和图形化的X11服务器。MobaXterm旨在简化远程计算任务&#xff0c;提供了许多有用的功能&#xff0c;使远程访问和管理远程服务器变得更加方便&#xff0c;它提供了一个强大的终端模拟器&#xff…

钉钉开放平台创建企业内部H5微应用或者小程序

前言&#xff1a; 在当今企业数字化转型的浪潮中&#xff0c;创建企业内部H5微应用或小程序已成为提升工作效率和促进内部沟通的重要举措。发话不多说本文将介绍如何利用钉钉平台快速创建这些应用&#xff0c;让企业内部的工作更加便捷高效。 步骤 1.在浏览器打开链接…

【解决】Android APK文件安装时 已包含数字签名相同APP问题

引言 在开发Android程序过程中&#xff0c;编译好的APK文件&#xff0c;安装至Android手机时&#xff0c;有时会报 包含数字签名相同的APP 然后无法安装的问题&#xff0c;这可能是之前安装过同签名的APP&#xff0c;但是如果不知道哪个是&#xff0c;无法有效卸载&#xff0c;…

ESD静电问题 | 更换接口芯片

【转发微信公众号&#xff1a;EMC容冠电磁】

VMare Workstation安装ubuntu虚拟机异常问题处理

安装方法 ubuntu官网下载插件 异常处理 开启时报错"unable to proceed without a log file" 遇到此问题的都有一个共同点&#xff0c;工作目录路径上都带了数字&#xff0c;比如"Ubuntu 64位 01"&#xff0c;解决方法为&#xff1a; 选中"Ubuntu 64位…

用得助全媒体呼叫中心,让AI落到实处帮品牌做营销

怎么让人工智能落到实处的帮助到我们&#xff1f;我们今天来讲讲中关村科金得助全媒体呼叫中心是怎么让AI帮品牌。 这次聊的案例是知名的护肤品牌&#xff0c;该品牌在中国功能性护肤品市场占有率达到20.5%&#xff0c;这么高的市场占有率客户的咨询量也是非常庞大的&#xff0…

C++ 继承篇

面向对象语言的三大特性&#xff1a;封装&#xff0c;继承和多态 根据目前学到的知识&#xff0c;对于封装的理解&#xff0c;大致有两层&#xff1a; 将数据和方法封装&#xff0c;不想让外面看到用private/protected修饰&#xff0c;想让外面看到用public修饰类型的行为不满…

[嵌入式系统-71]:RT-Thread-组件:日志管理系统ulog,让运行过程可追溯

目录 ulog 日志 1. ulog 简介 ulog 架构 配置选项 日志级别 日志标签 2. 日志初始化 初始化 去初始化 3. 日志输出 API 4. 日志使用示例 使用示例 在中断 ISR 中使用 同步模式&#xff08;Synchronous Mode&#xff09; 异步模式&#xff08;Asynchronous Mode&…

蓝桥杯EDA客观题

目录 前言 一、PCB类知识点和题目分析 1.电阻 2.电容 3.封装类 4.单位转换类 5.电路板结构类 6.PCB绘制规则 7.立创软件 8.PCB硬件 线性电源和开关电源 二、数电知识点和题目分析 1.门电路 2.逻辑代数 3.组合逻辑电路 4.触发器 5.时序逻辑电路 6.其他 三、模…

vue3+ts之el-tooltip换行显示内容

<el-tooltip placement"top-end"><div slot"content" class"tips"><el-button type"primary" click"exportData">导出</el-button></div><template #content><span class"cont…

【项目实战】使用Yolov8 + tesseract 实现身份证信息解析(OCR) + 输入可为图片或者pdf + 完整代码 + 整体方案 + 全网首发

本项目可用于实验,毕业设计参考等。整体效果如下所示: 说明:图片来源于网络,如有侵权,请联系作者删除。 目录 一 数据集制作

C语言--带环链表问题

继续学习 一、判断链表是否带环 141. 环形链表 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;用快慢指针&#xff0c;快指针走两步&#xff0c;慢指针走一步&#xff0c;当慢指针走一半快指针进到环里 当慢指针进环&#xff0c;快指针已经在环中转了一会儿了 | |…

关于Java selenium使用前浏览器驱动的下载和环境变量的配置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

vue+ant-design+formBuiler表单构建器——技能提升——form design——亲测有效

最近看到后端同事在弄一个后台管理系统&#xff0c;额&#xff0c;前端真的是夹缝中生存啊&#xff0c;AI抢饭碗&#xff0c;后端也想干前端的活儿。。。 他用到了表单构建器&#xff0c;具体效果如下: 网上有很多适用于ElementUi和ant-design的form design插件&#xff0c;下…

武汉星起航:精准市场定位引领跨境电商新潮流,创造辉煌业绩

在跨境电商领域&#xff0c;市场定位的准确性直接关系到企业的成败。武汉星起航电子商务有限公司&#xff0c;凭借其自运营团队的深厚经验和精准洞察力&#xff0c;成功在亚马逊平台开设多家自营店铺&#xff0c;并取得了显著成绩。这一成绩的取得&#xff0c;离不开公司对市场…

设计模式——行为型模式——策略模式(含实际业务使用示例、可拷贝直接运行)

目录 策略模式 定义 组成和UML图 代码示例 实际业务场景下策略模式的使用 策略模式优缺点 使用场景 JDK中使用策略模式示例 参考文档 策略模式 定义 策略模式定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以相互替换&#xff0c;且算法的变化…

FMEA助力医疗设备研发制造:领跑未来,实现弯道超车!

医疗设备作为保障人类健康的重要工具&#xff0c;其研发与制造水平直接关系到医疗技术的进步。然而&#xff0c;在激烈的市场竞争中&#xff0c;如何能够让自家医疗设备研发制造实现弯道超车&#xff0c;成为行业佼佼者&#xff1f;答案就在于——FMEA&#xff08;失效模式与影…

试用NXP官方的UDS bootloader

文章目录 1.前言2.资料获取2.1 MCU例程 2.2 开发环境2.3 上位机2.4 硬件 3.工程修改3.1 boot工程修改 3.2 app工程修改4.测试情况5.例程分享 1.前言 最近很多客户在开发S32K系列MCU时咨询是否可以提供基于UDS协议的bootloader。本文以S32K144为例&#xff0c;介绍如何使用NXP官…