正点原子【第四期】Linux之驱动开发学习笔记-2.1LED灯驱动实验(直接操作寄存器)

 前言:

本文是根据哔哩哔哩网站上“正点原子【第四期】手把手教你学Linux系列课程之 Linux驱动开发篇”视频的学习笔记,该课程配套开发板为正点原子alpha/mini Linux开发板。在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。

引用:

正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com

正点原子【第四期】手把手教你学 Linux之驱动开发篇_哔哩哔哩_bilibili

《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》

正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档

正点原子imx6ull-mini-Linux驱动之Linux I2C 驱动实验(21)-CSDN博客

uboot移植(4)--在NXP官方uboot适配ALPHA开发板网络_uboot sr8201f-CSDN博客

正文:

本文是 “正点原子【第四期】手把手教你学 Linux之驱动开发篇-1.1 Linux驱动开发与裸机开发的区别”。本节将参考正点原子的视频教程和配套的正点原子开发指南文档进行学习。

0. 概述

上一章我们详细的讲解了字符设备驱动开发步骤,并且用一个虚拟的 chrdevbase 设备为例带领大家完成了第一个字符设备驱动的开发。本章我们就开始编写第一个真正的 Linux 字符设备驱动。在 I.MX6U-ALPHA 开发板上有一个 LED 灯,我们在裸机篇中已经编写过此 LED 灯的裸机驱动,本章我们就来学习一下如何编写 Linux 下的 LED 灯驱动。


1 Linux 下 LED 灯驱动原理

Linux 下的任何外设驱动,最终都是要配置相应的硬件寄存器。所以本章的 LED 灯驱动最终也是对 I.MX6ULL 的 IO 口进行配置,与裸机实验不同的是,在 Linux 下编写驱动要符合 Linux的驱动框架。I.MX6U-ALPHA 开发板上的 LED 连接到 I.MX6ULL 的 GPIO1_IO03 这个引脚上,因此本章实验的重点就是编写 Linux 下 I.MX6UL 引脚控制驱动。关于 I.MX6ULL 的 GPIO 详细讲解请参考第八章。

1.1 地址映射

在编写驱动之前,我们需要先简单了解一下 MMU 这个神器, MMU 全称叫做 MemoryManage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU,但是现在Linux 内核已经支持无 MMU 的处理器了。 MMU 主要完成的功能如下:

①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

我们重点来看一下第①点,也就是虚拟空间到物理空间的映射,也叫做地址映射。首先了解两个地址概念:虚拟地址(VA,Virtual Address)、物理地址(PA, Physcical Address)。对于 32 位的处理器来说,虚拟地址范围是 2^32=4GB,我们的开发板上有 512MB 的 DDR3,这 512MB 的内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间,如图 41.1.1 所示

物理内存只有 512MB,虚拟内存有 4GB,那么肯定存在多个虚拟地址映射到同一个物理地址上去,虚拟地址范围比物理地址范围大的问题处理器自会处理,这里我们不要去深究,因为MMU 是很复杂的一个东西,后续有时间的话正点原子 Linux 团队会专门做 MMU 专题教程。

Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟 地 址 。 比 如 I.MX6ULL 的 GPIO1_IO03 引 脚 的 复 用 寄 存 器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03  的地址为 0X020E0068。如果没有开启 MMU 的直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启了 MMU,并且设置了内存映射,因此就不能直接向 0X020E0068 这个地址写入数据了。我们必须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数: ioremap 和 iounmap。

1、 ioremap 函数

ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 , 定 义 在arch/arm/include/asm/io.h 文件中,定义如下:

arch/arm/include/asm/io.h

ioremap 是个宏,有两个参数: cookie 和 size,真正起作用的是函数__arm_ioremap,此函数有三个参数和一个返回值,这些参数和返回值的含义如下:

  • phys_addr:要映射的物理起始地址。
  • size:要映射的内存空间大小。
  • mtype: ioremap 的类型,可以选择 MT_DEVICE、 MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC, ioremap 函数选择 MT_DEVICE。

返回值: __iomem 类型的指针,指向映射后的虚拟空间首地址。

假如我们要获取 I.MX6ULL 的 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器对应的虚拟地址,使用如下代码即可:

#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
static void __iomem* SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);

宏 SW_MUX_GPIO1_IO03_BASE 是寄存器物理地址, SW_MUX_GPIO1_IO03 是映射后的虚拟地址。对于 I.MX6ULL 来说一个寄存器是 4 字节(32 位)的,因此映射的内存长度为 4。映射完成以后直接对 SW_MUX_GPIO1_IO03 进行读写操作即可。

2、 iounmap 函数

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射, iounmap 函数原型如下:

void iounmap (volatile void __iomem *addr)

1.2 I/O 内存访问函数

这里说的 I/O 是输入/输出的意思,并不是我们学习单片机的时候讲的 GPIO 引脚。这里涉及到两个概念: I/O 端口和 I/O 内存

当外部寄存器或内存映射到 IO 空间时,称为 I/O 端口。当外部寄存器或内存映射到内存空间时,称为 I/O 内存

。但是对于 ARM 来说没有 I/O 空间这个概念,因此 ARM 体系下只有 I/O 内存(可以直接理解为内存)。使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

1、读操作函数

读操作函数有如下几个:

readb、 readw 和 readl 这三个函数分别对应 8bit、 16bit 和 32bit 读操作,参数 addr 就是要读取写内存地址,返回值就是读取到的数据。

2、写操作函数

写操作函数有如下几个:

writeb、 writew 和 writel 这三个函数分别对应 8bit、 16bit 和 32bit 写操作,参数 value 是要写入的数值, addr 是要写入的地址。

2.硬件原理图分析

本章实验硬件原理图参考 8.3 小节即可
 

3.实验程序编写

本章实验编写 Linux 下的 LED 灯驱动,可以通过应用程序对 I.MX6U-ALPHA 开发板上的LED 灯进行开关操作。

新建名为“2_led”文件夹,然后在 2_led 文件夹里面创建 VSCode 工程,工作区命名为“led”。工程创建好以后新建 led.c 文件,此文件就是 led 的驱动文件,在 led.c 里面输入如下内容:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <asm/io.h>#define LED_MAJOR 200
#define LED_NAME "led"#define CCM_CCGR1_BASE                          (0x020C406C)
#define SW_MUX_CTL_PAD_GPIO1_IO03_BASE          (0x020E0068)
#define SW_PAD_CTL_PAD_GPIO1_IO03_BASE          (0x020E02F4)
#define GPIO1_DR_BASE                           (0x0209C000)
#define GPIO1_GDIR_BASE                         (0X0209C004)void __iomem * CCM_CCGR1 = NULL;
void __iomem * SW_MUX_CTL_GPIO1_IO03 = NULL;
void __iomem * SW_PAD_CTL_GPIO1_IO03 = NULL;
void __iomem * GPIO1_GDIR = NULL;
void __iomem * GPIO1_DR = NULL;static int led_open(struct inode *inode, struct file *filep) {return 0;
}static int led_release(struct inode *inode, struct file *filep) {return 0;
}static ssize_t led_read(struct file *filep, char __user *buf,size_t count, loff_t *ppos) {return 0;
}#define LED_OFF 0 
#define LED_ON 1void led_switch(uint8_t sta) {u32 val = sta;if (sta == LED_ON) {val = readl(GPIO1_DR);val &= ~(1 << 3);writel(val, GPIO1_DR);}else if (sta == LED_OFF) {val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);}
}static ssize_t led_write(struct file *filep, const char __user *buf,size_t count, loff_t *ppos)
{int retvalue;unsigned char databuf[1];retvalue = copy_from_user(databuf, buf, 1);if(retvalue < 0 ){printk("kernel write failed\n\n");return -EFAULT;}if (databuf[0] == 0) {led_switch(LED_OFF);}else if (databuf[0] == 1) {led_switch(LED_ON);}return 0;
}//
static const struct file_operations led_fopes = {.owner = THIS_MODULE,.read =  led_read,.write = led_write,.open = led_open,.release = led_release,
};// entry 
static int __init led_init(void) {int ret = 0;u32 val = 0;printk("led init\n");/**/// map gpio physical memory address to vitual addressCCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_CTL_GPIO1_IO03 = ioremap(SW_MUX_CTL_PAD_GPIO1_IO03_BASE, 4);SW_PAD_CTL_GPIO1_IO03 = ioremap(SW_PAD_CTL_PAD_GPIO1_IO03_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);//gpio init//CCGR0 clockval = readl(CCM_CCGR1);val |= (3 << 26);writel(val, CCM_CCGR1);//GPIO1_IO03 MUXwritel(0x5, SW_MUX_CTL_GPIO1_IO03);writel(0x10B0, SW_PAD_CTL_GPIO1_IO03);//GPOI1 directionval = readl(GPIO1_GDIR);val |= (1 << 3);writel(val, GPIO1_GDIR);//GPIO1 dataval = readl(GPIO1_DR);val &= ~(1 << 3);writel(val, GPIO1_DR);// register chrdeviceret = register_chrdev(LED_MAJOR, LED_NAME, &led_fopes);if(ret < 0){printk("register chardev fail\r\n");return -EIO;}return 0;
}//exit 
static void __exit led_exit(void) {u32 val;unregister_chrdev(LED_MAJOR, LED_NAME);//GPIO1 dataval = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);iounmap(CCM_CCGR1);iounmap(SW_MUX_CTL_GPIO1_IO03);iounmap(SW_PAD_CTL_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);printk("led exit\n");
}//retister .ko load and deload function
module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("dimon.chen@163.com");


编写测试 APP

编写测试 APP, led 驱动加载成功以后手动创建/dev/led 节点,应用 APP 通过操作/dev/led文件来完成对 LED 设备的控制。向/dev/led 文件写 0 表示关闭 LED 灯,写 1 表示打开 LED 灯。新建 ledApp.c 文件,在里面输入如下内容:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>/** ./ledApp <filename> <0|1>  , 0:trun off led, 1:turn on led* ./ledApp /dev/led 0 * ./ledApp /dev/led 1*/
int main(int argc, char *argv[]) 
{int ret = 0;int fd = -1;char *filename = NULL;char read_data[100];char write_data[100];if(argc < 3) {printf("usate:  %s <dev> <opt>\r\n", argv[0]);return -1;}filename = argv[1];fd = open(filename, O_RDWR);if(fd < 0) {printf("open %s fail\r\n");return -1;}char databuf[1];databuf[0] = atoi(argv[2]);ret = write(fd, databuf, sizeof(databuf));if(ret < 0 ){printf("write fail \n");close(fd);return -1;}close(fd);return 0;
}


4 运行测试

4.1 编译驱动程序和测试 APP

1、编译驱动程序
编写 Makefile 文件,本章实验的 Makefile 文件和第四十章实验基本一样,只是将 obj-m 变量的值改为 led.o, Makefile 内容如下所示:


obj-m := led.oPWD=$(shell pwd)
KDERDIR=/home/dimon/I.MX6ULL/linux_altek_drivermodules:$(MAKE) -C $(KDERDIR) M=$(PWD) modulesclear:$(MAKE) -C $(KDERDIR) M=$(PWD) clear

2、编译测试 APP

输入如下命令编译测试 ledApp.c 这个测试程序:

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

编译成功以后就会生成 ledApp 这个应用程序。

4.2 运行测试

注意! 如果大家使用的正点原子出厂系统来做本实验,那么会发现 LED 灯会一直闪烁。这是因为正点原子出厂系统默认将 LED 灯作为了心跳灯,因此系统启动以后 LED 灯就会自动闪烁,这样会影响大家做实验。如果是完全按照本教程自行移植的内核和根文件系统,那么就不会遇到此问题。 如果直接使用出厂系统来做实验,我们需要关闭 LED 灯的心跳功能,关闭方法参考《【正点原子】 I.MX6U 用户快速体验》第 3.1 小节,或者输入如下命令即可:

echo none > /sys/class/leds/sys-led/trigger // 改变 LED 的触发模式

创建设备节点

mknod /dev/led c 200 0

加载内核模块

执行ledApp测试程序,控制LED亮灭

./ledApp /dev/led 1 //打开 LED 灯

输入上述命令以后观察 I.MX6U-ALPHA 开发板上的红色 LED 灯是否点亮,如果点亮的话说明驱动工作正常。

在输入如下命令关闭 LED 灯

./ledApp /dev/led 0 //关闭 LED 灯

输入上述命令以后观察 I.MX6U-ALPHA 开发板上的红色 LED 灯是否熄灭,如果熄灭的话说明我们编写的 LED 驱动工作完全正常!至此,我们成功编写了第一个真正的 Linux 驱动设备程序。

如果要卸载驱动的话输入如下命令即可:

rmmod led.ko

5. 总结

Linux中开启了MMU则ARM 处理器访问的所有地址都是虚拟内存地址,当在led.ko中需要对寄存器物理地址进行读操作和写操作时,需要先把寄存器物理内存地址映射为虚拟内存物理地址。从物理内存地址映射为虚拟物理内存地址使用的是 ioremap() 和 iounmap(),在Linux中类似这种ioremap()/iounmap()的函数必须成对的使用,有ioremap,在退出内核模块时就需要做iounmap()。

在内核里对映射到虚拟内存地址的 void __iomem * xxx_addr 的地址做读写的时,需要使用Linux内核提供 void __iomem * 的读写函数,内核提供的读函数有 readb(), readw(), readl() 分别是读8bit, 16bit, 32bit,内核提供的写函数有 writeb(), writew(), writel() 分别是写 8bit, 16bit, 32bit。

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

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

相关文章

【GM3568JHF】FPGA+ARM异构开发板 测试命令

本章节的命令操作均在板卡的终端执行 1 初探/sys目录 与/proc目录类似&#xff0c;/sys目录下的文件/文件夹向用户提供了一些关于设备、内核模块、文件系统以及其他内核组件的信息&#xff0c; 如子目录block中存放了所有的块设备&#xff1b;子目录bus中存放了系统中所有的总…

【Win】Motrix+Aria2浏览器下载加速

系统安装Motrix Motrix官网下载&#xff0c;推荐下载NSIS Installer 安装版 浏览器安装Aria2 下载Aria2插件&#xff0c;然后开发者模式安装到浏览器 Aria2扩展选项的配置如下&#xff1a; 端口号需要改成Motrix的&#xff0c;默认是16800

SpringBoot applicationContext.getBeansOfType获取某一接口所有实现类,应用于策略模式

本文介绍了如何在Springboot项目中通过ApplicationContext获取接口的实现类&#xff0c;并通过枚举策略模式避免if/else&#xff0c;展示了如何使用getBeansOfType获取TrafficModeService的实现&#xff0c;以及如何在实际场景中应用&#xff0c;如查询交通方式费用 1 在实际工…

大模型问题:幻觉分类+原因+各个训练阶段产生幻觉+幻觉的检测和评估基准

1. 什么是幻觉&#xff1f;大模型出现幻觉&#xff0c;简而言之就是“胡说八道”。 用《A Survey on Hallucination in Large Language Models》1文中的话来讲&#xff0c;是指模型生成的内容与现实世界事实或用户输入不一致的现象。 研究人员将大模型的幻觉分为事实性幻觉&…

智慧冷库物联网解决方案——实现降本增效与风险可控的冷库管理新范式

一、冷库管理痛点设备孤岛化&#xff1a;冷库品牌、型号分散&#xff0c;缺乏统一接入标准&#xff0c;数据互通难&#xff0c;依赖人工巡检&#xff0c;故障响应滞后。能耗黑洞&#xff1a;制冷系统能耗占冷库总运营成本的60%以上&#xff0c;传统管理粗放&#xff0c;缺乏动态…

太空生活的八种要素

数代以来&#xff0c;科学家们一直在银河系中搜寻地外行星存在生命的证据。他们试图找到一组特定的环境条件与化学物质&#xff0c;在恰当的时间、恰当的地点交汇融合。 通过研究人类、植物、动物及微生物在地球上的生存与繁衍方式&#xff0c;科学家们已识别出生命演化所需的关…

Flutter 小技巧之有趣的 UI 骨架屏框架 skeletonizer

很久没有更新过小技巧系列&#xff0c;今天简单介绍一个非常好用的骨架屏框架 skeletonizer &#xff0c;它主要是通过将你现有的布局自动简化为简单的骨架&#xff0c;并添加动画效果来实现加载过程&#xff0c;而使用成本则是简单的添加一个 Skeletonizer 作为 parent &…

基于SpringBoot的宠物用品系统【2026最新】

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

MongoDB 分片集群修改管理员密码

记得关注一下博主&#xff0c;博主每天都会更新IT技术&#xff0c;让你有意想不到的小收获哦^_^ 文章目录*记得关注一下博主&#xff0c;博主每天都会更新IT技术&#xff0c;让你有意想不到的小收获哦^_^*一、注释MongoDB分片集群认证参数&#xff08;三台主机都要操作&#xf…

C++函数重载与引用详解

一、函数重载&#xff1a;同名函数的 “差异化生存”​1. 概念定义​函数重载&#xff08;Function Overloading&#xff09;是 C 的重要特性&#xff0c;指在同一作用域内&#xff0c;允许存在多个同名函数&#xff0c;但要求这些函数的参数列表必须不同。&#xff08;参数个数…

2025-08-17 李沐深度学习16——目标检测

文章目录1 介绍1.1 实际应用1.2 边界框1.3 数据集2 锚框2.1 什么是锚框2.2 交并比2.3 分配标签2.4 非极大值抑制3 经典目标检测网络3.1 R-CNN3.1.1 R-CNN (原始版本)3.1.2 Fast R-CNN3.1.3 Faster R-CNN3.1.4 Mask R-CNN3.2 单阶段检测器&#xff1a;SSD 和 YOLO3.2.1 SSD (Sin…

Bluedroid vs NimBLE

&#x1f539; 对比&#xff1a;Bluedroid vs NimBLE 1. 协议栈体积 & 内存占用 Bluedroid&#xff1a;体积大&#xff0c;RAM 占用也大&#xff08;几十 KB 到上百 KB&#xff09;。NimBLE&#xff1a;轻量级&#xff0c;内存占用大概是 Bluedroid 的一半甚至更少。 &…

(纯新手教学)计算机视觉(opencv)实战八——四种边缘检测详解:Sobel、Scharr、Laplacian、Canny

边缘检测详解&#xff1a;Sobel、Scharr、Laplacian、Canny边缘检测是图像处理和计算机视觉中的重要步骤&#xff0c;主要用于发现图像中亮度变化剧烈的区域&#xff0c;即物体的轮廓、边界或纹理特征。OpenCV 提供了多种常用的边缘检测算子&#xff0c;本教程将通过四种方法带…

PyTorch 环境配置

目录一、安装 CUDA二、安装 PyTorch1. 创建虚拟环境2. 安装 PyTorch三、在 PyCharm 上创建一个 PyTorch 项目参考文章&#xff1a; 【2025年最新PyTorch环境配置保姆级教程&#xff08;附安装包&#xff09;】 【超详细 CUDA 安装与卸载教程&#xff08;图文教程&#xff09;】…

鸿蒙中冷启动分析:Launch分析

启动的分类&#xff08;热身环节&#xff09; 启动动类型触发条件系统开销 & 速度主要优化方向冷启动应用进程不存在&#xff08;首次启动或进程被杀后启动&#xff09;最高&#xff0c;需创建进程、加载资源、初始化所有组件主要优化目标&#xff0c;减少主线程任务&…

告别盲目排查,PolarDB+DAS Agent智能运维新突破

1.概述 周五下午6点正准备下班&#xff0c;数据库CPU突然爆满&#xff0c;业务告警响成一片&#xff0c;DBA却要手动翻查CPU/内存/负载等多个监控指标&#xff0c;还要查询是否有新增慢SQL&#xff0c;死锁等问题&#xff1f;” 这可能是数据库DBA最闹心的场景了&#xff0c;…

Linux------《零基础到联网:CentOS 7 在 VMware Workstation 中的全流程安装与 NAT 网络配置实战》

&#xff08;一&#xff09;Linux的发行版Centos安装与配置 下载Linux发行版本Centos:centos-7-isos-x86_64安装包下载_开源镜像站-阿里云点击CentOS-7-x86_64-DVD-2009.torrent &#xff0c;CentOS-7-x86_64-DVD-2009.torrent是官方提供的 BT 种子文件&#xff08;176.1 KB&a…

iOS App 混淆工具实战,教育培训类 App 的安全保护方案

随着在线教育、企业培训、知识付费平台的兴起&#xff0c;越来越多的 iOS 应用需要保护自己的课程资源和核心逻辑。然而&#xff0c;教育类 App 面临的最大风险并非传统的外挂或刷分&#xff0c;而是 视频盗链、题库数据泄露、源码逻辑被二次利用。 在这种场景下&#xff0c;合…

RabbitMQ:SpringAMQP Topic Exchange(主题交换机)

目录一、案例需求二、基础配置三、代码实现TopicExchange与DirectExchange类似&#xff0c;区别在于RoutingKey可以是多个单次的列表&#xff0c;并且以.分割。 Queue与Exchange指定BindingKey时可以使用通配符&#xff1a; #&#xff1a;代指0个或多个单词。*&#xff1a;代…

(纯新手教学)计算机视觉(opencv)实战六——图像形态学(腐蚀、膨胀、开运算、闭运算、梯度、顶帽、黑帽)

图像形态学在图像处理中&#xff0c;形态学&#xff08;Morphology&#xff09; 是一种基于图像中物体形状的处理方法&#xff0c;通常用于二值图像和灰度图像。它通过腐蚀、膨胀等基本操作&#xff0c;结合开运算、闭运算、梯度运算、顶帽、黑帽等派生操作&#xff0c;来实现去…