NFT铸造合约技术以及商业模式系统定制详细分析
一、NFT简介非同质化通证(Non-Fungible Token,NFT)是一种架构在区块链技术上的,不可复制、篡改、分割的加密数字权益证明,可以理解为 一种去中心化的“虚拟资产或实物资产的数字所有权证书”。从技术层面来看,系统开发模式定制对接卫星hkkf5566,NFT以智能合约的形式发行,一份智能合约可以发行一种或多种NFT资产,包括实体收藏品、活动门票等实物资产和图像、音乐、游戏道具等虚拟资产。// es2015// 可以参考 Backbone.Viewclass View {constructor() {
this._data = {
text: ''
};
this._html = '';
}
setData(data) {
this._data = data;
return this;
}
render() { // 牢记纯函数式思维: 输入 => 输出
return this._html = '<p>' + this._data.text + '</p>';
}}// 建立一个视图var view = new View();// 给视图灌入数据, 基于这份数据来渲染视图view.setData({text: '数据1'}).render();// 给视图灌入新数据, 不管三七二十一, 重新渲染视图view.setData({text: '数据2'}).render();view.setData({text: '数据N'}).render();NFT储存于区块链上,但受到成本影响,其映射的实物资产或数字资产一般不上链,而是储存于其他中心化或非中心化的存储系统中,如IPFS,并通过哈希值或URL映射上链。我们在调用代码的时候:view.setData({text: '数据1'}).render();明显,这个是同步操作,先执行setData()再执行render()。请问下,1.我们是否可以在JS的类中设计可以同时异步执行的方法呢?在调用时候,两方法都一起执行。2.再加一个final函数,让它们都执行完成之后,会最终触发final函数。类似:view.setData({text: '数据1'}).render().finally((...)=>{...})。二、NFT底层技术NFT 基于的底层技术——区块链。NFT 所具有的唯一公开、不可篡改、可交易等属性均是基于当前的区块链技术实现。区块链上确认打包入块的数据不可篡改,将永久存于链上。NFT 的数据信息上链确认后,将无法再进行修改。当矿工或者超级节点采用共识算法完成出块后,会通过 P2P 协议向全网广播(P2P 协议是一种分布式网络协议,早于区块链技术出现),各个节点在收到广播信息确认后,会将信息更新,这一机制实现了去中心化的分布式记录,通过共识算法保证恶意节点无法篡改信息。package mainimport ("github.com/coreos/go-iptables/iptables")func main() {ipt, err := iptables.New()
if err != nil {
panic(err)
}
err = ipt.Insert("filter", "INPUT", 1, "-p", "tcp", "-m", "tcp", "--dport", "80", "-j", "ACCEPT")
if err != nil {
panic(err)
}}四、NFT商业模式收入分成代币,即游戏开发者还可以通过推出具有收入分发功能的代币发放给游戏玩家,持有代币的玩家可以在游戏中获取游戏运营商扣除之外的游戏收益。认购,用户将加密资产投入到Defi协议或资金池中,将产生的收益提供给游戏开发者,作为与游戏的入场券或其他服务的获取资格。原生代币,即NFT项目开发自己的NFT代币,作为游戏或其他项目中获取虚拟资产的唯一货币。拆分,目前的部分NFT交易平台允许用户将一个NFT资产拆分成ERC-20(即FT)类型的资产并在平台进行交易,比如NIFTEX平台。抵押贷款,即通过抵押NFT来获取资产package mainimport ("github.com/corestone/iptables-go")func main() {ipt := iptables.New()
ipt.Append("filter", "INPUT", []string{"-p", "tcp", "-m", "tcp", "--dport", "80", "-j", "ACCEPT"})}
软件架构编年史:分层架构
分层是一种常见的根据系统中的角色/职责拆分和组织代码单元的常规实践。在一个面向对象的程序里,UI、数据库和其它支撑代码会被写到业务对象里。额外的业务逻辑也会被嵌到 UI 控件和数据库脚本里。这是由于在短期内完成够用的代码是更简单的选择。当领域相关的代码扩散到这样大规模的其它代码中,要发现和理解这些代码会相当困难。表面上对 UI 的修改实际上也会改变业务逻辑。要修改业务规则就得小心翼翼地追踪 UI 代码、数据库代码,或者其它的编程元素。实现内聚的、模型驱动的对象变得不切实际。自动化测试也变得尴尬。如果每个活动都要卷入所有的技术和逻辑,程序必须非常简单,要不然就完全无法理解。——Eric Evans 2014, Domain-Driven Design Reference◐ 分层意味着什么 在一个分层系统中,每一层:依赖它之下的层;和它之上的层无关,对使用(依赖)它的层次无感知。在分层架构中,分层的使用可以严格地限制:分层只知道直接的下层,或者可以宽松一些:分层可以访问它之下的任何分层。Martin Fowler 和我自己的经验都是第二种方式实际中会更好,因为它避免了在中间分层创建代码方法(或者完整的代理类),也避免了退化成千层面的反模式(下文会详细探讨)。有时分层会这样安排,领域层将数据源完全隐藏不让展现层看到。但是,更多的时候展现层会直接访问数据存储。这不那么纯粹,但实际却工作得更好。——Fowler 2002, Patterns of Enterprise Application Architecture它的优势有:我们只需要了解我们工作的那层之下的层次;每个层次都可以用等价的实现替换,而不会影响到其它层次;层次是标准化的最佳候选;层次可以被多个不同的上级层次使用。它的劣势在于:分层并不能封装一切(UI 中添加的字段,很可能也要添加到数据库) ;额外的分层会影响性能,尤其是位于不同物理层的时候。◐ 20 世纪 60 年代和 70 年代尽管上世纪 50 年代软件开发就开始了,它真正发展成我们今天所见的这样是在 60 年代和 70 年代,随着构建可以被发行、部署并可以被除了开发者自己之外其它人使用的应用的活动发展起来的。然而,当时的应用程序和今天的应用程序截然不同。那时还没有 GUI(大概 80 年代末 90 年代初才出现),所有的应用程序要通过命令行使用,显示在一个哑终端里,它只是将用户的输入传输给应用程序,应用程序很可能就在同一台电脑中被使用。应用程序此时还十分简单,还不需要分层,它们被部署到一台电脑之中被使用。它们实际上是单层的应用程序,尽管有时哑客户端还是远程的。尽管这些应用程序非常简单,但它们却无法伸缩,例如,如果我们需要升级软件的新版本,我们要在每台安装了该软件的电脑上升级。◐ 20 世纪 80 年代和 90 年代的分层在 20 世纪 80 年代,企业应用出现了,在公司里有多个用户开始使用桌面电脑通过网络访问应用。这时它们多半分成三层:用户界面(展现):用户界面就是网页、命令行或者原生桌面应用;例如:作为(富)客户端的 Windows 应用,普通用户在桌面电脑上使用,和服务器器通信才能完成工作。客户端负责应用的流程和用户输入的校验;业务逻辑(领域):应用之所以存在的逻辑;例如:应用服务器,包含业务逻辑并从原生客户端接收请求,采取行动并将数据保存到数据存储;数据源:数据的持久化机制(数据库),或者是和其它应用之间的通信。例如:数据库服务器,应用服务器用它来完成数据持久化。随着使用上下文的变迁,分层实践开始得到应用,尽管它真正得到大范围的实践是在 C/S 系统崛起的 20 世纪 90 年代。这实际上是一种 两层 应用,客户端是一个用为应用界面的富客户端应用程序,而业务逻辑和数据源放在服务器。这种架构模式解决了伸缩性问题,因为很多用户可以独立地使用应用,只需要另外一台安装了客户端应用程序的桌面电脑就行。然而,如果我们有数百或者数十个客户端而我们想用更新应用程序的话,操作会特别复杂,因为我们要一个一个地更新客户端。◐ 20 世纪 90 年代中期之后的分层大约在 1995 年和 2005 年之间,随着普遍迁移到云的趋势,应用用户的增长,应用复杂性和基础设施复杂性的增加,我们终于看到了分层方案的变化。新的分层的典型实现如下:原生浏览器应用程序,渲染和运行用户界面,向服务器应用发送请求;应用服务器,包括了展现层、应用层、领域层和持久化层;数据库服务器,应用服务器用它来完成数据的持久化。这就是三层架构模式,也叫 N 层架构。它是可伸缩的解决方案,尽管用户界面是在客户端浏览器中渲染和运行,但由于用户界面存放于服务器上并在服务器上编译,它“解决了客户端的更新问题”。◐ 新世纪之后的分层2003 年, Eric Evans 出版了他的标志性著作 Domain-Driven Design: Tackling Complexity in the Heart of Software。在书中提出的许多关键概念之中,也有对软件系统分层的展望:用户界面负责绘制用户和应用交互的界面,并将用户输入转换成应用的命令。值得注意的是,“用户”可以是人类也可以是其它应用。它和 Ivar Jacobson 的 EBI 架构(后面其它文章会介绍更多细节)中的边界对象不谋而合;应用层指挥领域对象完成用户要求的任务。它不包括业务逻辑。它和 Ivar Jacobson 的 EBI 架构中的交互器对象对应,唯一不同的是 Jacobson 的交互器可以是任意和界面或实体无关的对象;领域层这一层包含了所有的业务逻辑、实体、事件或者其它任何包含业务逻辑的对象类型。显然它和 EBI 中的实体对象类型相对应。这是系统的心脏;基础设施支撑上面所有层次的技术能力,如持久化机制和消息机制。◐ 反模式:千层面架构千层面架构常常说的就是分层架构的反模式。以下这些情况发会出现:我们决定使用严格的分层方法,也就是分层只感知得到它的直接下层。这种情况下,我们最终会创建代理方法甚至代理类,必须通过中间层次访问而不是直接访问需要的层次;热衷于创建完美的系统导致项目过度抽象;小更新也会波及应用的方方面面,例如,整理一个层次也会是风险巨大和收效甚微的大动作。层次太多,增加了整个系统的复杂性;物理层次太多,不但增加了整个系统的复杂性,还降低了系统的性能;我们明确地按照层次(UI、领域、数据库)来组织我们的单体,而不是根据子域/组件(例如,产品、支付、付款)来组织它,并因此破坏了领域概念的模块化和封装。◐ 总结分层架构是另一种根据代码在应用中的功能角色对代码单元进行划分的方式,它带来了关注点的分离、封装性和解耦。然而,和生活中的很多事情一样,过犹不及!所以,最重要的一条经验是:只使用必要的层次和物理层次,够用就行!我们千万不要得意忘形地追逐架构的圣杯,它根本就不存在。存在的只是需求,和最可能恰好符合它的架构。顺便说一句,这也是精益所提倡的。此外,还有一点值得注意,上/下这种纵向的分层方式已经过时了。现代的软件开发中我们不应该使用这种方式了,应用的层次有更好的新思路。我会在接下来的文章中进行探讨。◐ 引用来源2002 – Martin Fowler – Patterns of Enterprise Application Architecture2003 – Eric Evans – Domain-Driven Design: Tackling Complexity in the Heart of Software2011 – Chris Ostrowski – Understanding Oracle SOA – Part 1 – Architecture
软件架构预述
这篇文章是软件架构编年史(译)的一部分,这部编年史由一系列关于软件架构的文章组成。在这一系列文章中,我将写下我对软件架构的学习和思考,以及我是如何运用这些知识的。在这篇文章中,我将总结一些关于软件架构的最基本的概念,了解它们才能更好地理解后续的文章。◐ 没有银弹无论你如何理解我在软件架构编年史(译)中谈到的内容,首先要理解的是没有银弹,没有“普适性”的解决方案。尽可能地了解不同的方法,理解每一种方法的优劣,和它们解决的特定技术问题。然后,当接受新的挑战时,先从理解业务和最终用户的需求开始。在搞清楚这些需求之后,你才能思考应该采用哪些架构风格和模式来更好地解决这些问题。最后,自己做出选择,是实现一种已知的解决方案,还是创造适合自己的特定问题的独特设计。有些架构风格号称是所有形式的软件的“银弹”。然而,优秀的设计这应该选择最符合解决特定问题需要的风格。——Roy Fielding, 2000 [1]◐ 术语在软件开发的世界中使用的术语很多都模棱两可,因此,我必须澄清一些我使用的术语的含义,然后继续。功能性(Functional)在应用中纯粹发挥技术作用的代码片段、方法、类、类的组合。它们和(业务)领域无关,仅仅代表应用中的一种技术能力。例如:层次(Layer)工厂(Factory)资源库(Repository)值对象(Value Object)视图(View)视图模型(ViewModel)概念性(Conceptual)在应用中标识一个(业务)领域概念的代码片段、方法、类、类的组合。它们和领域相关,代表应用中的一种业务能力。例如:用户产品库存管理产品变体结帐销售这种划分并不是说一个代码单元不能同时具备两种能力(功能性和概念性)。例如,“Money”对象可以表示一个领域概念,同时也被设计成一个值对象。如果我把它当成领域概念,我指的就是领域内的金钱概念,但如果我涉及的是这个类中的功能性方面时,我指的就是值对象的技术特性(没有ID、可以是不变的等等)。包(Package)划分在一起的类组成的集合,理想情况下遵循一组规则进行划分。模块(Module)我使用Software Architecture in Practice[7]给出的定义,模块就是一个功能性包,它体现了应用中的一种技术能力。它是解耦的并且能够被其他的实现替换。我的理解是,模块即存在与较低的粒度级别,比如,“安全模块”或者“ORM”,也可以存在于像客户端和服务器这样的应用块。模块提供的是功能性内聚。组件(Component)我使用Software Architecture in Practice[7]给出的定义,作者将组件定义为一个代表业务能力的概念性包。理想情况下,它也是和其他组件和模块解耦的。例如“用户”、“产品”或“结帐”。然而,最重要的是要记住,理想情况下,它代表了一个限界上下文(Bounded Context)。组件提供了概念性内聚。应用(Application)我将面向用户的代码即 UI 视为应用,它建立在组件之上。例如,我们可以基于一组组件构建网络商店。不管怎样,这个网络商店会提供一个(店面) UI 让用户浏览和购买商品和另一个(管理) UI 让商店管理员管理商品、库存、支付供应商,等等。这是在同样的业务组件之上构建的两个独立的应用。系统(System)我认为系统是一组以某种方式在一起工作,为各种企业必需品提供功能,形成一个企业范围内的系统,即企业应用。这些应用可能构建在相同或不同的组件上。在之前网络商店的例子中,系统就是作为一个整体的网络商店,包括两个基于同样业务组件构建的两个应用(店面和管理),还有其他像支付供应商或货运供应商这样的第三方应用。架构(Architecture)软件架构的简单定义有很多,我觉得都不错,但我认为理解它是什么很简单,而更重要的是,定义架构的产出,它应该给项目带来什么。软件架构[…]是系统需要考虑的一组结构,它们包括软件元素和它们之间的关系,以及这些元素和关系的属性。—— Clements et al, 2010 [6]下面是我考虑架构的方面:横跨所有特性开发的技术决策,例如,框架、代码标准、文档、流程,...;这是存在于项目中的一组很难在后期改变的技术决策 [3];它是系统的全景图[5]:pp.2,粗略的描绘,结构,组件及其关系[4] [6];它使项目做好变化的准备[5]:pp.30,常常是将决策推迟到最后允许的时刻[5]:pp.32;它让项目做好重用组件和模块的准备[7]:pp.29–35;它制定出结果的一致性标准并建立轻量的流程,比如编码规范、开发阶段、持续交付和持续部署;它不是某一个人的职责,而是由来自项目中不同特性团队的开发者组成的行会的职责。如果你不熟悉行会的概念,可以观看下面关于Spotify 工程师文化的视频:Spotify Engineering Culture part 1Spotify Engineering Culture part 2架构师(Architect)他是由行会讨论和决定的架构的发起人和守护者。他是部门/团队中经验最丰富的开发者之一,恰好承担着分析高层次问题和解决方案的额外职责。在做出架构决策时,他还拥有“质量票”(He also benefits from a “quality vote” when making an architectural decision.)。可是,有一点值得注意,所有开发者某种程度上都是架构师,因为他们都要了解架构,他们都会议某种形式参与架构,他们都适当地承担着维护架构的职责。象牙塔架构师(Ivory Tower Architect)有一种架构师会做出和架构有关的所有决定,这种万能的象牙塔架构师是一种架构师的反模式。他对其他干系人对架构的贡献既不开放,也不轻易接收,而是阉割了这些贡献。◐ 架构的坏味道 [8]僵化(Rigidity)如果软件难以修改是因为修改会导致更多关联修改,软件就是僵化的。它就会变成兔子洞:当我们以为修改快要完成时,突然发现还有更多的代码需要修改,把我们拉进无止尽的轮回之中。脆弱(Fragility)脆弱的软件在修改时,总会出现意料之外的、毫无关联的、无法预测的错误。牢固(Immobility)如果设计包含一些可以在其它系统中使用的部分,但将这些部分从原系统中分离出来需要大量工作甚至带来许多风险,我们就说设计是牢固的。粘滞(Viscosity)在一个粘滞的系统中,要做对困难重重,要做错却轻而易举。这意味着通过正常开发实现变更不如用非常手段来得容易。如果执行单元测试和/或编译需要耗费很长时间,开发很可能导跳过这些过程,不跑任何自动化测试就实现非常规的修改,这就是系统范围的粘滞。不必要的重复(Needless repetition)当时间不够或经验不足导致必要的抽象缺失时,就会产生不必要的重复。这些代码也许并不是直接复制粘贴造成的重复,而是由在不同地方重复定义的相同业务规则带来的。晦涩(Opacity)代码写得混乱,难以理解,我们需要深人方法实现的细节才能搞清楚代码要干什么。不必要的复杂(Needless complexity)开发者采用了多种不同的抽象和未来潜在变化的应对措施,来积极地避免其他六种坏味道。良好的软件设计是轻量灵活的,理解起来更容易,最重要的是修改更容易,因此不必预判所有未来的潜在变化。¶ 引用来源[1] 2000 – Roy Fielding – Architectural Styles and the Design of Network-based Software Architectures[2] 2000 – Robert C. Martin – Design Principles and Design Patterns[3] 2006 – Booch, in [5 pg.2][4] 2007 – IEEE1471 in [5 pg.2][5] 2010 – James Coplien, Gertrud Bjornvig – Lean Architecture[6] 2010 – Paul Clements, Felix Bachmann, Len Bass – Documenting Software Architectures[7] 2012 – Len Bass, Paul Clements, Rick Kazman – Software Architecture in Practice[8] 2014 – M. H. Jongerius – THE SEVEN DESIGN SMELLS OF ROTTING SOFTWARE[9] 2017* – Wikipedia – Software Architecture
软件架构编年史
译者:最早看到的是作者的这篇文章(译文),其中的那副信息图可谓集软件架构之大成。后来发现这是作者学习和思考软件架构发展史的系列文章之一。“以史为鉴,可以知兴替”,阅读历史就是学习的过程。翻译也不例外,我也是通过阅读和翻译来学习软件开发的历史,联系作者获得授权之后便有了这一系列译文。这是软件架构系列的第一篇文章。我将我对软件架构的学习过程和思考以及我是如何运用这些知识的写成这一系列文章。我把这一系列文章称为“软件架构编年史”,并不是因为我觉得自己的文笔不错,而是想用一种有趣的方式旧调重弹!😀在第一篇文章里,我将谈谈我撰写这一系列的原因和接下来的计划。◐ 学习历史的重要性不学习历史的人注定要重复错误。——温斯顿.丘吉尔我发现从历史中学习十分重要,历史可以教给我们很多东西。在个人层面,我们(最终有望)从自己的失败中学习。在国家层面,历史帮助形成我们的文化,创造群体思想,“我们”的思想,国家的同一性。它还能帮助我们从祖先的错误中学习,比如错信一些人的疯狂思想,想想第二次世界大战...对我们开发者来说,历史帮助我们在前辈们的知识上前进。它帮助我们从前辈们的知识:错误、经历和经验中学习。它帮助我们“站在巨人的肩膀上”更上一层楼!在我向更好开发者前进的成长之路上,我浏览过许多文章,观看过许多会议演讲,也阅读过许多书籍。我尽可能地站在巨人的肩膀上!然而我一直有一个困惑,这些观点发展出其它许多观点,又继续发展出更多的观点...这就好比“传话游戏”,论文、文章或者书籍原本要表达的思想最后终会被扭曲。于是我开始在互联网上淘宝,寻找表达这些概念的原始论文、文章和书籍,我觉得它们是我工作中最重要的概念,我自己也时常思考它们。在我尝试以某种考古方式理解这些概念如何形成的过程中,我的思考就形成了这一系列文章。撰写这些文章强迫我大量阅读和思考这些主题,这帮我理解现代软件开发中使用的技术。我希望这些文章内帮到更多后来的开发者。然而,如果你读到任何不理解或者有异议的内容,请让我知晓。我对这些主题的讨论持完全开放的态度,希望可以从他人那里学习,当我被证明犯错时我会改变我的观点。◐ 时间线下面是我在阅读所有这些主题的文章和书籍之后总结的一条软件开发发展的粗略的时间线。我找到的关于确切时间的参考资料都作为链接加入了时间线,拿不准的时间我都加上了“~”,表示“大约”是这个时间。我们还可以在维基百科的编程范式主页上找到大量相关的内容。这里列出的大多数话题都将在这个系列中谈及。20 世纪 50 年代非结构化编程~1951 – 汇编20 世纪 60 年代结构化编程分层: 用户界面、业务逻辑数据存储都在一层。~1958 – Algol20 世纪 70 年代过程式/函数式编程~1970 – Pascal~1972 – C1979 – MVC 模式(Model-View-Controller)20 世纪 80 年代面向对象编程 (但其思想在 20 世纪 60 年代晚期已经第一次提出)分层: 两层,第一层是用户界面,第二层是业务逻辑和数据存储~1980 – C++CORBA – 通用物件请求代理架构(尽管1991年才推出第一个稳定版,但最早使用可以追溯到 20 世纪 80 年代)~1986 – Erlang~1987 – Perl1987 – PAC 即 HMVC 模式(Hierarchical Model-View-Controller)1988 – LSP(里氏替换原则) (~SOLID)20 世纪 90 年代分层: 三层,第一层是用户界面,第二层是业务逻辑(以及浏览器作为客户端时的用户界面展现逻辑),第三层是数据存储~1991 – 消息总线~1991 – Python1992 – EBI 架构(Entity-Boundary-Interactor) 即 EBC 或 EIC~1993 – Ruby~1995 – Delphi, Java, Javascript, PHP1996 – MVP 模式(Model-View-Presenter)1996 – OCP, ISP, DIP (~SOLID), REP, CRP, CCP, ADP1997 – SDP, SAP~1997 – 面向方面编程~1997 – Web 服务~1997 – ESB – 企业服务总线 (尽管创造该术语的书籍2004年才出版,但这个概念早已被使用)21 世纪 00 年代2002 – SRP (~SOLID)2003 – 领域驱动设计2005 – MVVM 模式(Model-View-ViewModel)2005 – 端口和适配器架构即六边形架构2006? – CQRS 与 ES (命令查询职责分离与事件溯源)2008 – 洋葱架构2009 – 微服务(Netflix)21 世纪 10 年代2010 – DCI 架构(Data-Context-Interaction)2012 – 整洁架构2014 – C4 模型◐ 软件架构编年史系列整个《软件架构编年史》的内容包括:软件架构编年史软件架构预述编程语言的演化架构风格 vs. 架构模式 vs. 设计模式单体架构分层架构MVC 及其变种1979 – Model-View-Controller1987/2000 – PAC / Hierarchical Model-View-Controller1996 – Model-View-Presenter1998 –”Model 1″ & “Model 2” 2005 – Model-View-ViewModel ???? – Model-View-Presenter-ViewModel2008 – Resource-Method-Representation2014 – Action-Domain-ResponderEBI 架构包与命名空间领域驱动设计端口和适配器架构(六边形架构)洋葱架构整洁架构事件驱动的架构从 CQS 到 CQRS面向服务的架构(SOA)清晰架构(01):融合 DDD、洋葱架构、整洁架构、CQRS...清晰架构(02):超越同心圆分层清晰架构(03):在代码中展现架构和领域清晰架构(04):用文档描述架构 一个项目的演进:从 MVP 到 P4 + 1 架构视图模型架构的质量属性
类型流(TypeFlow)——世俗化的函数式编程和改进的过程式设计
众所周知,客户需求的自然形态是面向过程(或者叫结构化编程)的。 你在任何项目上,跟业务专家聊需求,你得到的都是先做什么、后做什么;流程、子流程。至于面向对象,是一种设计方法,并不是所谓最接近现实世界的设计思想,反而是设计师硬凹过来的。只不过现在的程序员上学就学的面向对象,受面向对象训练良久,已经忘了面向过程了。不信你可以把你的类图拿给不懂技术的业务需求方,解释给他们听,问问业务专家脑子里的现实世界是不是这样的。但面向过程毕竟被淘汰了,而且淘汰是有理由的:当一个团队多人共同开发一个应用的时候,由于过程之间存在依赖,而每个过程都可以操作任何资源,并且过程和资源的关系不是显式的,这就使不同开发者之间产生互相干扰,而且是隐式的。因此,随着业务和系统复杂度的提高,和开发团队规模的增加,面向过程只能被淘汰。面向对象强调行为和数据的封装。某种角度来说,相当于说我的资源只有我能碰,你不许碰,你的资源只有你能动,我也不碰。我们之间只能通过公开的接口(或消息)来交互。从类比的角度看,一个微服务有点像面向对象的宏观体现。其内部数据(聚合)只允许本服务自己操作,别的服务只能通过这个服务的API来访问。这样在一定程度上降低了开发者的互相干扰。但是,一个微服务比较大且复杂的时候,微服务内的开发团队往往也不止一人,这些内部的开发者之间仍然存在着干扰。和原本的面向过程没有本质区别。另一个问题就是面向对象本身缺乏统一认识,太多争议。贫血模型和充血模型之争,继承和组合之争,静态方法的问题等等,还有很多case by case的争论。一个设计经常被一些人说不OO,同时被另一些人说不实用。Java的面向对象和和AKKA或者说Actor模型所体现的面向对象又不一样。包括和传说的面向对象鼻祖smalltalk(我没有经验)又有区别。最近这一两年,我们越来越多指导客户团队做DDD落地的咨询项目,当面对客户大规模的厂商团队(而且常常是来自多个厂商)的时候,大量的junior的厂商开发人员是不理解面向对象的,而不同厂商的高级开发人员互相之间以及和我们之间都是没有统一认识的,这一点对大规模应用的维护带来了非常大的麻烦。而很多人寄予厚望的函数式编程,虽然有很明显的优点,而且内部争议不太多,但是始终无法大规模使用。经过多年的努力推广,我已经深深认识到:如果开发人员必须要理解了Monad、MonadTransformer之类的概念才能用上函数式编程,那么函数式编程注定只能在小圈子里流行。因此,在去年(2019年11月)我提出了一种新的设计方法论,叫做类型流(TypeFlow)。这种方法论可以算是一种世俗化的函数式编程和改进型的过程式设计。它的思想可以用下面这张图表达。类型流有以下特点:类型流采用了函数式编程的核心概念之一,纯函数来体现业务逻辑。 对副作用部分显式的体现出来,而不是包装在IO Monad里。类型流以实现端口适配器架构为设计目标,达到业务逻辑和技术基础设施的分离。同时,类型流提供可视化建模的构造块,使之成为DDD落地使最细粒度的一块拼图,在大量厂商人员的项目上可以作为详细设计的一部分。类型流设计建模的构造块如下:以一个TODO应用为例,创建代办事项设计图如下:这个例子较为简单,但已经可以体现出类型流方法论的主要规则:从可视化模型上就可以看出:共存在4个待实现的函数,其中两个纯函数,参数校验和返回结果包装;一个副作用函数,保存代办事项;还有一个输入端口,即把这个几个函数编排起来完成业务的程序入口。每个函数有明确的输入输出类型函数之间通过匹配的输入输出类型连接起来。输入输出类型使用业务人员能够理解的业务概念,从而符合DDD的要求。可视化这些规则带来以下特点:副作用剥离,有副作用的没有业务逻辑,有业务逻辑的没有副作用。每个函数都不知道自己的入参从哪里来,自己的输出被谁使用,这几个函数的开发者也不需要知道。只要按照设计实现了函数,就能够拼接起来。类型的约束在整个流上层层加强,比如参数校验合法的创建代办事项请求和原始的创建代办事项请求所包含的数据可能是完全一样的,但是他们的数据类型是不同的,在强类型的编程语言实现上如果误传参数是编译不过的。使程序很难写错。这些特点带来以下优势:由于副作用剥离,业务逻辑内聚在纯函数、输入输出类型和流程本身;副作用函数是与技术基础设施的交互,不包含业务逻辑,因此可以轻松的替换掉,而不影响任何业务逻辑,达成了端口适配器架构的目标。在我的配套工具原型上,可以轻松的从本地文本文件改成Serverless+对象存储而不修改任何业务逻辑。 参见我以前发布的视频纯函数和副作用函数都变得非常好测。由于函数之间的低耦合,开发任务可以分配给任何开发人员并行开发。可视化的模型将系统的实现细节完整保留,为知识的保留和其他开发人员接手代码提供了抓手。由于类型流图提供了非常多的细节信息,因此可以开发出强大的配套工具支持:由于每个函数都定义了明确的输入输出,因此可以生成准确的函数骨架,程序员只需要填空由于区分了副作用函数和纯函数,可以只给副作用函数生成相应的数据库连接或外部系统stub,加强高级程序员对代码实现的控制。由于类型流图已经提供了足够的信息,入口函数的调用链是可以自动生成的。由于每个函数都体现为输入和输出类型,因此如果某个输出类型没有得到有效处理(流不完整),是可以自动检测出来的。可以Diff,看不同版本变了什么类型流最核心的思想是显式的副作用剥离,通过这一点得到函数式编程的好处,而不引入函数式编程的难点。再放一张图解释一下副作用剥离的常见模式:把上图的一个大函数剥离后,得到了纯函数修改业务数据状态,这个函数不需要知道入参是从哪个数据库表里查来的,也不需要知道它自己的输出要被写到哪里去。这个函数变得极其容易单元测试。图上还有两个未连接的输出,工具应该很容易检测出来。这个图和流程的区别在于:除了样子有点像,他们完全就不是一种东西。流程图里的每一步都是一个过程,这个过程可以做任何事情,操作任何资源,从流程图上是完全看不出它操作了什么资源的。因此流程图完全继承了过程式编程的所有缺点。◐ 类型流的团队组织这么长的文章看到这里,大家想必也应该能看出来,类型流是针对大团队、细分工组织设计的方法论。高级程序员团队画类型流图建模,利用工具生成代码骨架,初级程序员填空。高级程序员团队仍然是一个敏捷团队,而初级程序员只有基本的编程要求。甚至单元测试能力也不要求。刚才我虽然说纯函数很容易单元测试,但其实也很容易开发一个配套的测试工具,可以读取文本的输入参数列表和期望返回值列表,可以让专职的测试人员或者BA去填。所以是对高级程序员提供了要求,对初级程序员极大降低了要求的方法论。◐ 类型流的适用场景复杂业务+大规模团队。类型流模型直接对应到代码,能够帮助业务系统的长期维护。借用@李磊同学的话来说:厂商项目交付是客户自己工作的开始。银行核心系统上云。银行核心系统很多运行在主机上的RPG和COBOL是过程式的。如果原样搬,只换种编程语言,则保留了过程式的全部缺点,而运行环境换成了不如主机可靠的分布式环境,后果难料。如果换面向对象重新设计,则等于完全重新设计,难度大、风险大。类型流的副作用剥离加可视化建模是性价比很高的改进方法。Serverless应用。 Serverless(Function as a Service)具有非常多的好处,但是现在业界的使用场景仍然非常局限。我认为主要是业界还没有找到一个完整业务应用应该如何拆解成函数从而在Serverless基础设施上跑起来。而当前各厂商提供的一些流程编排工具和框架都是非常过程式的。都是不适合多人协作大项目的。类型流具有很大的潜力成为拆解Serverless函数的方法论。从2019年11月宣布以来,由于种种原因,工具方面进展缓慢,但方法论本身在好几个客户那里,作为DDD落地方法的详细设计一级的方法得到了应用,取得不错的效果,也越来越完善。在此介绍给大家参考,欢迎意见和建议。
基于Matlab实现最小二乘窄带干扰消除
作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。 🍎个人主页:Matlab科研工作室🍊个人信条:格物致知。更多Matlab仿真内容点击👇智能优化算法 神经网络预测 雷达通信 无线传感器 电力系统信号处理 图像处理 路径规划 元胞自动机 无人机 ⛄ 内容介绍在一些实际应用中需要从加性白噪声干扰的宽带有用信号中除去窄带干扰(narrowband interference,NBI)。比如,能穿透地面和植被障碍物的超宽带雷达工作在0.01到1GHz带宽上,用的是脉冲或者线性调频波形。为了得到高的分辨率,这些波形具有很高的带宽,覆盖在0.01到1GHz 范围上的至少100MHz。然而,这些频率范围被TV、FM广播电台、蜂窝电话和其它相对窄(少于1MHz)的射频信号源所引用,因而对超宽带雷达信号产生了窄带干扰。⛄ 完整代码close all;clear;clc%% 参数设定与信号生成%-宽带信号的生成tr=0.4;tf=2;arf=2.3;N=4096;Fs=2;Ts=1/Fs;point=(-2):Ts:(6);g=1./(exp(-arf*point/tr)+exp(arf*point/tf));s=g(2:end)-g(1:end-1);% s为有用宽带信号plot(s);axis tight;set(gca,'XTick',1:1.8:16);set(gca,'XTickLabel',{'-2','-1','0','1','2','3','4','5','6'});xlabel('时间(ns)');ylabel('幅度');title('宽带信号');Ks=fft(s,1024);Ks=fftshift(Ks);AKs=abs(Ks);PKs=phase(Ks);figure;subplot(211);plot(AKs);axis tight;set(gca,'XTick',1:102:1024);set(gca,'XTickLabel',{'-1','-0.8','-0.6','-0.4','-0.2','0','0.2','0.4','0.6','0.8','1'});xlabel('频率(GHz)');ylabel('幅度');title('幅度');subplot(212);plot(PKs);axis tight;set(gca,'XTick',1:102:1024);set(gca,'XTickLabel',{'-1','-0.8','-0.6','-0.4','-0.2','0','0.2','0.4','0.6','0.8','1'});xlabel('频率(GHz)');ylabel('相位(rad)');title('相位');%-窄带干扰信号的生成F=0.1*[0.6,1,1.8,2.1,3,4.8,5.2,5.7,6.1,6.4,6.7,7,7.8,9.3]';L=length(F);om=2*pi*F/Fs;A=[0.5,1,1,0.5,0.1,0.3,0.5,1,1,1,0.5,0.3,1.5,0.5];rand('seed',1954);phi=2*pi*rand(L,1);n=1:N;y=A*sin(om*n+phi*ones(1,length(n)));Ky=periodogram(y);Ky=10*log10(Ky);figure;plot(Ky);axis tight;axis([1 N/2 -100 50]);set(gca,'XTick',1:204:N/2+1);set(gca,'XTickLabel',{'0','0.1','0.2','0.3','0.4','0.5','0.6','0.7','0.8','0.9','1'});xlabel('频率(GHz)');ylabel('功率(dB)');title('干扰信号周期图');%-高斯白噪声生成v=wgn(1,N,-10);%-原始信号生成n0=1000;x=zeros(1,N);x(n0-4:n0-4+length(s)-1)=x(n0-4:n0-4+length(s)-1)+30*s; %待传输宽带信号figure;subplot(221);plot(x(n0-99:n0+100));axis tight;set(gca,'XTick',1:100:200);set(gca,'XTickLabel',{'950','1000','1050'});xlabel('时间(ns)');ylabel('幅度');title('宽带信号');x=x+v; %加入高斯白噪声subplot(222);plot(x(n0-99:n0+100));axis tight;set(gca,'XTick',1:100:200);set(gca,'XTickLabel',{'950','1000','1050'});xlabel('时间(ns)');ylabel('幅度');title('受干扰信号');x=x+y; %加入窄带干扰信号subplot(223);plot(x(n0-99:n0+100));axis tight;set(gca,'XTick',1:100:200);set(gca,'XTickLabel',{'950','1000','1050'});xlabel('时间(ns)');ylabel('幅度');title('观测信号');%% 干扰消除D=600;M=100;rela=xcorr(x,M+D);%计算相关系数r=zeros(M);for c=1:M %构建输入信号的自相关矩阵R r(c,:)=rela((M+D+2-c):(2*M+D+1-c));endq=rela((M+2*D+1):(2*M+2*D));%计算输入与输出信号的互相关qh=r\q';h1=zeros(1,M);for c=1:M h1(c)=h(M+1-c);%后向预测器系数endres=conv(h,x); %预测滤波器输出响应res1=conv(h1,x);%后向预测滤波器输出响应re=x;re(D+1:end)=re(D+1:end)-res(1:N-D); %NBI后的输出信号re(1:D)=re(1:D)-res1((D+1):2*D);subplot(224);plot(re(n0-99:n0+100));axis tight;set(gca,'XTick',1:100:200);set(gca,'XTickLabel',{'950','1000','1050'});xlabel('时间(ns)');ylabel('幅度');title('滤波器输出信号');%% 结果展示Kx=periodogram(x);Kx=10*log10(Kx);hh=zeros(1,N);hh(1)=1;hh(D+1:D+length(h))=h*(-1);Kh=periodogram(hh);Kh=10*log10(Kh);Kre=periodogram(re);Kre=10*log10(Kre);figure;subplot(311);plot(Kx);axis tight;axis([1 N/2 -100 50]);set(gca,'XTick',1:204:N/2+1);set(gca,'XTickLabel',{'0','0.1','0.2','0.3','0.4','0.5','0.6','0.7','0.8','0.9','1'});xlabel('频率(GHz)');ylabel('功率(dB)');title('原信号周期图');subplot(312);plot(Kh);axis tight;axis([1 N/2 -100 50]);set(gca,'XTick',1:204:N/2+1);set(gca,'XTickLabel',{'0','0.1','0.2','0.3','0.4','0.5','0.6','0.7','0.8','0.9','1'});xlabel('频率(GHz)');ylabel('功率(dB)');title('滤波器频率响应');subplot(313);plot(Kre);axis tight;axis([1 N/2 -100 50]);set(gca,'XTick',1:204:N/2+1);set(gca,'XTickLabel',{'0','0.1','0.2','0.3','0.4','0.5','0.6','0.7','0.8','0.9','1'});xlabel('频率(GHz)');ylabel('功率(dB)');title('输出信号周期图');figure;spectrogram(x,512,500,4096,2e9);figure;spectrogram(re,512,500,4096,2e9);⛄ 运行结果⛄ 参考文献[1]孟藏珍, 徐向东, 袁俊泉,等. 一种基于最小二乘的DS—SS系统抗窄带干扰方法[J]. 空军预警学院学报, 2006, 20(3):174-176.❤️部分理论引用网络文献,若有侵权联系博主删除❤️ 关注我领取海量matlab电子书和数学建模资料
【Linux】文件操作、文件描述符和重定向(上)
👉重新谈论文件👈空文件也要占据磁盘空间。文件等于文件内容加文件属性。文件操作等于对文件内容的操作、对文件属性的操作或对文件内容和属性的操作。标识一个文件,必须使用文件路径加文件名(具有唯一性)。如果没有指明对应的文件路径,默认是在当前路径(进程的工作路径)进行文件访问。当我们把 fopen、fclose、fread 和 fwrite 等接口写完之后,代码编译链接形成可执行程序之后,没运行可执行程序,文件对应的操作也没有被执行。故对文件的操作,本质是进程对文件的操作。一个文件如果没有被打开,不可以进行文件访问。一个文件要被访问,就必须先被用户进程和操作系统打开。并不是所有的磁盘文件都被打开了,磁盘文件可分为两种:被打开的文件和没有被打开的文件。所以,文件操作的本质是研究进程和被打开文件的关系。(注:文件系统里研究没有被打开的文件)👉回顾 C 语言的文件操作👈C 语言有文件操作,C++ 也有文件操作,任何一门语言都会有文件操作,而这些语言的操作接口都不一样!因为这些语言的文件操作接口都不一样,学习的成本是挺高的。那如何降低学习成本呢?我们知道:文件是在磁盘里的,磁盘是硬件。所有人想访问磁盘就不能绕过操作系统,那么开发者就必须使用操作系统提供的文件级别的系统调用接口。所以无论上层语言如何变化,库函数底层实现都必须调用系统调用接口。那么库函数可以千变万化,但是底层是不变的。那我们学习不变的东西,就可以降低学习成本了。现在我们来回顾一下 C 语言的文件操作接口,再来学习文件操作的系统调用接口。以 w 的方式打开文件注:fprintf 函数可以将格式化的数据写到指定的流中。以 w 的方式打开文件,文件不存在会自动创建;如果文件存在,先清空文件的内容再进行写入。#include <stdio.h>
#define FILE_NAME "log.txt"
int main()
{
// r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建)
// a(append,追加), a+(追加式写入)
FILE* fp = fopen(FILE_NAME, "w"); // 没有指明路径,默认在当前路径进行文件操作
if(fp == NULL)
{
perror("fopen");
return 1;
}
int cnt = 5;
while(cnt)
{
fprintf(fp,"%s:%d\n", "hello world", cnt--);
}
return 0;
}
以 r 的方式打开文件#include <stdio.h>
#include <string.h>
#define FILE_NAME "log.txt"
int main()
{
// r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建)
// a(append,追加), a+(追加式写入)
FILE* fp = fopen(FILE_NAME, "r"); // 没有指明路径,默认在当前路径进行文件操作
if(fp == NULL)
{
perror("fopen");
return 1;
}
char buffer[64];
// 读取sizeof(buffer)-1个字符,为\0留一个位置
while(fgets(buffer, sizeof(buffer) - 1, fp) != NULL)
{
buffer[strlen(buffer) - 1] = '\0'; // 清除\n
puts(buffer);
}
return 0;
}
以 a 的方式打开文件#include <stdio.h>
#define FILE_NAME "log.txt"
int main()
{
// r(读,不存在则出错), w(写,不存在则创建), r+(读写,不存在则出错), w+(读写,不存在则创建)
// a(append,追加), a+(追加式写入)
FILE* fp = fopen(FILE_NAME, "a"); // 没有指明路径,默认在当前路径进行文件操作
if(fp == NULL)
{
perror("fopen");
return 1;
}
int cnt = 5;
while(cnt)
{
fprintf(fp,"%s:%d\n", "hello world", cnt--);
}
return 0;
}
打开文件的方式如上就是我们之前学的文件相关操作。还有 fseek、ftell 和 rewind 等函数,在 C 语言部分已经学习过,大家可以自行复习一下。👉文件操作的系统调用👈open上面所使用的的 C 语言文件操作函数都是通过调用相应的系统调用接口的,fopen 函数对应的是系统调用 open,fwrite 等函数对应的是系统调用 write,fclose 函数对应的是系统调用 close。系统调用 open 的参数和返回值第一个参数:文件路径+文件名。只提供文件名,默认在当前路径进行文件操作。第二个参数:打开文件的方式。该参数是通过宏来表示不同的打开方式,如:O_RDONLY 只读方式打开文件,O_WRONLY 只写方式打开文件等。通过按位或可以实现不同的文件打开方式,原因是这些宏都是通过比特位的不同来标记不同的选项,也就是说一个比特位就是一个选项。需要注意的是,比特位的位置不能重复。第三个参数:创建文件的起始权限权限。为打开的文件设置不同的权限。使用 C 语言文件操作函数创建出来的文件默认权限是 664,文件的权限等于起始权限 & (~umask),普通文件的起始权限是 666,目录文件的起始权限是 777。成功打开文件时返回一个大于 0 的文件描述符,打开失败则返回 -1 并且设置错误码 errno。O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
通过比特位来传递信息示例:#include <stdio.h>
// 不同的标记位表示不同的选项
// 下面的每个宏对应的数值,只有一个比特位是1,彼此的位置不重叠
#define ONE (1<<0)
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
void show(int flags)
{
if(flags & ONE) printf("ONE\n");
if(flags & TWO) printf("TWO\n");
if(flags & THREE) printf("THREE\n");
if(flags & FOUR) printf("FOUR\n");
}
int main()
{
show(ONE);
printf("--------------------\n");
show(TWO);
printf("--------------------\n");
show(ONE | TWO);
printf("--------------------\n");
show(ONE | TWO | THREE);
printf("--------------------\n");
show(ONE | TWO | THREE | FOUR);
return 0;
}
那么,系统调用 open 中的各种宏也是通过这种方式实现的。#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main()
{
int fd = open(FILE_NAME, O_WRONLY);
// assert(fd != -1);
if(fd < 0)
{
perror("open");
return 1;
}
close(fd); // close关闭文件,参数是对应的文件描述符
return 0;
}
宏 O_WRONLY 只是写,没有对应的文件就打开失败,并不是没有对应的文件就自动创建。如果想要没有对应文件就自动创建,想要按位或上 O_CREAT。现在虽然没有出错,但是创建出来的文件的权限却是全乱的。如果想要创建出来的文件的权限是不乱的,就需要传入 open 的第三个参数。如果我们不想要系统默认的权限掩码,可以通过 umask 函数来设置。write打印被打开文件的文件描述符关于为什么被打开文件的文件描述符是 3,会在后面的内容里讲解,这也是埋下的一个小小的伏笔。提示内容:在语言层面上,文件是分文本类文件和二进制类文件的;但再操作系统层面上,文件都是二进制的。#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <unistd.h>
#define FILE_NAME "log.txt"
int main()
{
umask(0); // 将权限掩码设置为0,文件的最终权限等于起始权限&(~umask)
int fd = open(FILE_NAME, O_WRONLY | O_CREAT, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
int cnt = 5;
char outBuffer[64];
while(cnt)
{
sprintf(outBuffer, "%s:%d\n", "hello world", cnt--);
// C语言规定以\0作为字符串的结尾, 但与文件没有任何的关系, 所以下面的strlen不需要+1
write(fd, outBuffer, strlen(outBuffer));
}
//printf("fd:%d\n", fd);
close(fd); // close关闭文件,参数是对应的文件描述符
return 0;
}
如果我们上面的代码改成下面的样子,再运行起来并查看文件会出现上面情况呢?可以看到:write 是覆盖式写入的,并不是先清空文件里的内容再进行写入。但是 C 语言的写入不是这样子的呀,C 语言的文件操作函数是对系统调用的封装。如果我们也想要实现文件存在时,先清空文件的内容再进行写入的话,还需要给 open 多传入一个宏 O_TRUNC。所以,C语言的fopen(FILE_NAME, "w")对应的系统调用就是open(FILE_NAME, O_WRONLY | O_CREAT | O_TRUNC, 0666)。如果想以追加的方式向文件写入的话,只需要再给 open 再传入一个宏 O_APPEND。注:O_APPEND 不要和 O_TRUNC 一起使用。
【目标检测】基于YOLO神经网络实现人体举重时杠铃的路径和速度跟踪附matlab代码
作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。 🍎个人主页:Matlab科研工作室🍊个人信条:格物致知。更多Matlab仿真内容点击👇智能优化算法 神经网络预测 雷达通信 无线传感器 电力系统信号处理 图像处理 路径规划 元胞自动机 无人机 ⛄ 内容介绍基于计算机视觉的运动跟踪和检测是计算机视觉领域的重要课题。对视频序列中的运动目标进行检测、跟踪,获得目标的位置、速度等运动参数,是进行更高级的运动目标行为分析的基础。快速实时地检测出运动目标,对运动目标进行跟踪,并实现目标的运动数据分析,在体育领域可以得到广泛的应用。例如,对一个田径运动员的训练视频进行处理,提取出其瞬时运动速度、手臂摆动幅度、频率等信息,由于视频序列每秒钟有多幅图像,含有大量的人眼捕捉不到的有价值的信息,可用于分析运动员技术上的不足,提高运动技能。一种基于YOLO和改进模板匹配的杠铃识别与跟踪控制方法,具体包括以下步骤:S1,采集多种杠铃的侧视图作为训练数据,对训练数据制定标签,划分为训练集和测试集;S2,对训练集中的图片进行标准化处理,并使用YOLO算法通过调整超参数的方式训练杠铃识别模型;S3,获取原始杠铃视频,通过杠铃识别模型对杠铃视频的第一帧图像进行检测并定位杠铃的位置;S4,将从第一帧图像中得到的杠铃位置和原始杠铃视频输入到改进的模板匹配算法中,计算得到绘制有杠铃轨迹的杠铃追踪视频.与现有技术相比,本发明具有提升杠铃识别的准确率并能够识别出不同样式的杠铃,提高帧率和准确率等优点.⛄ 部分代码classdef liftVid < handle %liftVid Read and analyze a weightlifting video frame by frame. % vid = liftVid("file.mp4"); % % This is the main class definition for running BarTrace. properties vObj = 0; % video object frameSize = [0 0] % frame size numFrames = 0; % number of frames radius = 0; % avg radius of the plate in pixels pos = zeros([],3); % center of plate and radius in each frame model = load("TrainedNNB.mat"); % trained neural net end methods function obj = liftVid(fileName) %liftVid Construct an instance and open the video file. % obj = liftVid(fileName) if nargin == 0 [fileName, path] = uigetfile("*.mp4","Select video file"); fileName = string(path) + fileName; elseif nargin > 1 error('Expected 0 or 1 inputs'); end obj.vObj = VideoReader(fileName); obj.frameSize = [obj.vObj.Height obj.vObj.Width]; end function frame = processNextFrame(obj) %processNextFrame if isa(obj.vObj,"VideoReader") && hasFrame(obj.vObj) % read frame obj.numFrames = obj.numFrames + 1; cdata = readFrame(obj.vObj); % find plate trainedSize = obj.model.detector.TrainingImageSize; imSmall = imresize(cdata,trainedSize); [bboxes,scores] = detect(obj.model.detector,imSmall); if ~isempty(bboxes) [~,idx] = max(scores); box = bboxes(idx,1:4); x = box(1) * obj.frameSize(2)/trainedSize(2); y = box(2) * obj.frameSize(1)/trainedSize(1); w = box(3) * obj.frameSize(2)/trainedSize(2); h = box(4) * obj.frameSize(1)/trainedSize(1); position = [(x+w/2) (y+h/2) ((w+h)/4)]; %[x y r] obj.pos(obj.numFrames,1:3) = position; % store position obj.radius = median(obj.pos(1:obj.numFrames,3)); % median of all past radii else % plate wasn't found position = [1 1 10]; obj.pos(obj.numFrames,1:3) = position; % store position end % annotate frame = insertShape(cdata,'circle',position,'LineWidth',5,'Color',"red"); trace = zeros(obj.numFrames,4); %add lines and dots to bar trace for k = 2:obj.numFrames % concatonate a list of all past centers trace(k-1,1:4) = [obj.pos(k-1,1) obj.pos(k-1,2) obj.pos(k,1) obj.pos(k,2)]; end if ~isempty(trace) frame = insertShape(frame,'Line',trace,'LineWidth',2,'Color',"blue"); frame = insertShape(frame,"Circle",[trace(1:size(trace,1),[3 4]) 2*ones(size(trace,1),1)],"LineWidth",1,"Color",{"Yellow"}); end else disp("no more frames") end end function tracePlate(obj,playit,saveit,saveName) % tracePlate Finds the plate in each frame % tracePlate(obj,playit,saveit) % playit = true will play the video % saveit = true will save the video if isa(obj.vObj,"VideoReader") && hasFrame(obj.vObj) if playit figure; f = gca; end if saveit v = VideoWriter(saveName); open(v); end else disp("no more frames to process - try reloading") playit = false; saveit = false; end while hasFrame(obj.vObj) frame = obj.processNextFrame; if playit imshow(frame, 'Parent', f); drawnow; end if saveit writeVideo(v,frame); end end if saveit close(v) end end end % end of methodsend % end of classdef⛄ 运行结果⛄ 参考文献[1]李文举, 王子杰. 基于YOLO和改进模板匹配的杠铃识别与跟踪控制方法:, CN114743125A[P]. 2022.[2]王群. 基于计算机视觉的人体跟踪与运动参数测量[D]. 大连海事大学.⛄ 完整代码❤️部分理论引用网络文献,若有侵权联系博主删除❤️ 关注我领取海量matlab电子书和数学建模资料
【Linux】进程程序替换及shell的模拟实现(下)
用 C语言程序调用 C++ 的程序用 C语言程序调用 python 的程序可以使用程序调换,调用任何后端语言对应的可执行程序!5. execleint execle(const char *path, const char *arg, ...,char *const envp[]);传入自定义环境变量传入系统的环境变量execle 函数能够传入环境变量,但是我们发现传入系统环境变量,就不能传入自定义环境变量了。如果我们先要两个都有的话,就可以借助 putenv 函数了。当使用 exec 函数将程序加载到内存的时候,其在调用 main 函数之前首先调用一个特殊的例程,并且将此启动例程指定为程序的起始位置。这个启动例程将从内核取得该可执行程序的命令行参数和环境变量,然后传递给 main 函数。尽管前 4 个 exec 函数没有传环境变量,但是子进程照样能够通过 environ 拿到默认的环境变量,其是通过进程地址空间的方式让子进程拿到的!6. execveint execve(const char *path, char *const argv[], char *const envp[]);在程序替换中,execve 是系统调用。其余 6 个函数都是对 execve 系统调用做的封装,以满足开发者的需求。和 mian 函数的命令行参数结合以上就实现了用我们的程序去执行系统的程序。如果再把前面的./myexec去掉,就相当于我们自己写了个shell。那么接下来,我们就模拟实现一个简易的shell。👉shell 的模拟实现👈现在我们已经学习到了进程创建、进程退出、进程登台、进程程序替换等知识,那么我们理解这些知识模拟实现简易版的命令行解释器 shell。注:本次模拟实现的 shell 并不是十全十美的,多少会有一些 BUG。对于一些常用的命令,还是能实现的。实现思路:通常来说,shell 读取一行新的输入,对输入进行命令解析。然后创建子进程并进行程序替换执行输入的命令。但是对于一些内建(内置)命令,shell 会自己执行,而不是通过创建子进程再进行程序替换的方式。什么是内建(内置命令)?不需要创建子进程来执行,而是让 shell 自己执行的命令称为内建命令或者内置命令,其本质是调用相应的系统接口。echo 和 cd 就是常见的内建命令。因为 echo 是个内建命令,命令行解释器 bash 不会创建子进程来执行 echo 命令而是自己去执行该目录,所以 echo 能够输出不具有全局属性的本地变量。shell 的循环过程获取命令行解析命令行建立一个子进程(fork)替换子进程(execvp)父进程等待子进程退出(wait)关于 cd 为什么是内建命令,我们需要了解什么是当前路径!当前路径就是当前进程的工作路径。默认情况下,当前路径就是可执行程序所处的路径。进程的工作路径可以通过系统调用chdir来修改。如果我们创建子进程来执行 cd 命令的话,子进程执行 cd 命令改变子进程的工作路径。而子进程执行 cd 命令后就退出了,并不会改变父进程 bash 的工作路径。所以说,要想改变父进程 bash 的工作路径,就只能父进程 bash 来执行 cd 命令了。所以,cd 也是内建命令。让子进程执行 cd 命令的情况使用 chdir 修改进程的工作路径myshell 源码#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <string.h>
#define NUM 1024
#define OPT_NUM 64 // 命令行参数的最多个数
char lineCommand[NUM];
char* myargv[OPT_NUM];
// 上一个进程的退出信息
int lastCode = 0;
int lastSignal = 0;
int main()
{
while(1)
{
char* user = getenv("USER"); // 当前登录用户
// 根据用户输出对应的提示信息, get_current_dir_name函数可以获得当前的工作路径
if(strcmp(user, "root") == 0)
{
printf("[%s@%s %s]# ", user, getenv("HOSTNAME"), get_current_dir_name());
}
else
{
printf("[%s@%s %s]$ ", user, getenv("HOSTNAME"), get_current_dir_name());
}
fflush(stdout); // 刷新缓冲区
// 获取用户输入
char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
assert(s != NULL);
// 清除最后一个\n, abcd\n
lineCommand[strlen(lineCommand) - 1] = 0;
// 字符串切割:"ls -a -l" -> "ls" "-a" "-l"
myargv[0] = strtok(lineCommand, " ");
int i = 1;
// 因为无法执行"ll"指令, 所以这里做一下处理
if(myargv[0] != NULL && strcmp(myargv[0], "ll") == 0)
{
myargv[0] = "ls";
myargv[i++] = "-l";
}
if(myargv[0] != NULL && strcmp(myargv[0], "ls") == 0)
{
myargv[i++] = "--color=auto";
}
// 如果切割完毕, strtok返回NULL, myargv[end] = NULL
while(myargv[i++] = strtok(NULL, " "));
// 如果是cd命令, 不需要创建子进程来执行, 让当前进程的父进程shell执行对应的命令, 本质就是调用系统接口
// 像这种不需要创建子进程来执行, 而是让shell自己执行的命令, 称为内建命令或者内置命令
// echo和cd就是一个内建命令
if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0)
{
// 如果cd命令没有第二个参数, 则切换到家目录
if(myargv[1] == NULL)
{
chdir(getenv("HOME")); // 更改到家目录
}
else
{
if(strcmp(myargv[1], "-") == 0) // 该功能还有BUG, 因为环境变量的问题
{
chdir(getenv("OLDPWD")); // 回到上一次所处的路径
}
else if(strcmp(myargv[1], "~") == 0)
{
chdir(getenv("HOME")); // 去到家目录
}
else
{
chdir(myargv[1]); // 更改到指定目录
}
}
continue; // 不创建子进程, continue回到while循环处
}
// 实现echo命令, 当前的echo命令功能也不是很全
if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0)
{
if(strcmp(myargv[1], "$?") == 0)
{
printf("%d, %d\n", lastSignal, lastCode);
}
else
{
printf("%s\n", myargv[1]);
}
continue;
}
// 创建子进程来执行命令
pid_t id = fork();
assert(id != -1);
// child process
if(id == 0)
{
execvp(myargv[0], myargv);
exit(1); // 进程替换失败
}
int status = 0;
pid_t ret = waitpid(id, &status, 0); // 阻塞等待
assert(ret > 0);
lastCode = ((status >> 8) & 0xFF);
lastSignal = (status & 0x7F);
}
return 0;
}
myshell 使用演示myshell 的源码里已经有了相应的注释,所以就不详细讲解了。我们无法做到使用 cd 命令时,使得 bash 和 myshell 的工作路径一起跟着改变。因为当你登录上 Xshell 时,操作系统已经将 bash 进程给创建好了,myshell 是 bash 的一个子进程,所以 myshell 执行 cd 命令并不会修改 bash 的工作路径。👉补充知识👈exec 和 exit 就像 call 和 return 一样。一个 C 语言程序有很多函数组成,一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过 call 和 return 进行通信。这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux 鼓励将这种应用于程序之内的模式扩展到程序之间。如下图:一个 C 语言程序可以 fork 和 exec 另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过 exit 来返回值。调用它的进程可以通过 wait 来获取 exit 的返回值。👉总结👈本篇博客主要讲解了进程的程序替换并且综合前面学到的进程创建、进程退出和进程等待的知识模拟实现了一个简易版的命令行解释器 myshell。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️
【智能优化算法-灰狼算法】基于内存、进化算子和局部搜索的改进灰狼优化器附matlab代码
作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。 🍎个人主页:Matlab科研工作室🍊个人信条:格物致知。更多Matlab仿真内容点击👇智能优化算法 神经网络预测 雷达通信 无线传感器 电力系统信号处理 图像处理 路径规划 元胞自动机 无人机 ⛄ 内容介绍即使对于进化算法和基于群体的算法,多模态函数的优化也具有挑战性,因为它需要有效探索以找到搜索空间的有希望区域,并有效利用以精确找到全局最优值。Gray Wolf Optimizer (GWO) 是最近开发的一种元启发式算法,其灵感来自于自然界,具有相对较少的参数进行调优。然而,GWO 及其大多数变体可能会遭受缺乏种群多样性、过早收敛以及无法在探索和剥削行为之间保持良好平衡的困扰。为了解决这些限制,这项工作提出了一种新的 GWO 变体,结合了记忆、进化运算符和随机局部搜索技术。它进一步集成了线性种群规模缩减(LPSR)技术。所提出的算法在 23 个数值基准函数、高维基准函数、13 个工程案例研究、4 个数据分类和 3 个函数逼近问题上进行了综合测试。基准函数主要取自CEC 2005和CEC 2010专场,包括轮换、移位函数。工程案例研究来自 CEC 2020 真实世界的非凸约束优化问题。所提出的 GWO 的性能与流行的元启发式算法进行了比较,即粒子群优化(PSO),引力搜索算法(GSA),拍击群算法(SSA),差分进化(DE),自适应差分进化(SADE),基本GWO及其三个最近改进的变体。已经进行了统计分析和弗里德曼测试获得的结果表明,所提出的 GWO 优于所测试的基准函数和工程案例研究的算法。⛄ 部分代码close allclear all clcSearchAgents=30; Fun_name='F2'; Max_iterations=1000; [lowerbound,upperbound,dimension,fitness]=fun_info(Fun_name);[Best_score,Best_pos,TSA_curve]=MELGWO(SearchAgents,Max_iterations,lowerbound,upperbound,dimension,fitness);figure('Position',[500 500 660 290])subplot(1,2,1);func_plot(Fun_name);title('Objective space')xlabel('x_1');ylabel('x_2');zlabel([Fun_name,'( x_1 , x_2 )'])subplot(1,2,2);plots=semilogy(TSA_curve,'Color','g');set(plots,'linewidth',2)hold ontitle('Objective space')xlabel('Iterations');ylabel('Best score');axis tightgrid onbox onlegend('MELGWO') ⛄ 运行结果⛄ 参考文献⛄ 完整代码❤️部分理论引用网络文献,若有侵权联系博主删除❤️ 关注我领取海量matlab电子书和数学建模资料