自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(190)
  • 收藏
  • 关注

原创 总线、设备与驱动(4)

需要注意的是,在注销一个驱动对象的过程中,如果其所在的总线定义了remove方法,那么内核会调用它,否则要看驱动所在的驱动程序中有没有实现该方法,如果实现了的话内核会调用该函数。函数中对kset_create_and_add(“class”, NULL, NULL)的调用将导致在/sys目录下新生成一个“class”目录(/sys/class)​,在以后的class相关的操作中,class_kset将作为系统中所有class内核对象的顶层kset。如果查找成功,将返回该驱动对象的指针,否则返回0。

2025-07-25 10:09:23 102

原创 TRIZ(3)——发明原理

提取工程参数()是为了将具体问题转化为典型问题,进而找出典型问题所对应的典型解决方案。绝大多数专利都是在解决矛盾,而且相似的矛盾之间,其解决方案在本质上也具有一致性。TRIZ理论从大量发明方案中总结、提炼出解决矛盾的40个发明原理。这也是在TRIZ理论发展过程中,阿奇舒勒最先得到的“解决问题的规律”​。他发现,虽然不同的专利解决的是不同领域内的问题,但是它们使用的方法是具有相似性的,即一种方法可以解决来自不同工程技术领域的类似问题。

2025-07-25 10:08:52 251

原创 总线、设备与驱动(3)

由于在对dev对象调用device_initialize函数时,曾指定了dev所属的kset为devices_kset:dev->kobj.kset = devices_kset,所以这种情况下在将dev->kobj加入系统时,内核会将devices_kset所对应的kobj设置为dev->kobj的parent,所以dev->kobj.parent = devices_kset->kobj。最后的driver_bound(dev)用来将驱动程序的一些数据信息加入到dev对象中。

2025-07-24 11:53:40 627

原创 linux驱动开发(22)-Linux设备驱动模型(三)

其次,call_usermodehelper_exec函数通过引入一个completion变量done来实现和工作节点sub_info->work上的延迟函数__call_usermodehelper的同步:函数通过queue_work(khelper_wq,&sub_info->work)将工作节点提交到khelper_wq队列之后,将等待在wait_for_completion(&done)语句上。__call_usermodehelper是该工作节点上的延迟执行的函数。直接看看核心的代码。

2025-07-24 11:52:52 362

原创 linux驱动开发(22)-Linux设备驱动模型(二)

其中kset_init和kobject_add_internal的功能都比较直观,分别用来初始化kset对象和向系统注册该kset对象,因为kset对象本身就是一个由kobject代表的内核对象,所以kobject_add_internal函数会为代表该kset对象的k->kobj在sysfs文件树中生成一个新目录,这个过程同前面谈到的kobject的操作是完全一样的。udev的实现基于内核中的。这些枚举数值定义了kset对象的一些状态变化,此处使用的是KOBJ_ADD,表明将向系统添加一个kset对象。

2025-07-23 09:33:24 553

原创 linux驱动开发(21)-Linux设备驱动模型(一)

sysfs_get_sb函数用来产生sysfs文件系统的超级块,其内部调用的最主要的函数是sysfs_fill_super,后者再经过一系列的函数调用链进入到sysfs_init_inode函数,这里之所以重点强调这个函数,是因为在接下来谈到内核对象的属性问题时会看到用户空间和内核对象的沟通问题,这种文件接口形式的交互发生在内核空间和用户空间,所以我们需要知道这条沟通的通道是如何建立起来的。显然不同类型的内核对象会有不同的ktype,用以体现kobject所代表的内核对象的特质。用来表示该内核对象的名称。

2025-07-23 09:33:06 482

原创 总线、设备与驱动(2)

在总线上发生的两类事件将导致设备与驱动绑定行为的发生:一是通过device_register函数向某一bus上注册一设备,这种情况下内核除了将该设备加入到bus上的设备链表的尾端,同时会试图将此设备与总线上的所有驱动对象进行绑定操作(当然,操作归操作,能否成功则是另外一回事)​;每类对应一个内核对象,分别为sysfs_dev_block_kobj和sysfs_dev_char_kobj,自然地这些内核对象也在sysfs文件树中占有对应的入口点,block和char内核对象的上级内核对象为dev_kobj。

2025-07-22 10:13:01 859

原创 总线、设备与驱动(1)

现在开始讨论Linux设备驱动模型的高层部分,核心分为三个组件,分别是总线(bus)​、设备(device)和驱动(driver)。接下来将依次讨论每个组件,看看Linux引入的这个新的设备模型到底给系统带来了哪些好处和不足。

2025-07-22 10:12:38 803

原创 linux驱动开发(20)-DMA(四)

函数首先确定当前DMA池分配的对齐指标。函数通过dma_map_page来映射scatterlist上的page_link,跟x86平台不一样的是,ARM架构需要通过软件来保证cache一致性问题,所以做完这种虚拟物理地址的转换之后,ARM需要做的是使映射区对应的cache无效,以保证设备通过DMA将数据放到主存之后,CPU读到的不是cache中的数据,或者是保证CPU写到RAM中的数据立刻反映到RAM中,而不是暂时缓存到cache中,这样后续DMA在把主存中的数据传到设备中时,才能确保数据的有效性。

2025-06-25 09:34:11 762

原创 linux驱动开发(19)-DMA(三)

如同建立一致性映射的dma_alloc_coherent函数一样,dma_map_single内部用来完成实际的流式映射操作的代码也是体系架构相关的,内核通过struct dma_map_ops对象来屏蔽这种平台的差异,当然具体的平台需要提供其特有的struct dma_map_ops对象来供内核中的DMA层使用。的指针,cpu_addr是CPU的虚拟地址,也是流式映射DMA需要映射的区域,参数size指明了当前流式映射的空间范围,参数dir则用于表明当前的流式映射中DMA传输通道中的数据流向。

2025-06-25 09:33:51 699

原创 linux驱动开发(18)-DMA(二)

比如在该图中,如果RAM与Device之间的一次数据交换改变了RAM中DMA缓冲区的内容,假设在这个案例里恰好cache中缓存了DMA缓冲区对应的RAM中一段内存块,如果没有一种机制确保cache中的内容被新的DMA缓冲区数据所更新(或者无效)​,那么很明显cache和它对应的RAM中的一段内存块在内容上出现了不一致性。另外,这种一致性映射所获得的DMA缓冲区的大小都是页面的整数倍,如果驱动程序需要更小的一致性DMA缓冲区,则应该使用内核提供的DMA池(pool)机制,稍后讨论DMA缓冲池。

2025-06-24 09:17:12 1247

原创 软件设计与软件工程(7)-需求分析

目标要反映关键利益方的诉求需求不是“业务方告诉我怎么做,我就怎么做”​,而是要理解“为什么做这件事情”​。正如福特汽车公司的创办者亨利·福特的名言:(在汽车发明之前)如果你问客户需要的是什么,客户会告诉你,我需要的是一匹“更快的马”​。理解需求背后的业务目标非常重要。在上面的名言中,受限于认知水平,客户给出的并不是最恰当的解决方案—毕竟客户没有见过汽车,只知道骑马这样一种在当时相对较好的交通方式。但是,客户的诉求—能很快抵达目的地则是比较确定的。

2025-06-24 09:16:52 772

原创 软件设计与软件工程(6)-优秀代码

刻板遵循设计范例的现象比较常见,例如,我还见过某个团队,因为设计一般是分成若干层,如三层架构的Controller、Service和DAO,所以即使自己的项目不是一个典型的数据库相关的项目,也要硬性分出这些层次,其实每个类都只是简单地向下一级传递了一次调用而已,这就属于典型的多余设计。这是一个非常有效的技巧,之所以有效,是因为:编写良好的自动化测试代码,是最好的产品文档。更进一步,几乎所有的规范项目,都应用了版本管理系统,如git工具,因为有这些工具的存在,所以任何历史上曾经存在的代码,都可以方便地找回。

2025-06-23 09:13:07 720

原创 软件设计与软件工程(5)-优秀代码

下图展示了几种不同情况下的依赖示例。图中的每一个圆圈都代表一个设计单元,它可能是类,也可能是模块或者系统。箭头代表设计单元之间的某种依赖关系,如A使用了B的方法或者数据,甚至A只是简单地共享了B的知识,那么A就依赖于B。(不要将依赖局限于调用,若B改变则A也必须改变。那么A就依赖B)下面分别介绍这四种依赖形态对耦合的影响。(1) 循环依赖造成紧耦合。图中的①表示A和B之间存在循环依赖。循环依赖是一种非常紧的耦合。因为A的变化会引起B的变化,B的变化也会引起A的变化,所以A和B本质上是一个整体,而不是两个不同

2025-06-23 09:12:40 945

原创 软件设计与软件工程(4)-优秀代码

在我们的开发过程中会遇到不同类型的 依赖关系,相应的,这些依赖关系都有对应的解决方案来降低耦合度。登录逻辑的变化频率一般较低,但是消费记录的逻辑,以及消费记录的展示是否要和登录动作放在一起都有更多变化的可能。.从易于复用的角度看,登录功能本来是一个通用资产,可在各种场景下使用,但是加入了和消费记录相关的信息后,就只能在本系统中使用了。尽管在某个特定的业务场景下,消费记录和用户的登录动作会被组合,但是从概念上看,这样的设计是不内聚的,它们至多是相关的概念,很难说是“紧密相关”​。其实,这是没有唯一答案的。

2025-06-22 08:16:54 876

原创 软件设计与软件工程(3)-优秀代码

风格一致是对代码最基本的要求,门槛不高。在养成好的编程习惯、建立好的编程规范之后,风格一致是很容易做到的。形象地说,风格一致指的是“代码看上去像同一个人写出来的”​。(即使是这么简单,规则明确的要求在很多情况下都做不到,因为不少代码的committer并不重视这点)代码是软件开发活动中最基本的信息媒介,也是软件存在最重要的载体。没有人希望看到一段代码是一种布局,换一段代码就变成了另外一种布局。也没有人希望看到在一个地方是一种异常处理方式,在另外一个地方又是另一种。类似的情况还有很多。(其实背后蕴藏这破窗效应

2025-06-22 08:16:32 984

原创 软件设计与软件工程(2)-优秀代码

通过恰当的确定问题域的边界,如把一个订餐系统切分为用户、订单、支付、配送、消息通知等子域,并保持各个子域边界之间的抽象和隔离,就可以大幅提升问题的通用性,从而增加复用机会。此外,通过恰当的抽象,让代码在面临新的业务场景时更容易扩展,甚至完全无须修改原来的代码,这就是面向对象设计中的开放 - 封闭原则。尽管软件行业在框架层面的复用已经取得了突出的进步,如平台级的k8s1、框架级的Spring2等,但是在业务层面,还有许多业务组件都必须从头写起,很难在不同的场景下复用。,是代码质量的基础,也是代码演进的基础。

2025-06-20 08:41:33 1209

原创 软件设计与软件工程(1)-优秀代码

好的代码特征可以分为外部特征和内部特征。其中,外部特征是高质量代码应有的外在表现,从结果角度衡量。对这些特征的判断无须深入代码,即使是一个不懂软件的人,也能从外部感知到。内部特征则体现了代码是否“专业”​,从代码的内部质量角度衡量。经验丰富的软件工程师,只需要大致读一下代码,就能感知到代码的大致质量。优质代码的外部特征可以概括为以下5条。.实现了期望的功能。(实现了需求要求的功能)。.缺陷尽量少。 (问题单尽量少).易于理解。 (交付的代码同事之间容易看懂).易于演进。.易于复用。前两条和代码的外部质量有

2025-06-20 08:41:18 962

原创 linux驱动开发(17)-DMA(一)

直接内存访问DMA(Direct Memory Access)用来在与之间直接进行数据交换,因为这个过程无须CPU的干预,所以对于与系统有大量数据交换的设备而言,如果能充分利用DMA特性,可以大大提高系统性能。这种情况对设备驱动程序提出了新的要求,即必须能很好地支持设备的DMA操作。我们将首先讨论Linux内核中的DMA层,然后再讨论DMA操作的核心即DMA内存映射,包括一致性DMA映射、流式DMA映射和分散/聚集映射。

2025-06-19 08:53:51 1188

原创 linux驱动开发(15)-内存映射mmap(三)

vmalloc_32分配一段连续的虚拟地址,然后通过内核的伙伴系统分配相应的物理页面并提交到前面的虚拟地址上。现在我们有了设备内存,它保存在cam->frame_buffer中,接下来的核心是在while循环中,它首先通过kvirt_to_pa(pos)得到设备内存所映射到的物理内存地址,然后通过remap_pfn_range将用户进程空间的虚拟地址映射到该物理内存页面上,这里每次调用remap_pfn_range映射一个物理页面,直到所有的设备内存页面全部被映射完毕。现在显卡设备多以PCI设备形式存在,

2025-06-19 08:53:30 698

原创 linux驱动开发(16)-内存映射mmap(四)

这里再给出一个将内核空间某一物理页面映射到用户空间的例子,这里的物理页面其实可引申为设备的内存(比如某PCI-E显卡设备的Frame Buffer所在的这个例子将展示用户空间如何通过mmap来映射某一段物理地址空间并对其进行操作。内核空间的物理页面通过alloc_pages获得,其对应的将用printk打印出来,这样用户空间才可以告诉mmap函数要映射到哪个物理页面上。为了使代码简洁,程序去除了对错误情况的处理。内核模块使用了定时器每隔10秒钟打印被用户空间映射的。

2025-06-18 09:08:36 620

原创 linux驱动开发(14)-内存映射mmap(二)

现在继续回过头来看看do_mmap_pgoff函数的后续部分。实际的映射工作在mmap_region函数中完成(显然需要设备驱动程序中实现的mmap方法的配合)​。当该函数被调用时,参数addr已经指向了一块空闲的待映射的MMAP区域中的起始地址,所以函数会首先利用kmem_cache_zalloc分配出一个struct vm_area_struct实例对象,然后对其初始化。

2025-06-18 09:08:13 1109

原创 linux驱动开发(13)-内存映射mmap(一)

内存映射与内存分配不同,它要完成的任务是将映射到用户空间或者直接使用用户空间中的地址,设备程序这样做的目的显然是从提升系统性能的角度出发。如果将这种概念更具体化,内存映射部分实际上是描述如何实现设备驱动程序中file_operations中的mmap方法。

2025-06-17 09:28:47 813

原创 linux驱动开发(12)-原子操作和队列

系统中的Task A和B运行之后,g_flag会是多少,2吗?答案是有可能!这是一个典型的对变量的非原子操作可能导致错误结果的例子。如果Task A先被调度运行,在其执行完L1尚未执行L2时,Task B开始被调度执行,在其执行完整个代码后g_flag=1,然后系统又调度Task A从L2处继续执行,因为此前已执行了L1,导致EAX=0,经L2后,EAX=1,于是在L3执行完后,g_flag=1。

2025-06-17 09:28:27 978

原创 linux驱动开发(11)-其它互斥机制

顺序锁的设计思想是,对某一共享数据读取时不加锁,写的时候加锁。为了保证读取的过程中不会因为写入者的出现导致该共享数据的更新,需要在读取者和写入者之间引入一整型变量,称为顺序值sequence。读取者在开始读取前读取该sequence,在读取动作完成后再重读该值,如果与之前读取到的值不一致,则说明本次读取操作过程中发生了数据更新,读取操作无效。因此要求写入者在开始写入的时候要更新sequence的值。Linux内核中seqlock定义如下:无符号型整数sequence用来协调读取者与写入者的操作,spinl

2025-06-16 08:44:29 563

原创 linux驱动开发(10)- 互斥锁mutex

endif };#endif如同struct semaphore一样,对struct mutex的初始化不能直接通过操作其成员变量的方式进行,而应该利用内核提供的宏或者函数。1structmutex如果在程序执行期间要初始化一个mutex变量,则可以使用mutex_init宏。

2025-06-16 08:44:05 806

原创 linux驱动开发(9)- 信号量

其中,lock是个自旋锁变量,用于实现对信号量的另一个成员count的原子操作。无符号整型变量count用于表示通过该信号量允许进入临界区的执行路径的个数。wait_list用于管理所有在该信号量上睡眠的进程,无法获得该信号量的进程将进入睡眠状态。如果驱动程序中定义了一个struct semaphore型的信号量变量,需要注意的是不要直接对该变量的成员进行赋值,而应该使用sema_init函数来初始化该信号量。

2025-06-14 09:55:42 1096

原创 linux驱动开发(8)- spin_lock变体

与spin_lock_irq类似的还有一个spin_lock_irqsave宏,它与spin_lock_irq函数最大的区别是,在关闭中断前会将处理器当前的FLAGS寄存器的值保存在一个变量中,当调用对应的spin_unlock_irqrestore来释放锁时,会将spin_lock_irqsave中保存的FLAGS值重新写回到寄存器中。在raw_spin_unlock_irq函数中除了调用do_raw_spin_unlock做实际的解锁操作外,还会打开本地处理器上的中断,以及开启内核的可抢占性。

2025-06-14 09:53:30 1094

原创 linux驱动开发(7)-互斥与同步

Linux内核为设备驱动程序等内核模块提供的互斥与同步的内核机制。如果运行的系统中自始至终只有一个执行路径,那么无须考虑互斥与同步的问题,然而不幸的是,现代的Linux系统不只支持多进程而且支持多处理器,在这样的环境下,当多个执行路径并发执行时确保对共享资源的访问安全是驱动程序员不得不面对的问题。概括地说,互斥是指对资源的排他性访问,而同步则要对进程执行的先后顺序做出妥善的安排。因为程序的并发执行而导致的竞态是Linux内核中一个非常复杂的方面。

2025-06-12 22:55:28 1085

原创 linux驱动开发(6)-内核虚拟空间管理

此时就可以使用per-CPU变量,让系统中每个处理器都使用独属于自己的该变量的副本,这样在变量更新时就无须考虑多处理器的锁定问题,可以提高性能。实际代码中ioremap还有一些相关的变体,包括ioremap_nocache、ioremap_cached等,这些变体的主要功能是通过加入一些映射标志位来影响相关内核页表项的设置,比如设备驱动程序中最常用的ioremap_nocache,就是通过清除页表项中的C(ache)标志[插图],使得处理器在访问这段地址时不会被cache,这对外设空间的地址是非常重要的。

2025-06-12 22:54:47 1175

原创 linux驱动开发(5)-kmem_cache

函数会根据per-CPU缓存状态来尽量做最优化处理(内核总是挖空心思去干这种事情,还常常乐此不疲)​,但是我们可以想象free一个在kmem_cache中的内存对象,必然会引起一连串的连锁反应:如果要释放的对象恰恰是该slab上唯一被分配的对象,那么由于它的释放,将导致整个slab空闲,这种情况下内核有可能将该slab所对应的页面释放给伙伴系统,同时把该slab从kmem_cache对象的slabs_partial链表中摘除,更新对应的管理数据。参数cachep是要销毁的kmem_cache对象的指针。

2025-06-10 08:56:06 579

原创 linux驱动开发(4)-slab分配器(slab allocator)

页面分配器用于的分配,驱动程序中可以使用这些函数来分配连续的内存空间。然而只是有页面级的内存分配函数还不够,因为很多情况下我们需要分配比4 KB要小很多的物理地址空间,比如只有几十或者几百个字节,如果对这样的地址空间需求也分配一个完整的物理页,显然会对物理内存的使用造成巨大浪费。Linux系统在物理页分配的基础上实现了对更小内存空间进行管理的slab、slob和slub分配器。

2025-06-10 08:55:36 1071

原创 linux驱动开发(3)-页面分配器

Linux系统中对物理内存进行分配的核心建立在页面级的伙伴系统之上。在系统初始化期间,伙伴系统负责对物理内存页面进行跟踪,记录哪些是已经被内核使用的页面,哪些是空闲页面。有了伙伴系统就可以让系统分配单个物理页面或者连续的几个物理页面。驱动程序在内存分配时如果需要分配比较大的地址空间,可以在这一层面利用页面分配器提供的接口函数。这些函数(或者是宏)只能分配2的整数次幂个连续的物理页。

2025-06-09 09:18:51 1021

原创 linux驱动开发(2)-内核内存概览

Linux下对内存的管理总体上可以分为两大类:一是对物理内存的管理(RAM);二是对虚拟内存(地址空间)的管理。前者用于特定的平台构架上实际物理内存的管理,后者用于特定的处理器体系架构上虚拟地址空间的管理。我们讨论的内容是设备驱动程序中常用的内存分配函数及其实现机制。注意和用户态的内存分配函数相区分。Linux系统为了用统一的代码获得最大程度的兼容性,在对物理内存的定义方面,引入了内存节点(node)​、内存区域(zone)和内存页(page)的概念。

2025-06-09 09:18:30 954

原创 linux驱动开发(1)-内核模块

本文摘要:Linux内核模块机制允许动态扩展内核功能而无需重新编译或重启系统。内核模块以ELF格式文件存在,通过结构化的头部、节区和节区头表组成。EXPORT_SYMBOL宏实现的核心机制包含三个部分:宏定义创建符号表条目(包含函数地址和名称)、链接脚本指导链接器组织这些条目、以及运行时通过特定节区解析未定义符号。该机制通过将符号信息放置到__ksymtab等专用节区中,使内核模块能动态链接到内核导出的函数,实现模块化开发与动态加载的灵活性。

2025-06-01 16:53:20 958

原创 深度学习总结(41)

卷积神经网络是用于计算机视觉任务的最佳机器学习模型。即使在非常小的数据集上从头开始训练一个卷积神经网络,也可以得到不错的结果。卷积神经网络通过学习模块化模式和概念的层次结构来表示视觉世界。模型在小型数据集上的主要问题是过拟合。在处理图像数据时**,数据增强是降低过拟合的强大方法。利用特征提取**,可以很容易地将现有的卷积神经网络复用于新的数据集。对于小型图像数据集,这是一种很有用的方法。作为特征提取的补充,你还可以使用微调技术,将现有模型之前学到的一些数据表示应用于新问题。这种方法可以进一步提高模型性能。

2025-05-31 11:32:27 884

原创 深度学习总结(40)

有以下两种方法可供选择。在我们的数据集上运行卷积基,将输出保存为NumPy数组,并保存在硬盘上,然后将这个数组输入到一个独立的密集连接分类器中​。这种方法速度快,计算代价低,因为对于每张输入图像只需运行一次卷积基,而卷积基是当前流程中计算代价最高的。但出于同样的原因,这种方法无法使用数据增强。在已有模型(conv_base)上添加Dense层,并在输入数据上端到端地运行整个模型。这样就可以使用数据增强,因为每张输入图像进入模型时都会经过卷积基。但出于同样的原因,这种方法的计算代价比第一种要高很多。

2025-05-31 11:31:52 1055

原创 深度学习总结(39)

如果使用数据增强技巧来训练新模型,那么模型将永远不会两次看到同样的输入。但模型看到的输入仍然是高度相关的,因为这些输入都来自于少量的原始图像。我们无法生成新信息,只能将现有信息。因此,这种方法可能不足以完全消除过拟合。为了进一步降低过拟合,我们还会在模型的之前添加一个Dropout层,如代码清单所示。关于随机图像增强层,你应该知道一点:就像Dropout层一样,它在推断过程中(调用predict()或evaluate()时)是不起作用的。在评估过程中,模型的表现与不采用数据增强和dropout时一样。

2025-04-27 08:59:34 1144

原创 深度学习总结(38)

更一般地说,我们可以使用许多有用的dataset方法,举例如下。.shuffle(buffer_size):打乱缓冲区元素。.prefetch(buffer_size):将缓冲区元素预取到GPU内存中,以提高设备利用率。.map(callable):对数据集的每个元素进行某项变换(函数callable的输入是数据集生成的单个元素)​。.map()方法很常用。我们来看一个例子:利用这一方法将数据集元素的形状由(16,)变为(4, 4)。你会见到.map()的更多示例。

2025-04-27 08:59:12 872

原创 深度学习总结(37)

本例初始输入的尺寸为180像素×180像素(这是一个随意的选择)​,最后在Flatten层之前的特征图尺寸为7×7。注意 在模型中,特征图的深度逐渐增大(从32增大到128)​,而特征图的尺寸则逐渐缩小(从180×180缩小到7×7)​。(1)读取JPEG文件。这些步骤可能看起来有些复杂,但幸运的是,Keras拥有自动完成这些步骤的工具。具体地说,Keras包含实用函数image_dataset_from_directory(),它可以快速建立数据管道,自动将磁盘上的图像文件转换为预处理好的张量批量。

2025-04-26 15:38:37 946

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除