简 介: 本文详细介绍了在第十六届全国大学生智能汽车竞赛中智能视觉组的系统方案。本组别的比赛采用大赛组委会统一指定的C型车模,以恩智浦半导体公司生产的32位单片机I.MX RT1064 为核心控制器,使用OpenART mini摄像头视觉处理模块对Apriltag和数字标靶进行识别,采用Lenet来进行模型训练。要求智能车能够采集到赛道电磁信息以最快速度绕行赛道两圈,并且执行关心动物、击中水果、识别三叉路口等任务,智能车采用六路电磁运放对赛道信息进行检测,根据对称的配对电感采集到的电磁信号之差来执行相应的方向调整动作;通过编码器检测智能车的实时速度,使用 PID 控制算法调节电机的转速和舵机的转角,实现智能车在行驶过程中速度和方向的闭环控制;为了提高模型车调试的简便,使用无线转串口模块、TFT显示屏模块等调试工具,达到很好的人机交互的效果,大大节约调试时间。经过大量的实验后,实验结果表明,该系统设计方案可行。
关键词
: 智能视觉,IMX RT1064,Lenet,PID控制
队伍名称:The waves
参赛队员:刘云中
高威
张文启
带队教师:陈震
綦声波
第一章 绪论
1.1 智能车研究背景
目前 ,在企业生产技术不断提高、对自动化技术要求不断加深的环境下,智能车辆以及在智能车辆基础上开发出来的产品已成为自动化物流运输、柔性生产组织等系统的关键设备。智能车是一个集环境感知、规划决策,自动行驶等功能于一体的综合系统。它集中地运用了计算机、传感、信息、通信、导航及自动控制等技术,是典型的高新技术综合体。它具有道路障碍自动识别、自动报警、自动制动、自动保持安全距离、车速和巡航控制等功能,结合图像识别和大数据等知识来实现一些特定功能,是现代人工智能发展中很重要的组成部分。
全国大学生智能车竞赛是从 2006 开始,由教育部高等教育司委托高等学校自动化类教学指导委员会举办的旨在加强学生实践、创新能力和培养团队精神的一项创意性科技竞赛,至今已经成功举办了十五届,2021年开展第十六届全国大学生智能汽车竞赛。全国大学生智能汽车竞赛是在竞赛组委会提供的统一汽车模型平台上,使用英飞凌(Infineon)、宏晶公司(STC)、灵动微电子(MindMotion)、沁恒微电子(WCH)、恩智浦(NXP)等公司出品的微控制器作为车模的主要可编程主控制器,通过设计传感器采集电路和电机驱动电路、编写相应算法程序及装配模型车,制作一个能够自主识别道路的模型汽车,按照规定路线行进,并且完成一些特定功能,以完成时间最短者为优胜。
1.2 本届智能视觉组竞赛规则简介
比赛是在 PVC 赛道上进行,赛道采用黑色边线和电磁进行导引。车模是从车库驶出,运行两周之后再驶入车库内。比赛时间是从车模驶出车库到返回车库之间的时间差计算,因此计时线圈是放置在车库门口处。赛道上的斑马线和斑马线下的磁铁放置与往届规则相同。
▲ 图1.1 车库斑马线
在车模行进过程中,需要完成以下两个任务。
- 任务一:根据三叉路口数字标靶上数字的奇偶来选择从哪条支路走,如果是偶数则选择右侧路口,如果是奇数,则选择左侧路口,第二圈选择的支路方向与第一圈不同。
- 任务二:识别道路中的Apriltag,并且根据路边标靶的图案来执行不同动作。如果是动物,则在该区域停止三秒钟以上。如果是水果,则使用激光击打水果。
最终根据任务完成情况和运行完赛道的时间,得到最终的完赛时间。
1.3 方案总体介绍
比赛跑道表面为白色,两边有连续黑线作为赛道边界线,黑线宽度规定为20-30mm。赛道正中央有电磁引导导线。车模通过采集电磁信息进行路径检测。参赛队员的目标是使智能车按照规则在行驶过程中完成视觉组的一些指定任务,并且以最短时间在赛道上运行两周并进入车库。根据竞赛规则相关规定,本智能车系统采用大赛组委会统一提供的 C 型车模,以 NXP 公司生产的 32 位微控制器I.MX RT1064作为核心控制器,使用python进行视觉方面的模型训练,在IAR开发环境中进行软件开发。通过OpenART 摄像头扫描赛道元素,比如Apriltag、动物图片、数字标靶、水果图片。将摄像头识别到的图像分类结果通过五个IO端口并行输送到I.MX RT1064微控制器,微控制器根据得到的分类结果来控制执行器产生相应的动作;通过编码器来检测车速,并通过单片机正交解码进行脉冲计算获得速度和路程;转向舵机和驱动电机分别采用PD, PID控制,并且使用舵机转向+差速的算法来实现转弯。通过 PWM信号来控制驱动电路调整电机的功率,进而控制速度的大小。
1.4 本文结构
本文共分为七章。第一章主要介绍了比赛的背景及智能车系统总体方案;第二章从智能车系统的机械结构出发,详细阐述了智能车系统各部分机械结构的设计及安装;第三章重点介绍了智能车系统中所涉及的电气硬件设计方案和原理;第四章介绍了智能车的电机舵机的控制算法;第五章介绍了图像识别算法;第六章对调试过程中使用的一些工具、手段,以及遇到的问题进行了介绍;第七章则是对智能车一些物理参数进行了简要的列举。
第二章 机械结构设计及调整
设计一辆性能优越的智能车,首先需要让车模拥有稳定的机械结构。稳定的机械结构有利于编写智能车的控制算法,尤其是在车模高速运行时,它可以有效地减少车模飘移、抖动带来的干扰,降低控制难度。所以,它不仅是设计控制策略和算法的基础,也是突破智能车速度瓶颈的关键。因此,我们在遵守竞赛规则的前提下对车模进行了多方面的调整以使车模具有更良好的运行性能。
2.1 整体布局
根据智能视觉组竞赛规制,本次竞赛选用北京博思威龙科技有限公司生产的智能车竞赛专用模型车(C1型模型车),车模的驱动方案为前轮转向,后轮驱动,采用双驱RS380马达,配套伺服器型号为 S3010。该车模采用了车盘一体化以及后轴双轴设计,具有刚性足、重心低、操控性强的特点,但是在实际使用过程中,低底盘机构虽然降低了重心,让车模在运行过程中更稳定,但是不利于车模通过坡道,故设计时未安装影响车模上坡的前泵把,并对整体重心进行调整,防止车模在坡道最高点卡住。组装完毕的智能车如图2.1所示。
▲ 图 2.1 智能车全貌
2.2 舵机安装
根据智能视觉组的任务要求,车模至少需要安装两个舵机,一个负责控制车模转向,型号为S3010,另一个负责控制识别摄像头转向,型号为PDI-6225MG。
2.2.1 转向舵机
转向舵机是控制车模稳定运行的关键部件,同时舵机转向也是整个控制系统中延迟较大的一个环节。通过分析转向轮的转向原理可以发现,在相同条件下,转向连杆与舵机的连接点离舵机轴心距离越远,转向轮转向时角度变化越快。这相当于增加角度调节头的转动半径,提高了线速度。根据上述特性,为了提高舵机响应速度,将舵机竖直安装,使舵机的输出臂更长,这样就可以在舵机输出较小的转角时,前轮转动较大的角度,从而提高了整个车模转向控制的速度。同时舵机安装在车模正中央,避免多级传动产生空程和死区问题并保证两轮转动角度一致。
▲ 图 2.2 转向舵机
2.2.2 摇头舵机
控制识别摄像头转动的摇头舵机对摄像头的识别能力有着一定的影响,为了能够获得较好的视野,所选摇头舵机拥有300°的转动角度,并将其安装在车模正前方,底部通过热熔胶与转向舵机相连,并用支架固定,确保摇头舵机在车模高速行驶的过程之中不会剧烈晃动。使用轻便坚固的碳纤杆作为摄像头以及激光发射器的支撑材料,并让两者平行地指向同一角度。
▲ 图 2.3 摇头舵机
2.3 循迹传感器安装
本次竞赛我们采用双电磁前瞻进行循迹,线性CCD识别车库。前瞻作为电磁车的眼睛,其摆放位置值得深究。首先两电磁前瞻之间距离不能太近,需要流出足够的空间给摄像头识别放置在赛道上的图片,而前端的电磁前瞻离地面要有一定的距离,避免撞上坡道以及影响摄像头识别标靶上的图片。故在设计时将近端电磁前瞻安装在离车模较近的位置,留出足够的空间供摄像头识别使用,并增大前瞻支架与地面的夹角,增加远端电磁前瞻离地距离,此时远端前瞻离车模越远,其离地距离越大,所采集到的电磁信号越弱,所以在保证采集到的信号数值大小在合理范围的情况下,把远端前瞻安装在尽可能远的位置,使智能车“看”得更远。用于识别车库的线性CCD则安装在后端电磁前瞻的下方,调整好角度后用热熔胶固定。
▲ 图 2.4 电磁前瞻及线性CCD
2.4 重心调整
由于重心靠前会造成舵机负担,重心过于靠后又会导致侧滑,所以在保证车模顺利通过坡道的前提下,要尽可能的降低车子的重心并保证重心在整车的中轴线上。待各部件都安装完毕之后,调整电池的位置来调整车模的重心,并通过多次实际测试确定电池的安装位置。为了避免自锁轧带影响车模通过坡道(扎带可能会与坡道最高点刮碰),电池用魔术贴粘在底盘上
▲ 图 2.5 车模侧视图
2.5 车轮倾角
为了使汽车直线行驶稳定,转向轻便,减少轮胎和转向系零件的磨损等,需要进行车轮定位。C1型车模后轮为双轴设计,车轮与智能车的前进方向保持平行,无法调整,但是在安装电机时要保证左右两轮系的一致性,马达的铜齿轮与传动的塑胶齿轮之间的啮合间隙要合理,以减少齿面磨损和行驶过程中的噪音。C1型车模的前轮可以通过装入不同调节块、臂座,调节介子的位置来改变其高度及倾角。通过实际测试可以发现,如果前轮向外倾斜安装(外倾角为正),如图2.6所示,这种装法有助于智能车在弯道上行驶,但不利于跑直道,如果向内倾斜安装(外倾角为负),如图2.7所示,这种装法有助于智能车在直道上行驶,但不利于过弯道。经过多次修改,最终决定让前轮外倾角为0,既前轮竖直安装,不加倾斜角度。
▲ 图2.6 前轮外倾
▲ 图2.7 前轮内倾
第三章 硬件电路设计
硬件是整个智能车系统最基础的一部分,良好的硬件设计是实现软件编程设计的基础,才能保证智能车运行流畅稳定。在保证硬件电路稳定可靠的前提下,硬件设计要本着简便,强电和弱电相互隔离。在不增加硬件设计难度的情况下尽量保证软件开发方便,或者以小的硬件设计难度来换取更多方便、可靠、高效的软件设计。
根据车身结构来合理布局,确定电路板的尺寸以及固定孔位置,车模更加一体化。硬件电路设计的开始,要进行主控芯片的选择,主控芯片要考虑算力,是否能够满足图像处理的要求,芯片主频能否满足传输速率的要求等。整个硬件系统主要包括电源供电电路、电机驱动电路、运算放大电路等,将各个电路设计到一起时,需要考虑到供电的稳定性。
3.1 主控芯片选择
主控芯片使用的是 NXP 公司生产的 I.MX RT1064,此款芯片基于Cortex-M7 内核,主频高达 600MHz,cpu运算速度快,这意味着在对摄像头采集的图像做图像处理的时候会有很强的运算性能,为大量复杂的运算提供了更大的算力,增加了数据处理的速度,增强了图像处理能力。同时,此款芯片还有 1M 的片内 SRAM,可以将一些较为复杂程序中的一些中间变量放入特殊的内存,提高数据处理的速率。RT1064具体参数如下
表3.1 RT1064具体参数
- 产品名称 I.MX RT1064最小系统板
- 芯片主频 600MHZ
- 产品尺寸 长宽=53MM37MM
- 芯片内核 ARM-Cortex-M7
- 芯片RAM 1M片内SRAM
- 芯片ROM 4M片内FLASH
- 外置SDRAM 外置SDRAM:32MB
- 下载接口 标准10PIN SWD下载接口
- 适配下载器 DAP下载器和JLINK V9及以上ben本
3.2 电源电路的设计
3.2.1 电池的选择
电池选用C类车模要求的镍铬电池,规格型号为7.2V 2000mA。能够满足智能车中速运行大概二十多分钟,续航时间长。并且该类电池放电口和充电口一体式,可以对放电口进行改装,比如安装T型母插头,可以和主板的T型公插头直接相连,方便及时更换电池。并且该电池的尺寸很符合放置在车模底盘,不会影响车模运行情况及观感。
▲ 图3.1 镍铬电池
电路根据不同外设供电需要,需要从7.2V镍铬电池设计不同的稳压电路,用来提供6V舵机供电、5V单片机供电、3.3V编码器供电,另外电机的供电直接使用电池输出电压,无需做稳压处理。
3.2.2 6V舵机稳压电路的设计
AS1015是一款DC-DC降压转换器,可以满足5A的输出电流。利用脉宽调制控制方案,以300千赫的固定频率切换,输入电压的范围为3.6-23V,为客户提供0.8-Vin的可调输出电压。AS1015提供使能功能,可由外部逻辑信号控制,并且由于内部补偿,在线路或负载瞬态期间具有出色的调节能力,输出电压范围广,可以满足不同负载需要。另外还可以满足100%最大占空比输出,用于低压降情况。
▲ 图3.2 AS1015封装图
关于芯片手册中各个引脚的定义如下:
表3.2 AS1015引脚功能表
1 FB 该引脚检测反馈电压,以调节输出电压。将此引脚连接到电阻分压器,以设置输出电压。
2 EN 该引脚允许外部逻辑控制信号开启/关闭该器件。浮动此引脚或将其驱动至低电平以关闭该器件,驱动至高电平以打开该器件。如果不需要此功能,请将此引脚直接连接到输入引脚。
3 OCSET 在此引脚与输入引脚之间增加一个外部电阻,以设置峰值电流。
4 IN 降压转换器的输入引脚。必须在此引脚与地之间连接一个适当大的电容,以旁路集成电路输入端的噪声。
5,6 SW 降压转换器的输出引脚。该引脚是向输出端供电的开关节点。将一个LC滤波器从该引脚连接到输出负载,将一个整流二极管接地
7,8 GND 降压转换器的接地引脚。将此引脚连接到电路接地。
查找AS1015数据手册,了解到其推荐电路,电路如下:
▲ 图3.3 AS1015推荐电路
参考芯片手册推荐电路,设计了自己的电路:
▲ 图3.4 舵机驱动电路原理图
当给主板供电的时候,就让AS1015有效,所以EN使能管脚直接接的输入,没有接另外的IO管脚。
在SW管脚和GND管脚之间加了一个肖特基二极管,目的是为了限制输出电压的范围,当输出电压为负值的时候,而且很小的时候,稳压二极管的电压钳位作用,使得输出电压不至于太小。当输出电压为正值的时候,而且很大的时候,利用稳压二极管反向击穿特性,电压稳定在一个恒定值,不至于太大,烧坏负载。
FB为电压参考端,电压为0.8V,通过两个电阻分压,来设置输出电压。通过调整滑动变阻器来改变放大倍数,进而调整输出电压到6V,输出电压的计算公式如下:
V_out=V_FB*(1+R3/RP1)
输入电压端加了一个电容,旁路输入端的电压噪声,使得输入端电压纹波小。在输出端加了一个LC滤波器,目的是通低频,阻高频,维持输出电压相对稳定。
3.2.3 5V稳压电路的设计
LM2596s开关电压调节器是降压型电源管理单片集成电路,能够输出3A的驱动电流。相比于另外一款稳压芯片——AMS1117,能够带动更大的负载。该稳压芯片要给OPEN ART摄像头、线性CCD、六路电磁运放、单片机等供电,另外还可作为一级稳压器,为3v3稳压输入提供稳定的保障,为了各外设供电稳定,所以选择大功率稳压芯片比较适合。
▲ 图3.5 LM2596S实物图
LM2596S管脚定义:
表3.3 LM2596S管脚功能表
管脚号 管脚名字 管脚功能
1 VIN 正输入端,该管脚一般提供一个旁路电容减少暂态电压
2 Output 输出端,输出端一般接一个LC滤波器,滤除高频干扰
3 Ground 接地端
4 Feedback 反馈端,这个管脚把输出端的电压反馈到闭环反馈回路
5 ON(——)/OFF
这个管脚可以使用逻辑电平把LM2596切断,低电平该芯片工作
5V稳压原理图电路设计:
▲ 图3.6 5V稳压原理图
3.2.4 3.3V稳压电路的设计
3V3稳压模块主要是用lm39100s在5V稳压基础上再做稳压,lm39100是一种固定输出调节器,是一种低压差线性稳压器,最大可输出1A电流,可以给编码器,TFT显示屏等供电,这种供电的方式使输出电源受干扰小,可以使传感器采集的信号更加稳定。由于该稳压芯片没有使能端,所以会面临内核启动的问题,当时设计电路忽略了这一点,但是不会影响调试。原理图如下所示:
▲ 图3.7 3.3V稳压原理图设计
3.3 电机驱动电路的设计
电机驱动芯片选用BTN7971B驱动芯片,它具有逻辑电平输入、电流检测诊断、压摆率调整、死区时间产生以及过温、过压、欠压、过流和短路保护,便于进行控制和电路设计,且驱动能力满足智能车智能视觉组的使用要求。BTN7971B与其他BTN7971B结合使用,可以形成H桥电机驱动。
电流采集使用BTN的引脚进行负载电流输出,IS引脚与负载电流成比例(比例一般为19.5K,具体见芯片数据手册)的电流源,在该引脚上配置检流电阻,可以通过AD接口采集到电压得到相应的电流值。
对BTN的使能引脚进行单独控制,配合电流检测可以对BTN的使能状态进行控制,检测到过流时,切断BTN输出,对电机进行过流保护。
在BTN的电源和地之间接470nf的电容,进行滤波,减少电机造成的地抖动。下面是BTN7971B的封装:
▲ 图3.8 BTN7971B封装图
关于BTN7971B的引脚定义:
表3.4 BTN7971B引脚功能表
管脚号 管脚标号 I/O 功能
1 GND - 接地端
2 IN I 输入,定义高端或低端开关被激活
3 INH I 禁止端,当设置为低电压,芯片处于睡眠模式
4,8 OUT O 半桥的电压输出
5 SR I 功率开关的转换速率可以通过在SR和GND之间连接一个电阻来调节
6 IS O 与输出电流成比例的电流源输出,电流检测和诊断
7 VS - 电压供给
芯片手册建议的一种H桥设计方法如下:
▲ 图3.9 BTN7971B H桥推荐电路
参考上面H桥设计电路,最终设计了该驱动电路原理图如下:
▲ 图3.10 电机驱动原理图
使用两个H桥来控制两个电机,一个H桥由两路PWM组成,可以在IS端接一个ADC管脚,来检测电压,进行过流保护。
以下显示IN管脚和OUT管脚的时序问题,当IN由低变高 ,OUT会延时一段时间后,再有电压输出,当IN由高变低,OUT会有一段延时,然后由高变低。
▲ 图3.11 IN和OUT管脚时序图
3.4 六路电磁运放的设计
3.4.1 四路电磁运放的设计
运算部分采用OPA4377,目前可以通过匹配外接电阻达到147倍的放大倍数,可以通过改变电阻大小来改变放大倍数。OPA4377运算放大器是四路通道的运放,可以采集四路电磁信息,对比二路通道的运放,多通道运放可以简化PCB布局,并且也可以做到对称布线,提高信号质量和对称性。电磁运放的接口设计为下面这种,为可插拔式,方便更换运放。
▲ 图3.12 电磁运放接口设计
四路电磁运放的电路板如下:
▲ 图3.13 四路电磁运放电路板
六路电磁运放的设计是采用两个四路电磁运放模块,四路电磁运放原理图如下:
▲ 图3.14 四路电磁运放原理图设计
3.4.2 配对电感电容的设计
赛道信号发生器采用的是龙邱科技20Khz的电磁信号发生器,该信号发生器是100mA的电流源,通电导线周围存在磁场这个规律可知,离导线越远,磁场强度越弱,越靠近导线,磁场强度越强。
由谐振公式可知:
█(f_0=1/(2π√LC)#公式1)
█(1/(2π√LC)=1/(2π√(10*〖10〗(-3)*6.8*〖10〗(-9) ))=19.6Khz#公式2)
当LC谐振电路的谐振频率和信号发生器的电流源频率相近或者相等的时候,谐振电路的电压信号会达到峰值,方便信号采集。配对电感电容的选择要符合上式的谐振公式,尽可能地接近20Khz。
3.5 主控板的设计
主控板除了包括核心板插针以外,还包括5V,3V3稳压电源模块,另外电机驱动板接口和四路电磁运放模块接口也都设计在主控板上,最大程度简化了硬件布局。另外还设置了电机驱动模块的开关以及主控板的开关,还绘制了液晶接口以构成人机交互调试界面,还包括无线转串口接口作为辅助以方便车模进行在线调试。
主控电路原理图如图所示:
▲ 图 3.15 主控电路原理图
3.6 PCB设计
3.6.1 主控板PCB设计
主控板直接和底座匹配,上面有两个四路电磁运放的插座,还有电机驱动板的插座,这两个模块都可是随时插拔更换。
▲ 图3.16 主控板PCB图
3.6.2 四路电磁运放PCB设计
四路电磁运放使用了XH2.54的接口封装,方便从电磁杆直接采用5Pin的公公XH2.54接线连接,另外还设计了插针,可以直接插在主板上。
▲ 图3.17 四路电磁运放PCB图
3.6.3 电机驱动PCB设计
驱动板采取了双面设计,在上下两面都安置有电子器件,这样可以节省空间,节约主控板的面积,驱动板也设置了插针,可以直接插在主控板上。由于驱动板过电流比较大,电源供电接口采用XT30,最大过电流为30A,完全满足要求。
▲ 图3.18 电机驱动PCB图
3.6.4 电磁杆PCB设计
我们车模使用了六路电磁运放,一个长前瞻,一个短前瞻。信号接口采用XH2.54,方便引线给电磁运放模块。
▲ 图3.19 电磁杆PCB图
3.7 传感器的选择
3.7.1 OpenART摄像头
如下图,比赛所用的摄像头为OpenART摄像头,OpenART不仅可以很轻松的完成机器视觉应用,还可以完成OpenMV不能完成的神经网络模型的部署和训练。采用I.MX RT1064 芯片,其主频达到 600MHz,同时拥有1M片内SRAM、4M片内FLASH以及32M外置SDRAM,图像处理速度极快。支持两种供电方式,一种通过type C供电,可以直接电脑调试。另外一种是5V外部供电,该摄像头还可以有3V3的输出电压端,可以对外部传感器供电。
3.7.2 编码器
如下图光电编码器是一种通过光电转换将输出轴上的机械几何位移量转换为脉冲或者数字量的传感器,增量式编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。绝对值编码器为每一个轴的位置提供一个独一无二的编码数字值,单圈绝对值编码器把轴细分成规定数量的测量步,最大的分辨率为13位,这就意味着最大可区分8192个位置,我们使用的是9位的,可以区分512个位置。我们使用的光电编码器是绝对式编码器,这是目前应用最多的测速传感器之一。其获取信息准确,精度高、应用简单。该编码器的供电电压为3.3V-5V,可以满足不同IO口电平的单片机。
3.7.3 线性CCD
我们使用的线性CCD是一款基于TSL1401CL芯片设计的线阵CCD传感器模块,TSL1401CL 线性传感器阵列由一个 128 x 1 的光电二极管阵列,相关的电荷放大器电路和一个内部的像素数据保持功能组成,它提供了同时集成起始和停止时间的所有像素。我们常说的摄像头CCD模块通常使用的是面阵CCD芯片,一般以OV系列面阵CCD最为常用。而TSL1401属于线性CCD也叫做线阵CCD。与面阵CCD相比,我们用的这一款线性 CCD最明显的特点就是其只能采集一行128个可视像素点,我们使用线性CCD是用来扫描车库前面的斑马线的。
▲ 图3.20 面阵CCD与线性CCD对比
第四章 控制算法设计
高效的控制算法是智能车高速平稳行驶的基础。本次竞赛我们采用两组电磁前瞻进行赛道识别,电磁信号的采集处理和 PID 控制算法是整个程序的核心内容。在控制智能车的舵机和电机方面,分别使用了增量式和位置式 PID 控制算法,使智能车能够稳定快速在赛道中行驶。在此基础上,又通过线性CCD、电磁前瞻和摄像头对赛道中的特殊元素进行识别,使智能车能够完成各项任务。
4. 1 循迹算法
4.1.1 循迹流程
▲ 图4.1 循迹流程图
4.1.2 AD采集与滤波
滤波算法是智能车控制中不可或缺的一部分。它可以从含有干扰的接收信号中提取有用信号,很大程度上保证了采集到的信号的真实性和稳定性,在智能车采集电磁信号时,它是抑制和防止环境干扰的重要措施。
常见滤波方案有均值滤波、中值滤波、数值限幅、平滑滤波等,在进行AD采集时我们使用了均值滤波法。在读取ADC数据时,连续采样N次(实际调用时采样次数为5次)作为一组数据,去掉这组数据的最大值和最小值后再求平均。已将该功能封装成函数,如图4.2所示。
▲ 图4.2 ADC均值滤波
4.1.3 数据处理
由于需要通过分析各个电感采集值的情况来判定前方路况,所以要求这些采集值有迹可循且便于处理,归一化便是一种很好的方法。读取到电磁信号后,先对其进行归一化,然后再放大1000倍,最后进行限幅,将数据最小值限制为1,将数据映射到10~1范围之内,这样既能容易的由电磁值分析出路况,又不会因为数据过小而失真,还可以增强算法的适应能力。归一化公式中的最大最小值需要在赛道上提前测出,代码如图4.3所示。
▲ 图 4.3 归一化
数据归一化后,分别计算前后两个电磁前瞻的电磁信号的差比和,所使用的差比和公式为
█(error=(√(ele_L )-√(ele_R ))/(√(ele_L )+√(ele_M )+√(ele_R ))#公式3)
式中:
ele_L——左侧电感值 ele_R——右侧电感值
ele_M——中间电感值(由前端中间两个电感求平均所得)
该公式是在传统的差比和公式的基础上,通过实验测试改进得到,可以很大程度上增加电感信号的变化的线性度。代码如图4.4所示。
▲ 图 4.4 差比和计算
其中,所用到的非函数库的快速开平方算法,代码如图4.5所示。
▲ 图 4.5 快速开放算法
虽然较长的电磁前瞻能使智能车“看”得更远,但是会在过弯时会伸出赛道太多,所计算的差比和数值较小,会导致弯道处舵机打角不足而冲出赛道。所以增加了后端电磁前瞻弥补弯道处打角不足的问题。后端2个的电感离车模比较近,在过弯的时候差比和数值比较大,只要在前端电感偏出赛道之后切换到后端电感控制舵机占空比,就可以流畅地驶过弯道。代码如图4.6所示。
▲ 图 4.6 前后差比和切换
4.1.4 舵机控制
在工程实际中,应用最为广泛的调节器控制规律为比例、积分、微分控制,简称 PID 控制。PID 控制器是一种线性控制器,它根据给定值与实际输出值构成控制偏差。将偏差的比例§、积分(I)和微分(D)通过线性组合构成控制量,对被控对象进行控制,故称 PID 控制器,原理框图如图 4.7 所示。
▲ 图 4.7 PID控制原理图
PID控制分为很多种,常用的有增量式和位置式两种算法,对舵机进行控制时,我们先采用增量式算法计算输出增量∆u(k),再通过u(k)=u(k-1)+∆u(k)转换为位置式输出给舵机。这种算法计算量小,本质上具有抗积分饱和功能的优点,适合用于控制需要快速响应的舵机。
由于本次竞赛采用了双前瞻循迹方式,且前后两个电磁前瞻应用场景不同,故在计算输出占空比时采用了两套不同的PID参数。代码如图4.8所示。
▲ 图4.8 舵机PID控制
4.2 特殊元素判断
4.2.1 车库
我们采用线性CCD对车库进行识别。出库时,先让舵机保持直行状态,当线性CCD判定到第二条斑马线时(此时车模前端靠近车库边界),通过前轮转向和后轮差速就可以实现稳定出库。当车模转到赛道中央时就可以切换到正常行驶状态,既当电磁前瞻的电感值与在直道正常行驶时基本一致就表示出库完成。
▲ 图4.9 出库示意图
车模出库的状态直接决定其接下来的稳定性,因此设计时牺牲了一些出库的速度以保证车子的稳定性,代码如图4.10所示。
当第二次经过斑马线(智能车已行驶完两圈)时,再次通过前轮转向和后轮差速就可以实现稳定入库,同时开始定时停车,这样智能车就能平稳地停在车库里了。代码如图4.11所示。
▲ 图4.11 入库算法
虽然出入库都是使用线性CCD识别斑马线,但是两种情况下斑马线相对位置不同,故识别算法也不同。入库时斑马线的CCD图像如图4.12所示。
▲ 图4.12 斑马线CCD图像
识别时先将每次采集到的数据二值化,然后遍历所有数据,将白色点到连续黑色点的跳变处视为左侧边界,然后寻找黑色点到连续白色点的跳变处,将其视为右侧边界,若左右边界的距离与一条斑马线的宽度基本一致,则认为是斑马线,反之则舍弃,遍历剩下的点继续寻找斑马线,当找到的斑马线数量大于5条,则认为即将到达车库,开始入库。代码如图4.13所示。
▲ 图4.13 入库斑马线识别算法
出库时,若线性CCD图像中出现有效白线(黑色点小于10个)到有效黑线(黑色点大于40个)的跳变,则认为识别到一条斑马线。代码如图4.14所示。
▲ 图4.14 出库斑马线识别算法
4.2.2 环岛
设计时采用了状态机加定时的方法进入环岛,由实际测试可知,通过环岛与直道交汇处时,所有电感值会明显增大,靠近环岛一侧的电感值会突然增大再减小,在环岛与直道相切处达到最小,然后增大直至车模离开环岛范围。当电感值会出现明显的增大时(车模到达环岛边界),状态标志置1,准备进入环岛,当电感值再次增大时(车模到达环岛与直道相切处),状态标志置2,定时开始,前轮强制转向进入环岛,定时结束后正常循迹。准备出环岛时,需要向环岛内侧短暂转向,避免出环岛时车模因速度过快直接冲出赛道,然后屏蔽环岛识别标志,直到车模完全离开环岛。
▲ 图4.15 环岛示意图
4.2.3 三岔路口
因为在到达三岔路口前先经过环岛,且两者离得较近,为了能够在三叉路口处精准停车识别图片,故在出环岛后智能车减速。当智能车到达三岔路口时,前端的电磁前瞻中间两个电感远离赛道,所采集的电感值小于正常行驶时,两侧电感离三岔路口的两个分岔较近,电感值较大,而后端的电磁前瞻仍处于直道,没有明显变化,根据这一特点可以准确地识别出三岔路口。停车之后,依靠摄像头识别标靶确定方向,接收到摄像头数据后,前轮转向相应方向,继续前进。
▲ 图4.16 三岔路口示意图
4.3 速度闭环控制
后轮分别由两个PID闭环进行控制。控制电机的PWM信号的频率为10K ,占空比是五万份之x,可以通过设定x的值,控制电机输入电压,电机初始化函数如图4.15所示:
▲ 图4.17 电机初始化
经过实际测量,电机在满电压输入且空转的情况下,其编码器最大读数为330,我们将这一最大值作为满速,将其分为330份,每一份为1单位速度。因为左右两个编码器安装方向不同,故读取的脉冲数存在负数的情况,偏差计算公式也有所不同,相应的偏差计算代码如图4.16所示。
▲ 图4.18 求取速度偏差
根据计算的误差值,采用位置式PID控制算法,分别计算各环节数值,相加后即可得到期望的输出占空比,代码如图4.17所示。
▲ 图4.19 PID控制算法
但需要注意的是,计算出来的输出占空比有可能会超过最大占空比50000,所以需要对计算结果进行限幅。除此之外,当计算结果为负时(反转),硬件无法自动改变转动方向,需要依靠软件改变转动方向,即当计算结果为负时,交换控制该电机的两个PWM端口的输出值(为了便于控制,其中一个PWM端口占空比为0,另一个为期望占空比),输出计算结果的绝对值。
正常情况下,轮子转动的速度由设定值唯一确定且两边轮子的转速应当相同,但是因为C车车模体积较大,转向较为困难,所以我们还根据电磁信号计算出的前轮舵机转动占空比来进行差速转弯。
▲ 图4.20 差速算法
其中,0.005是比例系数,其值根据智能车的实际运行情况进行调整。因为我们的舵机的转动占空比范围是4300±500,所以两轮间的最大差速为5单位速度。
4.4 PID参数整定
运用 PID 控制的关键是调整 KP、KI、KD 三个参数,即参数整定。PID 参数的整定方法有两大类:一是理论计算整定法。它主要是依据系统的数学模型,经过理论计算确定控制器参数;二是工程整定方法,它主要依赖工程经验,直接在控制系统的试验中进行,且方法简单、易于掌握,在工程实际中被广泛采用。
而此处使用的是理论计算整定法中的稳定边界法进行的PID参数整定。
其具体整定过程如下:
在系统闭环的情况下,先将P取为较小值,I、D值置0,然后用上位机记录车在赛道上运行时的编码器读数。逐渐增大P的值直到编码器读数进入等幅震荡状态,记录下此时的临界比例增益KPcrit和震荡周期Tcrit,根据如下公式进行PID粗调:
▲ 图4.21 PID参数
由上公式计算得到PID参数后,再根据实际车辆运行过程中的编码器读数情况,适当调节比例、积分和微分环节的值,其规律大致为增大I则P需要减小,增大D则P需要增大。需要注意的是:增大I可以减小稳态误差,但容易产生积分饱和现象;增大D可以使系统对变化更敏感,即可在误差的产生时起到抑制作用,但在误差的消除阶段会对误差消除产生一定的阻碍;增大P可以让系统输出更容易达到设定值,即对误差更敏感,但也容易产生震荡。
第五章 图像识别设计
5.1 AprilTag识别
AprilTag的识别方法参考了openMV官方中文文档中的例程,其识别函数如下:
▲ 图5.1 AprilTag识别函数
代码逻辑如下:读取一张摄像头图片,通过模板匹配,寻找图片中是否存在AprilTag的25h9系列的码,如果存在,用红框框出码的位置,并在码的中央显示绿色十字,由于后面需要根据码的奇偶转动摄像头,后面的if语句将奇数和偶数的识别结果通过三个GPIO口的高低电平传送给主板单片机,并通过另一个GPIO口的上升沿触发主板单片机的中断,最后实现数据的交互。其中,degress变量可以得到图片的旋转角度,但在本程序中暂时没有使用。为了增加识别的准确度和滤除一些背景对识别的干扰,所以代码只有在识别到三次Apriltag后才会将第三次的结果作为输出。
5.2 数字识别
数字识别代码如下:
▲ 图5.2 数字识别代码
本代码中,参考了部分openMV官方中文文档的办法用来识别数字的紫色外框。
代码逻辑如下:读取一张摄像头图片,然后在图片中搜索矩形,找到矩形后,提取出矩形内的图片,将该图片输入到数字十分类模型中,如果识别的结果准确率大于百分之75,就存储该结果(奇或者偶,0或者1)到数组中,当得到了三次结果后,将其中至多的结果作为识别结果,通过三个GPIO口的高低电平传送给主板单片机,并通过另一个GPIO口的上升沿触发主板单片机的中断,最后实现数据的交互。其中,为了排除不同颜色光线对识别的影响,在训练模型的时候使用的是灰度图片,输入模型的图片也需要转换为灰度图;为了让结果更准确,所以在此处使用的是三次结果取其最大值。
5.3 动物水果识别
动物水果识别代码如下:
▲ 图5.3 动物水果识别代码
本代码中,参考了部分openMV官方中文文档的办法用来识别动物水果的紫色外框。
代码逻辑如下:读取一张摄像头图片,然后再图片中搜索矩形,找到矩形后,提取出矩形内的图片,将该图片输入到动物水果十分类模型中,如果识别的结果准确率大于百分之75,就存储该结果(动物或者水果,0或者1)到数组中,当得到了三次结果后,将其中至多的结果作为识别结果,通过三个GPIO口的高低电平传送给主板单片机,并通过另一个GPIO口的上升沿触发主板单片机的中断,最后实现数据的交互。为了让结果更准确,所以在此处使用的是三次结果取其最大值。
5.4 关于紫框的识别问题
关于openMV官方的识别矩形方式,经过多次尝试后发现,如果用于识别要求的紫色外框,效果较差,其识别效果受到光强,摄像头角度,图片距离等多方面因素的影响较大。使用openMV的官方识别矩形方式,经常出现错误识别矩形,识别不到需要图片紫色矩形外框等问题,调节其参数threshold也无法解决这一问题,只能在错误识别和识别不到之间徘徊。所以关于识别紫色外框,在参考了openMV的更底层的示例后,我们使用了色块识别的办法来作为备用的识别紫色外框方案,其代码如下:
▲ 图5.4 色块识别代码
其原理为:
先通过阈值编辑器分别得到紫框在强、弱、正常光环境下的LAB范围,然后在拍摄的图片中将阈值内和阈值外的颜色分成黑白两色,白色是阈值内的颜色区域,当阈值内的颜色像素点数量大于某个值时,该区域将会被选择。通常一张图会有多个阈值内颜色像素点富集的区域,这些区域都会被记录,因为我们需要识别的是外框,而且图片与摄像头的距离限制了外框的大小,所以通过一个位置,大小和密度的三重滤波后,就可以准确地得到紫色外框的位置了。
其识别结果如下:
▲ 色块识别代码
可以看到,即使在黑暗的情况下,其仍能较好的框出图片的位置,但该方法的问题是有的情况下可能框选的区域较大,但对于我们的模型来说,影响并不大,但是其三个光强下的LAB范围都需要具体测量,操作较为复杂。
5. 5 关于数字和动物水果的模型
5.5.1 数字模型
对于数字识别,因为较为简单,所以我们在lenet模型的基础上进行改进,且使用的是32*32的灰度图片作为训练测试集。
训练代码见附录train.py。
可以看到,在lenet的基础上,我们在第二个卷积层下加入了BatchNormalization层,以减缓其梯度下降,我们还在全连接层加入了传统的Dropout层,随机丢弃一些点以增加其泛化能力。
▲ 图5.6 数字模型训练结果
在0.001的学习率和10%的下降速率之下,其训练和实际测试效果良好,在测试集下其准确度达到了91%,在实际的使用过程中,几乎没有识别错误的情况发生。
5.5.2 动物水果模型
因为动物水果识别相较于数字识别更为复杂,所以这里使用的模型仍是修改过后的lenet模型,但训练测试图片是32*32的彩色图片。其训练代码与数字模型的训练代码大体相同。
▲ 图5.7 动物水果模型训练结果
在0.001的学习率和10%的下降速率之下,其训练和实际测试效果良好,在测试集下其准确度达到了99%,在实际的使用过程中,识别错误的情况发生较少。
5.6 关于数字和动物水果的训练测试图片集
5.6.1 数字训练图片集
数字的基本图片是在十张数字图片的基础下加上紫色外框形成的,如下图:
▲ 图5.8 数字基本图片
对基本图片,将进行旋转、平移、缩放、加入高斯噪声等操作进行数据增强,例如下图:
▲ 图5.9 处理后数字图片
最终,我们将测试图片控制在三万张,即每个类三千张图片;训练图片控制在总量十万张,即每个类一万张图片,虽然得到的图片是彩色的,但在训练之前,在将其转变为.npy图片压缩文件时我们会将其转化为灰度图。具体进行图像增强的代码将在文末贴出。
5.6.2 动物水果训练图片集
基础的动物水果的图片也是在基础数据集的基础上先加上紫色外框,如下图:
▲ 图5.10 动物水果基本图片
对基本图片,将进行旋转、平移、缩放、滤镜、曝光、加入各种噪声等操作进行数据增强,例如下图:
▲ 图5.11 动物水果处理后的图片
最终,我们将训练的图片控制在四十万张,即每个类四万张;测试图片控制在五万张,即每个类五千张。具体代码将在文末给出。
此外,数据增加的代码在编写过程中经常会出现小bug,而这时往往已经产生了大量的小图片,而win系统的删除机制是先得到所有文件的信息后才进行删除,这样删除速度非常慢,为了快速删掉得到的大量小图片,我们还使用了多进程删除的特殊方式进行图片删除,该部分代码也将在文末给出。
第六章 系统调试
6. 1 开发调试工具
6.1.1 主板开发调试工具
软件开发工具选用的是IAR 8.4,IAR Embedded Workbench是瑞典IAR Systems公司为微处理器开发的一个集成开发环境,支持ARM,AVR,MSP430等芯片内核平台。IAR 8.4具有入门简单、使用方便、功能丰富等特点,它为用户提供一个易学和具有最大量代码继承能力的开发环境。其能够有效提高用户的工作效率,可以大大节省软件调试时间。
▲ 图6.1 IAR软件界面
IAR 8.4有一些功能十分实用,例如联调功能:在对单片机进行调试、烧录后,如果此时你没有断开单片机和电脑的物理连接,其将会进入如下界面,你可以选择重新调试并烧录、重新调试并不烧录、结束调试、运行程序、暂停程序、只执行一句、执行到断点等等功能,而且还能在右方实时查询当前某个变量的值。这些功能大大缩短了程序员的程序测试时间,可以方便的寻找到bug的位置。
▲ 图6.2 IAR烧录界面
不仅如此,IAR 8.4还能动态的显示想要观察的变量的当前值,只需要在调试的时候点击View,选择Live Watch,如下图:
▲ 图6.3 IAR变量值动态显示选择过程
之后就会在屏幕右边出现如下框,点击add处输入变量名称即可得到变量的实时数值。
▲ 图6.4 变量值动态显示
6.1.2 图像识别调试工具
模型的训练,图片的处理使用的是Python3.6,使用Python可以方便的调用集成的Keras库和TensorFlow训练库,大大方便了模型的搭建的训练,Python中使用到的库如下图:
▲ 图6.5 Python3.6图像识别所需库
在模型训练完毕后,需要将模型搭载到OpenART mini中,由于单片机内存限制,需要对模型进行量化处理:
▲ 图6.6 量化表
因为将float类型数据量化为了8位int型数据,不仅使得模型占用的存储空间变小,还增强了模型的泛化能力,这里使用的量化工具是NNCU模型转换器:
▲ 图6.7 NNCU模型转换器
该工具不仅可以简单方便的调整量化的移位指数,输入的数据的分数位,输出的类型,增加随机波纹以增加模型的泛化能力,还能进行启发式量化,而且,这个工具还能方便的查看模型的结构。启发式量化大大增加的量化后的模型的识别准确度,但要注意的是这需要准备好合适的迷你数据集。
6.1.3 软件调试
在软件调试的过程中,我们也遇到了许多的问题。
比如在转弯问题上,我们曾遇到过一个奇怪的现象。在计算舵机转向占空比的时候,需要用到浮点数,而舵机转向占空比的变量是一个无符号整数,计算的过程中需要浮点数的整数相加。在C语言中,浮点数和整数相加整数会自动转为浮点数,整数加浮点数对计算结果没有影响,但是经过一步步测试,在实际运行过程中,几千的整数加上几百的浮点数的结果却是零,将浮点数强转为整数后,得到的数却是正常的,这是个很奇怪的现象,我们暂时还没找到原因。
还有就是环岛的识别。一开始的时候,我们的环岛是直接通过电磁前瞻,在检测到某个电磁值后,就直接强打方向进入环岛,但是后来我们发现,环岛的特征电磁值不是唯一的,车在左偏线或者右偏线的情况下,其环岛特征是不同的,而且因为环岛的电磁信号是对称的,所以车经常会提前识别到环岛,然后撞到环岛内侧路肩上。所以最后,我们的环岛触发选用了三组(左偏线、在线上、右偏线)电磁信号,而且为了能在需要的位置强打方向,我们还加了状态机,分别对应了识别到环岛,到转向位置强打方向,进入环岛三个状态。
除了在赛道上运行遇到的问题,在图像识别部分也遇到了许多问题。
在量化模型的时候,一开始的量化效果总是不好,在实际识别时十分类永远只输出一个类,而这些识别错误的图片在原模型中大都能正确识别。后来发现,这是因为量化的范围设定的太小,许多地方的输出结果都超出了期望的范围,最终使得量化后模型的一些层饱和了。
还有就是紫色外框的识别问题,在之前的规则中,动物水果标靶是在二维码前后一定范围内,所以需要智能车向前运行寻找紫色外框。在实际运行时,往往出现智能车从标靶旁跑过却识别不到紫色外框,从而无法进行下一环节的情况。在尝试了包括打开摄像头的白光灯,修改摄像头曝光值,打开大灯等方法后,都没有得到一个稳定的结果,最终我们开始尝试根OpenMV中文文档中的色块寻找方法,写一个基于LAB的紫框识别函数,最终较好的解决了这一问题。
第七章 模型车主要技术指标
名称 小车参数
- 车模重量 1.540kg
- 车模长度 56cm
- 车模宽度 27cm
- 车模高度 30cm
- 饲服电机个数 0个
- 传感器种类及个数 OpenART摄像头,线性CCD,两个512线编码器
- 电容总容量 1633.32uf
- 赛道信息检测精度 8位
- 赛道信号频率 200Khz
结论
自报名参加全国大学生智能汽车竞赛以来,我们小组成员多途径寻找资料,查找知乎、CSDN、中国知网等学术平台,设计机械结构、组装车模、商量循迹方案、团队分工等,最终定下了现在这个设计方案。
在我们这份技术报告中,简要地介绍了我们在调试车模的基本思路。包括机械设计、电路设计、控制算法研究以及图像识别模型的训练等方面。在机械方面,我们分析了舵机转向系统的改进问题,后来对前轮倾角进行了一系列改进。另外还分析了摄像头摇头舵机的安装方案,最终决定了采用连轴结构,通过下面的舵机来带动连杆转动,连杆带动摄像头转动。在电路设计方面,我们以模块化设计,最小系统主控板、六路电磁运放、电感杆、电机驱动模块等模块分别设计,主控板经历了五次更新换代,最后的方案是将驱动板和四路电磁运放以插座的方式插在主控板上,这样可以让整个车布局简约,另外当某个模块坏了,还可以很方便更换电路板,方便硬件调试。控制算法研究方面,我们采用C语言编程,使用IAR作为调试工具,结合无线模块和人机交互TFT显示屏来简化调试过程,终于设计出了一套比较稳定的控制程序。在图像识别方面,我们使用的是Python语言进行编程,利用win下的Python开发程序,最终实现了从图像增强到模型搭建,模型训练参数调整,再到模型的量化以及根据实测结果进一步调整模型和训练参数的整个流程。最终,我们的模型实现了对大部分图像的准确分类,达到了很好的识别效果。最终,我们的小车能够稳定地行驶完整个赛道。
经历了这次比赛,我们也学习到了很多,不光是知识,还有解决问题的思路,当然也磨练了我们的意志,我们也学会了团队合作,相信这些都会对以后的学习生涯起到很好的帮助。
在备战的过程中,我们遇到了很多挫折,比如技术瓶颈,多次尝试无果,但是又在一次次熬夜调试过程中看到了新机,一次次智能车校赛见证了我们这个智能车小队一次次艰难的成长。非常感谢学院在场地和经费方面的大力支持,更要特别感谢默默付出,不求回报,一直坚持传承海大智能车精神的负责人。同时也感谢大赛组委会能组织这样一项有技术含量、十分有意义的比赛。
参考文献
[1]颜循进.恩智浦杯全国智能车竞赛之PID调节[J].科技风,2020(10):263
[2]曾子雄,廖常浩,吴洪清.去耦电容在PCB电磁兼容设计中的应用[J].安全与电磁兼容,2021(01):81-84.
[3]陈皓,桂伟.基于Altium Designer平台的防盗报警电路设计[J].电子制作,2020(23):86-87+93.
[4]张铮,徐超,任淑霞,韩海玲.数字图像处理与机器视觉[M].人民邮电出版社,201405.59
[5]吴祥,王冠凌.基于电磁导航智能车的控制研究及实现[J].四川理工学院学报(自然科学版),2015,28(06):42-46.
[6]卢京潮.自动控制原理[M].西安:西北工业大学出版社,2009
[7]朱昌平,李永强,单鸣雷.“飞思卡尔”智能车常见技术问题与解决方案[J].实验室研究与探索,2012,31(04):45-49+57.
[8]王娟,华东,罗建平. Python编程基础与数据分析[M].南京大学出版社:信息素养文库, 201908.219.
[9]鲁道夫·保林.TensorFlow机器学习项目实战[M].人民邮电出版社,201711.204.
■ 附录A:算法核心代码
主函数
void camera_init()
{
flag11=4;
gpio_init(date_l,GPI,0,FAST_GPIO_PIN_CONFIG);//低
gpio_init(date_m,GPI,0,FAST_GPIO_PIN_CONFIG);
gpio_init(date_h,GPI,0,FAST_GPIO_PIN_CONFIG);//高
gpio_init(sent_int,GPO,1,FAST_GPIO_PIN_CONFIG);//发送中断
gpio_init(allow,GPO,0,FAST_GPIO_PIN_CONFIG);//允许信号
gpio_interrupt_init(get_int,RISING,GPIO_INT_CONFIG);
}
void SendImageData(void)
{
uint8 i = 0;
uart_putchar(USART_1,0x00);
uart_putchar(USART_1,0xff);
uart_putchar(USART_1,0x01);
uart_putchar(USART_1,0x00);
for(i=0; i<128; i++) //数据
{
uart_putchar(USART_1,ccd_data[i]>>8); //发送高8位
uart_putchar(USART_1,ccd_data[i]&0XFF); //发送高低8位
}
}
int main(void)
{
DisableGlobalIRQ();
board_init();//初始化MPU 时钟 调试串口
systick_delay_ms(300); //延时300ms,等待主板其他外设上电成功
pit_init(); //定时器模块初始
pit_interrupt_ms(PIT_CH0, 5);
elec_init();
encoder_init();
lcd_init();
motor_init();
ccd_init();
EnableGlobalIRQ(0); //总中断开启
camera_init();
gpio_init(D14, GPO, 1, GPIO_PIN_CONFIG);//C13
gpio_init(D15, GPO, 1, GPIO_PIN_CONFIG);//C14
while (1)
{ renwu();
systick_delay_ms(1000);
lcd_clear(WHITE);
snprintf(send_buff1, 5, "%d", stcsc);
snprintf(send_buff2, 5, "%d", flagdelay);
snprintf(send_buff3, 5, "%d",numa);
snprintf(send_buff4, 5, "%d", loop);
snprintf(send_buff5, 5, "%d", (int)im_speed1);
snprintf(send_buff6, 4, "%d", (int)im_speed2);
snprintf(send_buff7, 6, "%d", (int)(duoji_data));
snprintf(send_buff15, 5, "%d", flagsc);
snprintf(send_buff8, 5, "%d", flagh);
snprintf(send_buff9, 4, "%d", num);
snprintf(send_buff10, 4, "%d", ad3);
snprintf(send_buff11, 4, "%d", flagrw);
snprintf(send_buff12, 4, "%d", flag);
snprintf(send_buff13, 4, "%d", dianci);
snprintf(send_buff14, 5,"%d", flag11);
lcd_showstr(0,0,"sc:");
lcd_showstr(40,0,send_buff1);
lcd_showstr(80,0,"fd:");
lcd_showstr(120,0,send_buff2);
lcd_showstr(0,1,"nma:");
lcd_showstr(40,1,send_buff3);
lcd_showstr(80,1,"lp:");
lcd_showstr(120,1,send_buff4);
lcd_showstr(0,2,"ip1:");
lcd_showstr(30,2,send_buff5);
lcd_showstr(80,2,"ip2:");
lcd_showstr(120,2,send_buff6);
lcd_showstr(0,3,"dd:");
lcd_showstr(40,3,send_buff7);
lcd_showstr(100,3,"fc:");
lcd_showstr(100,4,send_buff15);
lcd_showstr(0,4,"fh:");
lcd_showstr(40,4,send_buff8);
lcd_showstr(0,5,"num:");
lcd_showstr(40,5,send_buff9);
lcd_showstr(80,5,"ad3:");
lcd_showstr(120,5,send_buff10);
lcd_showstr(0,6,"frw:");
lcd_showstr(40,6,send_buff11);
lcd_showstr(80,6,"fg:");
lcd_showstr(120,6,send_buff12);
lcd_showstr(0,7,"dc:");
lcd_showstr(40,7,send_buff13);
lcd_showstr(80,7,"f11:");
lcd_showstr(120,7,send_buff14);
}
}
#motor.c
//------------------------------------------------------------------------------
//电机初始化
//------------------------------------------------------------------------------
void motor_init()
{
//占空比设定的满值为50000
pwm_init (PWM_0, 10000, 0);
pwm_init (PWM_1, 10000, 10000);
pwm_init (PWM_2, 10000, 10000);
pwm_init (PWM_3, 10000, 0);
}
//------------------------------------------------------------------------------
//电机控制
//------------------------------------------------------------------------------
void motor_control()
{
//电机1//
if(im_speed1==0)oim_speed1=im_speed1;
else oim_speed1=im_speed1-(duty_over-4300)*0.005;
encoder_speed();
error_motor1=(oim_speed1+encoder1)/330*50000;//求e
//计算速度环P项偏差
SpeedError_P_1 = EP * error_motor1;
//计算速度环I项偏差
SpeedError_I_1 += EI*error_motor1;
//计算速度环D项偏差
SpeedError_D_1 = ED * (error_motor1 - SpeedError_Last_1);
//更新上一次偏差数据
SpeedError_Last_1= error_motor1;
SpeedOutput_1 = (int)(SpeedError_P_1 + SpeedError_I_1 + SpeedError_D_1);//需调整
//限副
if(SpeedOutput_1 >= 50000)
SpeedOutput_1 = 50000;
else if(SpeedOutput_1 <= -50000)
SpeedOutput_1 = -50000;
//方向
if(SpeedOutput_1>=0)
{
pwm_duty(PWM_1, SpeedOutput_1);
pwm_duty(PWM_0, 0);
}
else
{
pwm_duty(PWM_1, 0);
pwm_duty(PWM_0, abs(SpeedOutput_1));
}
if(im_speed2==0)oim_speed2=im_speed2;
else oim_speed2=im_speed2+(duty_over-4300)*0.005;
//电机2
error_motor2=(oim_speed2-encoder2)/330*50000;
//计算速度环 P项偏差
SpeedError_P_2 =EP * error_motor2;
//计算速度环 I项偏差
SpeedError_I_2 += EI*error_motor2;
//计算速度环D项偏差
SpeedError_D_2 =ED * (error_motor2 - SpeedError_Last_2);
SpeedError_Last_2= error_motor2;//更新上一次偏差数据
SpeedOutput_2 = (int)(SpeedError_P_2 + SpeedError_I_2 + SpeedError_D_2);//需调整
//限副
if(SpeedOutput_2 >= 50000)
SpeedOutput_2 = 50000;
else if(SpeedOutput_2 <= -50000)
SpeedOutput_2 = -50000;
//方向
if(SpeedOutput_2>=0)
{
pwm_duty(PWM_2, SpeedOutput_2);
pwm_duty(PWM_3, 0);
}
else
{
pwm_duty(PWM_2, 0);
pwm_duty(PWM_3, abs(SpeedOutput_2));
}
}
//------------------------------------------------------------------------------
//编码器初始化
//------------------------------------------------------------------------------
void encoder_init()
{
qtimer_quad_init(QTIMER_1,QTIMER1_TIMER0_C0,QTIMER1_TIMER1_C1);//初始化 QTIMER_1 A相使用QTIMER1_TIMER0_C0 B相使用QTIMER1_TIMER1_C1
qtimer_quad_init(QTIMER_1,QTIMER1_TIMER2_C2,QTIMER1_TIMER3_C24);//初始化 QTIMER_1 A相使用QTIMER1_TIMER2_C2 B相使用QTIMER1_TIMER3_C24
encoder1=0;
encoder2=0;
}
//------------------------------------------------------------------------------
//编码器读数
//------------------------------------------------------------------------------
void encoder_speed()
{
encoder1 = qtimer_quad_get(QTIMER_1,QTIMER1_TIMER0_C0 ); //读取编码器的值,这里需要注意第二个参数务必填写A相引脚
encoder2 = qtimer_quad_get(QTIMER_1,QTIMER1_TIMER2_C2 );
qtimer_quad_clear(QTIMER_1,QTIMER1_TIMER0_C0 ); //QTIMER正交解码计数清零
qtimer_quad_clear(QTIMER_1,QTIMER1_TIMER2_C2 );
}
#CDD.c
//-------------------------------------------------------------------------------------------------------------------
// TSL1401线阵CCD初始化
//-------------------------------------------------------------------------------------------------------------------
void ccd_init(void)
{
adc_init (ADC_2,AD_CHANNEL,ADC_12BIT); //B0 1号
gpio_init(CCD_CLK_PORT, GPO, 1,GPIO_PIN_CONFIG); //CLK
gpio_init(CCD_SI_PORT, GPO, 1,GPIO_PIN_CONFIG); //SI
pit_interrupt_ms(PIT_CH1, EXPOSURE_TIME); //定时EXPOSURE_TIME(ms) 后中断
CCD_CLK(1);
CCD_SI(0);
}
//-------------------------------------------------------------------------------------------------------------------
// TSL1401线阵CCD数据采集
//-----------------------------------------------------------------------------------------------------------------
void ccd_collect(void)
{
uint16 j = 0;
CCD_CLK(1);
CCD_SI(0);
CCD_SI(1);
CCD_CLK(0);
CCD_CLK(1);
CCD_SI(0);
for(j=0;j<128;j++)
{
CCD_CLK(0);
ccd_data[j] = adc_convert(ADC_2,AD_CHANNEL);
CCD_CLK(1);
}
}
//-------------------------------------------------------------------------------------------------------------------
// CCD数据处理
//-------------------------------------------------------------------------------------------------------------------
void cdd_datax(void)
{
int jLeft,iTmp,jRight,i,c=0;
static int j= 300,erR;
for(i=0;i<128;i++)
{
if(ccd_data[i]>1200) //阈值 3350
ccd_data1[i] = 1;
else
ccd_data1[i] = 0;
}
if(flags==0)
{
for(i=0;i<128;i++)
{
c=c+ccd_data1[i];
}
switch(flagc)
{
case 0:
{
if (c>100) //黑块宽度小于10才判定为有效的白线
{
flagc=1;
number1++;
}
}break;
case 1:
{
if (c<85) //黑块宽度大于40才判定为有效的黑线
{
flagc=0;
}
}break;
}
if(number1>5)
{
flags=1;
number1=0;
}
}
else if(flags==2 && loop==4)
{
if(j>250)
{
for (i = 0; i <= 110; i++)
{
if((ccd_data1[i]==1)&&(ccd_data1[i+1<128?(i+1):127]==1)&&(ccd_data1[i+2<128?(i+2):127]==0)&&(ccd_data1[i+3<128?(i+3):127]==0)&&(ccd_data1[i+4<128?(i+4):127]==0))
{
jLeft = i + 2; //黑块左侧位置
iTmp = jLeft;
for (; iTmp <= 123; iTmp++)
{
if ((ccd_data1[iTmp] ==0) && (ccd_data1[iTmp + 1] ==0) && (ccd_data1[iTmp + 2] ==1) && (ccd_data1[iTmp + 3] ==1) && (ccd_data1[iTmp + 4] ==1))
{
jRight = iTmp + 1; //黑块右侧问题
break;
}
}
if (jRight - jLeft > 13) //黑块宽度大于2才判定为有效的起跑线
{
number2++;
}
}
}
if(number2>=2)
{
flags=4;
j=0;
}
number2=0;
}
else
j++;
}
}
#elec.c
//------------------------------------------------------------------------------
//快速计算开方
//------------------------------------------------------------------------------
float invSqrt(float x)
{
float halfx = 0.5f * x;
float y = x;
long i = *(long*)&y;
i = 0x5f3759df - (i>>1);
y = *(float*)&i;
y = y * (1.5f - (halfx * y * y));
return 1/y;
}
//------------------------------------------------------------------------------
//AD端口初始化
//------------------------------------------------------------------------------
void elec_init()
{
adc_init(ADC_1,ADC1_CH3_B14,ADC_8BIT); //初始化AD1
adc_init(ADC_1,ADC1_CH4_B15,ADC_8BIT); //初始化AD6
adc_init(ADC_1,ADC1_CH8_B19,ADC_8BIT); //初始化AD4
adc_init(ADC_1,ADC1_CH10_B21,ADC_8BIT); //初始化AD5
adc_init(ADC_1,ADC1_CH12_B23,ADC_8BIT); //初始化AD2
adc_init(ADC_1,ADC1_CH5_B16,ADC_8BIT); //初始化AD3
adc_init(ADC_1,ADC1_CH6_B17,ADC_8BIT); //初始化AD7
adc_init(ADC_1,ADC1_CH7_B18,ADC_8BIT); //初始化AD8
pwm_init(S_MOTOR_PIN,50,4300); //初始化转向舵机引脚
pwm_init(S_MOTOR_PIN1,50,3800); //初始化摄像头舵机引脚
}
void ad_collect()
{
//读取AD值
ad6=adc_mean_filter(ADC_1,ADC1_CH8_B19,5);
ad2=adc_mean_filter(ADC_1,ADC1_CH4_B15,5);
ad1=adc_mean_filter(ADC_1,ADC1_CH3_B14,5);
ad5=adc_mean_filter(ADC_1,ADC1_CH5_B16,5);
ad4=adc_mean_filter(ADC_1,ADC1_CH12_B23,5);
ad3=adc_mean_filter(ADC_1,ADC1_CH10_B21,5);
ad7=adc_mean_filter(ADC_1,ADC1_CH6_B17,5);
ad8=adc_mean_filter(ADC_1,ADC1_CH7_B18,5);
ad1<AD1min && (ad1=AD1min);
ad2<AD2min && (ad2=AD2min);
ad3<AD3min && (ad3=AD3min);
ad4<AD4min && (ad4=AD4min);
ad5<AD5min && (ad5=AD5min);
ad6<AD6min && (ad6=AD6min);
ad1>AD1max && (ad1=AD1max);
ad2>AD2max && (ad2=AD2max);
ad3>AD3max && (ad3=AD3max);
ad4>AD4max && (ad4=AD4max);
ad5>AD5max && (ad5=AD5max);
ad6>AD6max && (ad6=AD6max);
//归一化
ele_R=1000.0*(ad4-AD4min)/(AD4max-AD4min);
ele_R=ele_R < 1.0?1.0:ele_R;
ele_L=1000.0*(ad1-AD1min)/(AD1max-AD1min);
ele_L=ele_L < 1.0?1.0:ele_L;
ele_M=500.0*(ad3-AD3min)/(AD3max-AD3min)+500.0*(ad2-AD2min)/(AD2max-AD2min);
ele_M=ele_M < 1.0?1.0:ele_M;
ele_L1=1000.0*(ad5-AD5min)/(AD5max-AD5min);
ele_L1=ele_L1 < 1.0?1.0:ele_L1;
ele_R1=1000.0*(ad6-AD6min)/(AD6max-AD6min);
ele_R1=ele_R1 < 1.0?1.0:ele_R1;
//前排电感差比和
error_AD1 = 1000*((invSqrt(ele_L)-invSqrt(ele_R))/(invSqrt(ele_M)+invSqrt(ele_L)+invSqrt(ele_R))) ;
//后排电感差比和
error_AD2 =1000*((invSqrt(ele_L1) -invSqrt(ele_R1))/(invSqrt(ele_L1)+invSqrt(ele_R1)+invSqrt(ele_M))) ;
//前后排比较
if(((ad1+ad4)<140 || fabs(ad1-ad4)>70))
{
error_AD = error_AD2;
flagqh=0;
}
else
{
error_AD = error_AD1;
flagqh=1;
}
}
void huandao()
{
switch(flagh)
{
case 0:
{ //和小于300或差大于50 ad15
if((ad1>265&&ad2>265&&ad3>265&& ad4<255 && ad5>265 && ad6<255 )
{
flagh=1;
}
break;
}
case 1:
{
if(ad1>120 && ad1<200)
{
i=0;
flagh=2;
}
break;
}
case 2:
{
if(i>=100)
{
flagh=4;
}
break;
}
case 4:
{
if(ad1>150 )
{
flagh=5;
i=0;
}
break;
}
case 5:
{
if(i>400 )
{
im_speed1=15;
im_speed2=15;
flagh=0;
}
break;
}
}
}
void elec_control()
{
if(flagqh)
{
if(error_AD>=0)
{
duty_over =(duty + error_AD *ep1 +(error_AD - LastSensorDeviation)* ed1);//比例项控制
}
else if(error_AD<0)
{
duty_over =(duty + error_AD *ep1+(error_AD - LastSensorDeviation)* ed1);//比例项控制
}
LastSensorDeviation = error_AD;//上一次偏差数据更新 .
}
else
{
if(error_AD>=0)
{
duty_over =(duty + error_AD *ep2 +(error_AD - LastSensorDeviation)* ed2);//比例项控制
}
else if(error_AD<0)
{
duty_over =(duty + error_AD *ep2+(error_AD - LastSensorDeviation)* ed2);//舵机转向比例项控制
}
LastSensorDeviation = error_AD;//上一次偏差数据更新 .
}
}
//------------------------------------------------------------------------------
//舵机控制
//------------------------------------------------------------------------------
void control_duoji()
{
if(dianci==0)
{
duty_over=duty_over>4800?4800:duty_over;
duty_over=duty_over<3800?3800:duty_over;
if(flags==0) //打直慢速出库
{
im_speed1=10,im_speed2=10;
pwm_duty(S_MOTOR_PIN,4300);
}
else if(flags==1) //强转等待转到线上
{
if(ad2>130&&ad3>130&&abs(ad3-ad2)<20)
{
qj=0;
im_speed1=aim_speed1,im_speed2=aim_speed2;//速度还原
flags=2;
}
pwm_duty(S_MOTOR_PIN,3800);
}
else if(flags==2) //已经出库
{
if(qj<600)qj++;
if(qj>500)
{
if(stcsc!=1 && stcsc!=2 && ad1<40 && ad2<40 && ad3<40 && ad4<40 && ad5<150 && ad5>50 && ad6<150 && ad6>50 )//出环后第一次识别到三叉,停车打直
{
flagsc=1;
duty_over=4300;
im_speed1=0;
im_speed2=0;
switch(stcsc)
{
case 0:stcsc=1;gpio_set(allow,1);
break; //第一次进入三叉
case 3:stcsc=5;break; //识别到奇数且准备出三叉
case 4:stcsc=6;break; //识别到偶数且准备出三叉
case 9: //第二圈三叉
gpio_set(allow,1);
if(numa==1)number(0); //第一圈识别到偶数左转,现在第二圈右转
else if(numa==2)number(1); //第一圈识别到奇数右转,现在第二圈左转
break;
}
}
if(flagsc==1) //识别到三叉
{
if(stcsc==1) //停车转动摄像头寻找数字
{
duty_over=4300;
static int find=50;
static int fx=1;
static int y=0;
y++;
if(y>50)
{
y=0;
control_duoji1(find);
if(fx==1)find=find+1;
else find=find-1;
if(find>130)fx=0;
else if(find<50)fx=1;
}
}
else if(stcsc==3) //慢速离开三叉并回到线上,计时离开三叉
{
//duty_over=3900;
gpio_set(allow,0);
stcsc=7;
}
else if(stcsc==4)
{
//duty_over=4700;
//im_speed1=aim_speed1,im_speed2=aim_speed2;
stcsc=7;
}
else if(stcsc==7 && (ad1+ad4)>240) //计时足够后离开三叉并将速度还原
{
stcsc=8;
flagsc==0;
im_speed1=aim_speed1,im_speed2=aim_speed2;
i=0;
//gpio_set(allow,1);
}
else if(stcsc==8 && numa==2 && i>1500)
{
im_speed1=5,im_speed2=5;
gpio_set(allow,1);
stcsc=9;
}
else if(stcsc==8 && numa==1 && i>1000)
{
im_speed1=5,im_speed2=5;
gpio_set(allow,1);
stcsc=9;
}
}
}
if(flagh==2) //如果进入环岛强行右打
pwm_duty(S_MOTOR_PIN,3850);
else if(flagh==4)
pwm_duty(S_MOTOR_PIN,3800); //出环岛强行右打
else
pwm_duty(S_MOTOR_PIN,duty_over); //正常寻线
}
else
{
static int rk=0;
rk++;
im_speed1=40;
im_speed2=5;
if(rk>150)
{
im_speed2=0;
im_speed1=0;
}
pwm_duty(S_MOTOR_PIN,3800);
}
}
else
{
pwm_duty(S_MOTOR_PIN,duty_over1); //特殊状态处理
}
}
//------------------------------------------------------------------------------
//摄像头舵机控制
//------------------------------------------------------------------------------
void control_duoji1(uint32 duty) //左转1900-5400
{
duty=(1.0*duty/90-1)*1600+3800;
if(duty<1900)
duty=1900;
else if(duty>5500)
duty=5500;
duoji_data=duty;
pwm_duty(S_MOTOR_PIN1,duty);
}
void control_duoji1_2(uint32 duty) //右转
{
if(duty<1900)
duty=1900;
else if(duty>5500)
duty=5500;
duoji_data=duty;
pwm_duty(S_MOTOR_PIN1,duty);
}
//****************************************************************************************************************************
//识别操作相关的函数
//****************************************************************************************************************************
float im_speed3,im_speed4;
int loop=0;
extern float aim_speed1,aim_speed2;
void renwu()
{
if(flagdelay==0)return;
else if(flagdelay==1) //Apriltag
{
im_speed1=-5,im_speed2=-5;//倒退
systick_delay_ms(3000);
control_duoji1(180);//右90度
gpio_set(sent_int,!now);
now=!now;
im_speed1=5,im_speed2=5; //低速前进
flagdelay=0;
}
else if(flagdelay==2) //动物
{
control_duoji1(0);//左90度
gpio_set(sent_int,!now);
now=!now;
im_speed1=0,im_speed2=0;
systick_delay_ms(3000); //要等待三秒
control_duoji1(90);
gpio_set(sent_int,!now);
now=!now;
im_speed1=aim_speed1,im_speed2=aim_speed2; //前进
flagdelay=0;
}
else if(flagdelay==3) //数字识别后找线
{
control_duoji1(90);
im_speed1=10,im_speed2=10;
if(ad1>30 ||ad4>30)
{
dianci=0;
stcsc=3;
flagdelay=0;
}
}
else if(flagdelay==4) //数字识别后找线
{
control_duoji1(90);
im_speed1=10,im_speed2=10;
if(ad1>30 ||ad4>30)
{
dianci=0;
stcsc=4;
flagdelay=0;
}
}
else if(flagdelay==5) //打到水果后
{
systick_delay_ms(1500);
al_shout=0;
gpio_set(allow,0);
gpio_set(sent_int,!now);
now=!now;
control_duoji1(90);
if(loop==3)loop=4;
if(loop!=3)
{
im_speed1=aim_speed1,im_speed2=aim_speed2; //前进
}
else
{
im_speed1=10,im_speed2=10; //前进
}
flagdelay=0;
}
}
//二维码
void erweima()
{
if(numa==1)
{
if(loop==0)
{
loop++;
flagdelay=1;
}
else if(loop==1)
{
//停车
loop++;
flagdelay=2;
}
else if(loop==2)
{
loop++;
flagdelay=1;
}
}
else if(numa==2)
{
if(loop==0)
{
//停车
loop++;
flagdelay=2;
}
else if(loop==1)
{
loop++;
flagdelay=1;
}
else if(loop==2)
{
loop++;
flagdelay=1;
}
}
}
void fruit()
{
num=low;
low=0;
if(num!=7)
{
static int angle=180,dir=1;
control_duoji1(angle);
if(dir)angle++;
else angle--;
if(angle>=180)dir=0;
else if(angle<=90)dir=1;
flag11=2;
}
else
{
al_shout=1;
gpio_set(allow,0);
flagdelay=5;
flag11=1;
}
}
//数字
void number(int a)
{
static int x=0;
x=x+1;
if(a==1)
{
flagdelay=3;
stcsc=2;
dianci=1;
duty_over1=3800;
if(x==1)numa=1;
}
else
{
flagdelay=4;
stcsc=2;
dianci=1;
duty_over1=4800;
if(x==1)numa=2;
}
im_speed1=10,im_speed2=10; }
附录B:视觉模型程序
#change_to_npy.py文件,用于将图片转换为.npy带标签的压缩图片格式
import tensorflow.keras
from tensorflow.keras.layers import Input, MaxPooling2D, Conv2D,Dropout, \
MaxPool2D, Dense, Activation, Flatten, AveragePooling2D, Permute
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint,LearningRateScheduler
from sklearn.model_selection import train_test_split
from tensorflow.keras.utils import to_categorical
import cv2 as cv
import os
import numpy as np
import random
import tensorflow as tf
gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
cpus = tf.config.experimental.list_physical_devices(device_type='CPU')
print(gpus, cpus)
for gpu in gpus:
tf.config.experimental.set_memory_growth(gpu, True)
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
N=10 #类别数
start=0
size=32
amount=30000#希望抽取的图片数
label = ["cat", "dog", "horse", "pig", "cattle"] + ["apple", "orange", "banana", "liulian", "grape"]#+["nobody"]
data=[]
path='./'
path_result='./result/'
for j in range(start,N):
path1=path+'%d'%j+'/'
sum1=0
data=[]
k=os.listdir(path1)
for f in k:
extension = os.path.splitext(f)[-1]
if ( extension == '.jpg' or extension == '.jpeg'or extension == '.png'):
img = cv.imread(path1+f)
img = cv.resize(img, (size,size))[...,(2,1,0)] # opencv read as bgr, but we need rgb
data.append(img)
sum1=sum1+1
print("\r已处理第%d组第%d张图片 "%(j,sum1),end='')
data = np.asarray(data)
b=os.getcwd()
if not os.path.exists(b+path_result[1:]+'%d/'%j):
os.makedirs(b+path_result[1:]+'%d/'%j)
np.save(path_result+'%d/'%j+label[j],data)
#bigger.py文件,将图片进行旋转、缩放、滤镜、翻转等操作
import skimage.io as io
import os,sys
from skimage import data_dir
import numpy as np
N=10
start=0
from keras.preprocessing.image import ImageDataGenerator, array_to_img, img_to_array, load_img
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
channel_shift_range=50,
horizontal_flip=1,#水平翻转
fill_mode='constant',cval=100)
path1="F:/python_project/pictures/basic_animal/"
path2="./"
label=[8]
for f in label:
path=path1+str(f)+'/'
path3=path2+str(f)
dirs=os.listdir(path)
x_all=[]
sum1=0
for file in dirs:
img=load_img(path+file)
x=img_to_array(img)
x=x.reshape((1,) + x.shape) # 这是一个numpy数组,形状为 (1, 3, 150, 150)
x_all.append(x)
i = 0
for batch in datagen.flow(x, batch_size=1,
save_to_dir=path3,
save_prefix='b5_'):
sum1=sum1+1
print("正在生成第%d组第%d张图片"%(f,sum1),end=" \r")
i += 1
if i > 10: # 数据扩充倍数,此处为数据扩充50倍
break # 否则生成器会退出循环
#bigger_hui.py文件,将图片加入各种噪声,可选择将图片转换为灰度图
import skimage.io as io
import os,sys
from skimage import data_dir
import numpy as np
from PIL import Image
from PIL import ImageFilter
import cv2 as cv
from PIL import ImageOps
import random
N=10
start=0
i=0
path1="./"
path2="F:/python_project/pictures/basic_animal/"
cl=50
for f in range(start,N):
path=path1+str(f)+'/'
path3=path2+str(f)+'/'
dirs=os.listdir(path3)
sum1=0
for file in dirs:
im = Image.open(path3+file)
im.thumbnail((64,64))
##############
img=np.array(im)
raw=cv.cvtColor(img, cv.COLOR_RGB2GRAY )
raw=cv.cvtColor(raw, cv.COLOR_GRAY2RGB )
img = raw.astype('uint8')
im = Image.fromarray(img)
###############
im.save(path+file)
dest_im = im.filter(ImageFilter.DETAIL)
dest_im.save(path+'1_'+file)
dest_im = im.filter(ImageFilter.SMOOTH_MORE)
dest_im.save(path+'3_'+file)
dest_im = im.filter(ImageFilter.SHARPEN)
dest_im.save(path+'4_'+file)
i=i+4
print('f=%d '%f,end='')
print("已生成%d张图片 "%i,end="\r")
print('\n')
print("huise")
os.system('python F:/python_project/pictures/basic_animal_result/huise.py')
#bigger_2.py文件,用于给图片加入滤镜
import skimage.io as io
import os,sys
from skimage import data_dir
import numpy as np
from PIL import Image
from PIL import ImageFilter
import cv2 as cv
from PIL import ImageOps
import random
N=10
start=0
i=0
path1="./"
path2="F:/python_project/pictures/basic_animal/"
cl=50
for f in range(start,N):
path=path1+str(f)+'/'
path3=path2+str(f)+'/'
dirs=os.listdir(path3)
sum1=0
for file in dirs:
im = Image.open(path3+file)
im.thumbnail((64,64))
im.save(path+file)
im_cl=ImageOps.invert(im)
im_cl.save(path+'1_'+file)#反色
img=np.array(im)
im_cl=img
cl=random.randrange(50,200,1)
im_cl[:,:,0] = cl
im_cl = im_cl.astype('uint8')
im_cl = Image.fromarray(im_cl)
im_cl.save(path+'2_'+file)#红色
img=np.array(im)
im_cl=img
cl=random.randrange(50,200,1)
im_cl[:,:,1] = cl
im_cl = im_cl.astype('uint8')
im_cl = Image.fromarray(im_cl)
im_cl.save(path+'3_'+file)#绿色
img=np.array(im)
im_cl=img
cl=random.randrange(50,200,1)
im_cl[:,:,2] = cl
im_cl = im_cl.astype('uint8')
im_cl = Image.fromarray(im_cl)
im_cl.save(path+'4_'+file)#蓝色
img=np.array(im)
im_cl=img
cl=random.randrange(50,200,1)
im_cl[:,:,0] = cl
im_cl[:,:,2] = cl
im_cl = im_cl.astype('uint8')
im_cl = Image.fromarray(im_cl)
im_cl.save(path+'5_'+file)#紫色
img=np.array(im)
im_cl=img
cl=random.randrange(50,200,1)
im_cl[:,:,0] = cl
im_cl[:,:,1] = cl
im_cl = im_cl.astype('uint8')
im_cl = Image.fromarray(im_cl)
im_cl.save(path+'6_'+file)#黄色
img=np.array(im)
im_cl=img
cl=random.randrange(50,200,1)
im_cl[:,:,1] = cl
im_cl[:,:,2] = cl
im_cl = im_cl.astype('uint8')
im_cl = Image.fromarray(im_cl)
im_cl.save(path+'7_'+file)#青色
dest_im = im.filter(ImageFilter.CONTOUR)
dest_im.save(path+'8_'+file)
dest_im = im.filter(ImageFilter.DETAIL)
dest_im.save(path+'9_'+file)
dest_im = im.filter(ImageFilter.EDGE_ENHANCE_MORE)
dest_im.save(path+'10_'+file)
dest_im = im.filter(ImageFilter.EMBOSS)
dest_im.save(path+'11_'+file)
dest_im = im.filter(ImageFilter.FIND_EDGES)
dest_im.save(path+'12_'+file)
dest_im = im.filter(ImageFilter.SMOOTH)
dest_im.save(path+'13_'+file)
dest_im = im.filter(ImageFilter.SMOOTH_MORE)
dest_im.save(path+'14_'+file)
dest_im = im.filter(ImageFilter.SHARPEN)
dest_im.save(path+'15_'+file)
i=i+15
print('f=%d '%f,end='')
print("已生成%d张图片 "%i,end="\r")
print('\n')
print("bigger")
os.system('python F:/python_project/pictures/basic_animal_result/bigger.py')
print("zaosheng")
os.system('python F:/python_project/pictures/basic_animal_result/zaosheng.py')
#bigger_3.py文件,用于给图片增加不同曝光
import cv2
import numpy as np
import os
import random
def gamma_trans(img,gamma):#gamma函数处理
gamma_table=[np.power(x/255.0,gamma)*255.0 for x in range(256)]#建立映射表
gamma_table=np.round(np.array(gamma_table)).astype(np.uint8)#颜色值为整数
return cv2.LUT(img,gamma_table)#图片颜色查表。另外可以根据光强(颜色)均匀化原则设计自适应算法。
def nothing(x):
pass
cv2.namedWindow("demo",0)#将显示窗口的大小适应于显示器的分辨率
gamma
data_base_dir="./"#输入文件夹的路径
outfile_dir="./"#输出文件夹的路径
processed_number=0#统计处理图片的数量
N=10
for i in range(N):
path=data_base_dir+'%d/'%i
for file in os.listdir(path):#遍历目标文件夹图片
read_img_name=path+file#取图片完整路径
image=cv2.imread(read_img_name)#读入图片
value_of_gamma=random.randrange(80,120,1)
value_of_gamma=value_of_gamma*0.01#压缩gamma范围,以进行精细调整
image_gamma_correct=gamma_trans(image,value_of_gamma)#2.5为gamma函数的指数值,大于1曝光度下降,大于0小于1曝光度增强
processed_number+=1
out_img_name=path+'1_'+file
cv2.imwrite(out_img_name,image_gamma_correct)
#bigger_4.py文件,同样可用于修改图片曝光
import cv2
import numpy as np
import os
data_base_dir="./"#输入文件夹的路径
outfile_dir="./"#输出文件夹的路径
processed_number=0#统计处理图片的数量
N=10
for i in range(N):
path=data_base_dir+'%d/'%i
for file in os.listdir(path):#遍历目标文件夹图片
read_img_name=path+file#取图片完整路径
image=cv2.imread(read_img_name)#读入图片
dst=image
cv2.convertScaleAbs(image, dst, 1.5, 10)
cv2.imshow("demo",dst)
cv2.waitKey(1)
processed_number+=1
out_img_name=path+'1_'+file
cv2.imwrite(out_img_name,dst)
cv2.convertScaleAbs(image, dst, 0.5, 10)
cv2.imshow("demo",dst)
cv2.waitKey(1)
processed_number+=1
out_img_name=path+'2_'+file
cv2.imwrite(out_img_name,dst)
print("zaosheng")
import os,sys
import numpy as np
from PIL import Image
from PIL import ImageFilter,ImageEnhance
import cv2 as cv
from PIL import ImageOps
import random
import time
amount=0
def jdt(i,scale,t):
i=round(i/scale*50)
scale=50
a='*'*i
b='.'*(scale-i)
c=(i/scale)*100
t-=time.clock()
print("\r{:^3.0f}%[{}->{}]{:.2f}s".format(c,a,b,-t),end='')
def change1(path, x,f,sum1,t):#亮度、色度、对比度、锐度
global amount
n=x+2
for file in os.listdir(path):
for i in range(n):
#读取图片
image = Image.open(path+file)
#变亮,亮度增强,增强因子为0.0将产生黑色图像;为1.0将保持原始图像。
k=random.randrange(8,15,1)
k=k/10
enh_bri = ImageEnhance.Brightness(image)
brightness = k
image_brightened1 = enh_bri.enhance(brightness)
image_brightened1.save(path+'/'+'L%d_'%i+file)
sum1=sum1+1
jdt(sum1,amount,t)
#色度,增强因子为1.0是原始图像
# 色度增强
k=random.randrange(8,15,1)
k=k/10
enh_col = ImageEnhance.Color(image)
color = k
image_colored1 = enh_col.enhance(color)
image_colored1.save(path+'/'+'S%d_'%i+file)
sum1=sum1+1
jdt(sum1,amount,t)
#对比度,增强因子为1.0是原始图片
# 对比度增强
k=random.randrange(8,15,1)
k=k/10
enh_con = ImageEnhance.Contrast(image)
contrast = k
image_contrasted1 = enh_con.enhance(contrast)
image_contrasted1.save(path+'/'+'D%d_'%i+file)
sum1=sum1+1
jdt(sum1,amount,t)
# 锐度,增强因子为1.0是原始图片
# 锐度增强
k=random.randrange(8,30,1)
k=k/10
enh_sha = ImageEnhance.Sharpness(image)
sharpness = k
image_sharped1 = enh_sha.enhance(sharpness)
image_sharped1.save(path+'/'+'R%d_'%i+file)
sum1=sum1+1
jdt(sum1,amount,t)
return sum1
def rotate1(img,angle,x,path,file,f,sum1,flag,t):#x:倍数,file:名字
global amount
if flag==0:
x=x+1
for j in range(x-1):
k=random.randrange(0,angle*10,5)
k=k/10
i=random.randrange(0,2)
if i:
k=360-k
im=img.convert('RGBA')
img1=im.rotate(k,expand=1)
fff = Image.new('RGBA',img1.size,(255,)*4)
im = Image.composite(img1,fff,img1)
#im=im.convert('L')
im=im.convert('RGB')
im.thumbnail((64,64))
im.save(path+'/'+'Z%d_'%j+file)
sum1=sum1+1
jdt(sum1,amount,t)
return sum1
def mohu(im,path,file,f,sum1,t):
global amount
im.save(path+file)
sum1=sum1+1
jdt(sum1,amount,t)
dest_im = im.filter(ImageFilter.DETAIL)
dest_im.save(path+'1_'+file)
sum1=sum1+1
jdt(sum1,amount,t)
dest_im = im.filter(ImageFilter.SMOOTH_MORE)
dest_im.save(path+'2_'+file)
sum1=sum1+1
jdt(sum1,amount,t)
dest_im = im.filter(ImageFilter.SHARPEN)
dest_im.save(path+'3_'+file)
sum1=sum1+1
jdt(sum1,amount,t)
return sum1
def gong(x):
y=round(x)
if(x<1):
print("\r参数有误")
else:
char=x+1
x1=1
y1=1
for i in range(1,y+1):
for j in range(1,y+1):
if(abs(5/4*j+i*j+i-x)<1):
if(abs(i-j)<char):
char=abs(i-j)
x1=j
y1=i
if(char<=4):
return x1,y1
else:
return x1,y1
return 0,0
N=10
start=0
path1="./"
path2="F:/python_project/pictures/basic_animal2/result/"
flag=1#1:训练,0:测试
if flag:
amount=30000
path1="./"
print("delete")
os.system('python F:/python_project/pictures/basic_animal_result/\
deletefast.py')
else:
amount=5000
path1="./test/"
print("delete")
os.system('python F:/python_project/pictures/basic_animal_result/test/\
deletefast.py')
if flag:
print("fastbemore")
os.system('python F:/python_project/pictures/basic_animal_result/\
fast.py')
print("more")
os.system('python F:/python_project/pictures/basic_animal_result/more.py')
print("demo")
os.system('python F:/python_project/pictures/basic_animal_result/de_more.py')
print("lj")
os.system('python F:/python_project/pictures/basic_animal_result/pil_lvj.py')
print("npy")
os.system('python F:/python_project/pictures/basic_animal_result/change_to_npy.py')
else:
for f in range(start,N):
path=path1+str(f)+'/'
path3=path2+str(f)+'/'
dirs=os.listdir(path3)
k=len(dirs)
t=time.clock()
print('\n第%d组'%f)
x=round(amount/k)
sum1=0
for file in dirs:
im = Image.open(path3+file)
im.thumbnail((64,64))
sum1=rotate1(im,20,x,path,file,f,sum1,flag,t)
print('')
#de_more.py文件,用于删除生成的过量图片
import os,sys
import numpy as np
from PIL import Image
from PIL import ImageFilter,ImageEnhance
import cv2 as cv
from PIL import ImageOps
import random
import time
def jdt(i,scale,t):
i=round(i/scale*50)
scale=50
a='*'*i
b='.'*(scale-i)
c=(i/scale)*100
t-=time.clock()
print("\r{:^3.0f}%[{}->{}]{:.2f}s".format(c,a,b,-t),end='')
#time.sleep(0.05)
N=10
start=0
path1="./"
amount=30000
for f in range(start,N):
t=time.clock()
path=path1+str(f)+'/'
dirs=os.listdir(path)
print('\n第%d个组'%f)
amount1=len(dirs)-amount
ndirs=[s for s in dirs for stri in ["L","S","D","R"] if stri in s]
if len(ndirs)<amount1:
print('\n无法删除需要数量的图片')
break
if(amount1>0):
for i in range(amount1):
jdt(i,amount1,t)
while 1:
k=random.randrange(0,len(ndirs))
print(' i=%d '%i,end='\r')
os.remove(path+ndirs[k])
ndirs.pop(k)
break
print('')
import os
import threading
import time
def d0():
os.system('python F:/python_project/pictures/\
basic_animal_result/delete/0.py')
def d1():
os.system('python F:/python_project/pictures/\
basic_animal_result/delete/1.py')
def d2():
os.system('python F:/python_project/pictures/\
basic_animal_result/delete/2.py')
def d3():
os.system('python F:/python_project/pictures/\
basic_animal_result/delete/3.py')
def d4():
os.system('python F:/python_project/pictures/\
basic_animal_result/delete/4.py')
def d5():
os.system('python F:/python_project/pictures/\
basic_animal_result/delete/5.py')
def d6():
os.system('python F:/python_project/pictures/\
basic_animal_result/delete/6.py')
def d7():
os.system('python F:/python_project/pictures/\
basic_animal_result/delete/7.py')
def d8():
os.system('python F:/python_project/pictures/\
basic_animal_result/delete/8.py')
def d9():
os.system('python F:/python_project/pictures/\
basic_animal_result/delete/9.py')
thread=[]
thread.append(threading.Thread(target= d0))
thread.append(threading.Thread(target= d1))
thread.append(threading.Thread(target= d2))
thread.append(threading.Thread(target= d3))
thread.append(threading.Thread(target= d4))
thread.append(threading.Thread(target= d5))
thread.append(threading.Thread(target= d6))
thread.append(threading.Thread(target= d7))
thread.append(threading.Thread(target= d8))
thread.append(threading.Thread(target= d9))
print(thread)
if __name__=='__main__':
t1=time.clock()
for t in thread:
t.setDaemon(True)
t.start()
for t in thread:
t.join()
print("t总=%d"%(time.clock()-t1))
print("删除完毕")
#fast.py文件,用于快速生成训练图片集
import os
import threading
import time
def d0():
os.system('python F:/python_project/pictures/\
basic_animal_result/more/0.py')
def d1():
os.system('python F:/python_project/pictures/\
basic_animal_result/more/1.py')
def d2():
os.system('python F:/python_project/pictures/\
basic_animal_result/more/2.py')
def d3():
os.system('python F:/python_project/pictures/\
basic_animal_result/more/3.py')
def d4():
os.system('python F:/python_project/pictures/\
basic_animal_result/more/4.py')
def d5():
os.system('python F:/python_project/pictures/\
basic_animal_result/more/5.py')
def d6():
os.system('python F:/python_project/pictures/\
basic_animal_result/more/6.py')
def d7():
os.system('python F:/python_project/pictures/\
basic_animal_result/more/7.py')
def d8():
os.system('python F:/python_project/pictures/\
basic_animal_result/more/8.py')
def d9():
os.system('python F:/python_project/pictures/\
basic_animal_result/more/9.py')
thread=[]
thread.append(threading.Thread(target= d0))
thread.append(threading.Thread(target= d1))
thread.append(threading.Thread(target= d2))
thread.append(threading.Thread(target= d3))
thread.append(threading.Thread(target= d4))
thread.append(threading.Thread(target= d5))
thread.append(threading.Thread(target= d6))
thread.append(threading.Thread(target= d7))
thread.append(threading.Thread(target= d8))
thread.append(threading.Thread(target= d9))
print(thread)
if __name__=='__main__':
t1=time.clock()
for t in thread:
t.setDaemon(True)
t.start()
for t in thread:
t.join()
#more.py文件,用于补足图片数量不足的类别
import os,sys
import numpy as np
from PIL import Image
from PIL import ImageFilter,ImageEnhance
import cv2 as cv
from PIL import ImageOps
import random
import time
amount=0
def jdt(i,scale,t):
i=round(i/scale*50)
scale=50
a='*'*i
b='.'*(scale-i)
c=(i/scale)*100
t-=time.clock()
print("\r{:^3.0f}%[{}->{}]{:.2f}s".format(c,a,b,-t),end='')
def rotate1(img,angle,x,path,file,f,sum1,t):#x:倍数,file:名字
global amount
for j in range(x):
k=random.randrange(0,angle*10,5)
k=k/10
i=random.randrange(0,2)
if i:
k=360-k
im=img.convert('RGBA')
img1=im.rotate(k,expand=1)
fff = Image.new('RGBA',img1.size,(255,)*4)
im = Image.composite(img1,fff,img1)
#im=im.convert('L')
im=im.convert('RGB')
im.thumbnail((64,64))
im.save(path+'/'+'Zm%d_'%j+file)
sum1=sum1+1
jdt(sum1,amount,t)
return sum1
N=10
start=0
path2="F:/python_project/pictures/basic_animal2/result/"
flag=1#1:训练,0:测试
if flag:
amount1=30000
path1="./"
else:
amount1=5000
path1="./test/"
for f in range(start,N):
path=path1+str(f)+'/'
path3=path2+str(f)+'/'
dirs=os.listdir(path3)
k=len(dirs)
dirs1=os.listdir(path)
k1=len(dirs1)
amount=amount1-k1
t=time.clock()
print('\n第%d组'%f)
if(amount>0):
x=round(amount/k)+1
sum1=0
if flag:
for file in dirs:
im = Image.open(path3+file)
im.thumbnail((64,64))
sum1=rotate1(im,20,x,path,file,f,sum1,t)
else:
for file in dirs:
im = Image.open(path3+file)
im.thumbnail((64,64))
sum1=rotate1(im,20,x,path,file,f,sum1,t)
print('')
import os,sys
import numpy as np
from PIL import Image
from PIL import ImageFilter,ImageEnhance
import cv2 as cv
from PIL import ImageOps
import random
import time
amount=0
N=10
start=0
flag=1 #1:训练 0:测试
if flag:
path1="./"
path2="./"
else:
path1="./test/"
path2="./test/"
def jdt(i,scale,t):
if(i>scale):
print('输入有误')
i=round(i/scale*50)
scale=50
a='*'*i
b='.'*(scale-i)
c=(i/scale)*100
t-=time.clock()
print("\r{:^3.0f}%[{}->{}]{:.2f}s".format(c,a,b,-t),end='')
for f in range(start,N):
path=path1+str(f)+'/'
path3=path2+str(f)+'/'
dirs=os.listdir(path3)
number=round(len(dirs)/10)
k=random.sample(dirs,number)
sum1=0
print("第%d组:"%f)
t=time.clock()
for file in k:
im = Image.open(path3+file)
im.thumbnail((64,64))
img=np.array(im)
im_cl=img
cl=random.randrange(50,200,1)
cl2=random.randrange(0,3,1)
im_cl[:,:,cl2] = cl
im_cl = im_cl.astype('uint8')
im_cl = Image.fromarray(im_cl)
im_cl.save(path+file)
sum1+=1
jdt(sum1,number,t)
print('')
#zaosehng.py文件,给图片增加多种噪声
import os
import cv2
import numpy as np
import random
def sp_noise(noise_img, proportion):
# 将图片灰度标准化
img = img / 255
# 产生高斯 noise
noise = np.random.normal(mean, sigma, img.shape)
# 将噪声和图片叠加
gaussian_out = img + noise
# 将超过 1 的置 1,低于 0 的置 0
gaussian_out = np.clip(gaussian_out, 0, 1)
# 将图片灰度范围的恢复为 0-255
gaussian_out = np.uint8(gaussian_out*255)
# 将噪声范围搞为 0-255
return gaussian_out# 这里也会返回噪声,注意返回值
def random_noise(image,noise_num):
# 参数image:,noise_num:
img_noise = image
# cv2.imshow("src", img)
rows, cols, chn = img_noise.shape
# 加噪声
for i in range(noise_num):
x = np.random.randint(0, rows)#随机生成指定范围的整数
y = np.random.randint(0, cols)
img_noise[x, y, :] = 255
return img_noise
def convert(input_dir, output_dir):
for filename in os.listdir(input_dir):
path = input_dir + "/" + filename # 获取文件路径
print("doing... ", path,end=" \r")
noise_img = cv2.imread(path)#读取图片
img_noise1 = gaussian_noise(noise_img, 0, 0.12) # 高斯噪声
img_noise2 = sp_noise(noise_img,0.025)# 椒盐噪声
img_noise3 = random_noise(noise_img,500)# 随机噪声
cv2.imwrite(output_dir+'/'+'z1_'+filename,img_noise1 )
cv2.imwrite(output_dir+'/'+'z2_'+filename,img_noise2 )
cv2.imwrite(output_dir+'/'+'z3_'+filename,img_noise3 )
if __name__ == '__main__':
input_dir = "./" # 输入数据文件夹
output_dir = "./" # 输出数据文件夹
for i in range(0,10):
path1=input_dir+str(i)
path2=output_dir+str(i)
convert(path1, path2)
import shutil
import os
import time
t=time.clock()
shutil.rmtree('F:/python_project/pictures/basic_animal_result/0/') #递归非空文件夹
os.mkdir('F:/python_project/pictures/basic_animal_result/0') # 表示在相对路径下创建文件
print('t=%d'%(time.clock()-t))
print('文件夹0删除完毕 ')
#0.py文件,用于对0文件夹进行数据增强
import os,sys
import numpy as np
from PIL import Image
from PIL import ImageFilter,ImageEnhance
import cv2 as cv
from PIL import ImageOps
import random
import time
def change1(path, x,f,t):#亮度、色度、对比度、锐度
n=x+2
for file in os.listdir(path):
for i in range(n):
#读取图片
image = Image.open(path+file)
#亮度增强,增强因子为0.0将产生黑色图像;为1.0将保持原始图像。
k=random.randrange(8,15,1)
k=k/10
enh_bri = ImageEnhance.Brightness(image)
brightness = k
image_brightened1 = enh_bri.enhance(brightness)
image_brightened1.save(path+'/'+'L%d_'%i+file)
#色度,增强因子为1.0是原始图像
# 色度增强
k=random.randrange(8,15,1)
k=k/10
enh_col = ImageEnhance.Color(image)
color = k
image_colored1 = enh_col.enhance(color)
image_colored1.save(path+'/'+'S%d_'%i+file)
#对比度,增强因子为1.0是原始图片
# 对比度增强
k=random.randrange(8,15,1)
k=k/10
enh_con = ImageEnhance.Contrast(image)
contrast = k
image_contrasted1 = enh_con.enhance(contrast)
image_contrasted1.save(path+'/'+'D%d_'%i+file)
# 锐度,增强因子为1.0是原始图片
# 锐度增强
k=random.randrange(8,30,1)
k=k/10
enh_sha = ImageEnhance.Sharpness(image)
sharpness = k
image_sharped1 = enh_sha.enhance(sharpness)
image_sharped1.save(path+'/'+'R%d_'%i+file)
def rotate1(img,angle,x,path,file,f,t):#x:倍数,file:名字
for j in range(x-1):
k=random.randrange(0,angle*10,5)
k=k/10
i=random.randrange(0,2)
if i:
k=360-k
im=img.convert('RGBA')
img1=im.rotate(k,expand=1)
fff = Image.new('RGBA',img1.size,(255,)*4)
im = Image.composite(img1,fff,img1)
im=im.convert('RGB')
im.thumbnail((64,64))
im.save(path+'/'+'Z%d_'%j+file)
def mohu(im,path,file,f,t):
im.save(path+file)
dest_im = im.filter(ImageFilter.DETAIL)
dest_im.save(path+'1_'+file)
dest_im = im.filter(ImageFilter.SMOOTH_MORE)
dest_im.save(path+'2_'+file)
dest_im = im.filter(ImageFilter.SHARPEN)
dest_im.save(path+'3_'+file)
def gong(x):
y=round(x)
if(x<1):
print("\r参数有误")
else:
char=x+1
x1=1
y1=1
for i in range(1,y+1):
for j in range(1,y+1):
if(abs(5/4*j+i*j+i-x)<1):
if(abs(i-j)<char):
char=abs(i-j)
x1=j
y1=i
if(char<=4):
return x1,y1
else:
return x1,y1
return 0,0
f=0
amount=30000
N=10
start=0
path1="F:/python_project/pictures/basic_animal_result/"
path2="F:/python_project/pictures/basic_animal/"
path=path1+str(f)+'/'
path3=path2+str(f)+'/'
dirs=os.listdir(path3)
k=len(dirs)
t=time.clock()
print('\n第%d组'%f)
amount1=amount/4
n=(amount1/k-5)/4
x,y=gong(n)
for file in dirs:
im = Image.open(path3+file)
im.thumbnail((64,64))
mohu(im,path,file,f,t)
dirs=os.listdir(path)
for file in dirs:
im = Image.open(path+file)
im.thumbnail((64,64))
rotate1(im,20,x,path,file,f,t)
change1(path,y,f,t)
print('\n第%d组t=%d'%(f,(time.clock()-t)))