【RPA入门教程】UIBot命令的基本操作

这篇教程给大家介绍下UIBot的命令的相关操作,插入命令、删除命令、移动命令等等下图就是UIBot的主要编辑界面看到这样的界面,估计不少人都是一脸懵逼来来来~这里就教大家一些基础的姿势1、命令的插入在界面左侧就是命令的列表,分为了不同的类别,点开不同的分类就会展开具体的命令插入命令的方式有两种,第一种就是直接双击你想要插入的命令,如果编辑视图中有已经选中的代码,那会自动插入到选中的代码下一行,否则会插入到代码最后一行第二种是鼠标拖动,可以把命令直接拖动到你想要插入的位置2、命令移动编辑过程中难免需要调整代码位置。只需要直接用鼠标选中命令后拖动到想要的位置就能完成命令的移动了3、命令删除在可视化视图中选中命令,按键盘上的DEL就能删除这条命令按住ctrl+鼠标左键能间隔选择多条命令,按住Shift能选择连续多条命令,再按DEL就能批量删除4、复制、剪切命令选中命令,按ctrl+c复制命令,按ctrl+x剪切命令,选中要插入的位置按ctrl+v粘贴命令这些就是命令的基础操作拉,大家学会了吗?如果还没学会,那就……

January 21, 2019 · 1 min · jiezi

客户端单周发版下的多分支自动化管理与实践

背景目前,互联网产品呈现出高频优化迭代的趋势,需求方希望尽早地看到结果,并给予及时反馈,所以技术团队需要用“小步快跑”的姿势来做产品,尽早地交付新版本。基于以上背景,美团客户端研发平台适时地推行了单周发版的迭代策略。单周版本迭代的优点可以概括为三个方面:更快地验证产品创意是否符合预期,更灵活地上线节奏,更早地修复线上Bug。首先说一下美团平台的发版策略,主要变更点是由之前的每四周发一版改为每周都有发版。具体对比如下:(旧)三周迭代指的是2周开发+1周半测试,依赖固定的排期和测试时间,如果错过排期,则需要等待至少20天方可跟着下个版本迭代发布,线上验证产品效果的时间偏长。具体示例描述如下:(新)单周版本迭代指一周一发版,单周迭代版本排期、测试不再依赖固定时间节点,需求开发并测试完成,就可以搭乘最近一周的发版“小火车”,跟版发布直接上线。对于一般需求而言,这将会大大缩短迭代时间。业务方研发人员的痛点在之前按月发版的迭代节奏下,基本上所有的需求都属于串行开发,每个版本的开发流程比较固定。从“评审-开发-提测-灰度-上线”各个环节都处于一个固定的时间点来顺序执行,开发人力资源的协调方式也相对简单。全面推进单周发版之后,并不能把所有需求压缩到5天之内开发完成,而是会存在大量的并行开发的场景,之前的固定时间节点全部被打破,由固定周期变成了动态化调配,这给业务方的需求管理和研发人员人力管理都带来了指数式复杂度的提升。一旦进入并行开发,需求之间会产生冲突和依赖关系,版本代码也会随之产生冲突和依赖,这也大大提高了开发过程中的分支管理成本,如何规范化管理分支,降低分支冲突,把控代码质量,是本文接下来要讨论的重点。下面描述了几种典型的单周发版带来的问题:业务需求开发周期不固定,会存在大量的多版本、多需求并行开发。平台只提供了单周发版的基础策略,每5天发一版,业务方完成需求即可搭车发版。对于各业务方来说,需求开发往往并不是都能在5天内完成,一般需求在5~10天左右,在之前串行发版模式下这个问题其实也存在,但并不突出,在单周发版的前提下,都要面临跨版本开发,意味着多个版本多个需求会同步并行开发,这给业务方的分支管理带来了极大的挑战。业务方架构复杂,仓库依赖多,单周发版分支创建合并维护成本大。交通业务线涉及火车票、国内机票、国际机票多条业务线,代码仓库除了业务线的独立仓库,还有交通首页,交通公共仓库,RN仓库等多个仓库,Android端6个Git仓库,iOS端5个仓库,RN5个仓库,共计16个Git仓库。多仓库频繁发版分支代码存在安全风险,容易漏合代码,冲掉线上代码。业务线自身的公共基础库需求变动频繁。也需要具备单周发版的能力。例如交通公共基础仓库,承载了很多交通业务线的UI功能组件,这些公共组件的业务变化频繁,公共基础仓库变化的同时,可能会对使用组件的业务产生影响,需要同步的升级发版。美团平台的策略是公共服务组件每四个小版本统一升级一次,但对业务方自身组件这种策略限制较大,还是需要公共组件也要具备随时发版的能力。单周发版分支管理解决方案针对上面提出的问题,交通客户端团队通过技术培训、流程优化、关键点检测、自动化处理等方式保证分支代码的安全。技术培训主要是加强技术人员分支管理的基本知识,Git的正确使用方法,这里不做过多描述。本文主要讨论关键点检测,以及如何进行自动化的分支管理。在实施单周发版之前,业务方代码仓库只有两个分支,Develop分支,即开发分支;Stage分支,即发版分支;开发流程基本在串行开发模式,每个版本10天开发,8天测试,然后进入下一版本的开发。这种方式只能适用于节奏固定的长周期开发方式,对于多版本并行开发来说,有点力不从心,显然已经不能承载当前更灵活的发版节奏。针对这些问题,我们推出了如下分支管理结构。总的来说,就是废除之前作为开发分支的Develop分支,建立对应的Release发版分支,每个版本打包从Release分支直接打包;同时Stage分支不再承担打包职责,而是作为一个主干分支实时同步所有已发布上线的功能,Stage分支更像一个“母体”,孵化出Release分支和其它Feature分支;当Release分支测试通过、并且发版上线之后,再合入到Stage分支,此时所有正在开发中的其它分支都需要同步Stage分支的最新代码,保证下一个即将发布的版本的功能代码的完整性。上面的流程描述可能有些复杂,下面是简化的流程图,每个版本都有自己的生命周期:从Stage创建一个Release分支;进入开发阶段;如果Stage分支有变化,同步Stage分支;打包测试;测试通过,发布线上;发布线上之后,合回Stage分支。为了适应单周发版,新的开发流程也引入了很多新的挑战。例如下图所示的一个Branch分支中涉及的六个关键点:创建分支、合入主干、主干变化通知、Merge主干变化、检测主干同步、未同步拦截,除了这些还要考虑多仓库同步操作的问题,还有热修复版本的管理方式的问题。能否把这些关键节点合理的规范和把控起来,是我们当前应对多分支并行开发的主要难点:如何更高效的解决这些问题呢?结合我们当前使用的工具:Git + Atlassian Stash 代码仓库管理工具;Jenkins Build打包工具;大象(美团内部通讯工具)内网通信工具。目前这三个开发工具已经非常成熟、稳定,并且接口丰富易于扩展,我们需要配合当前单周发版的分支管理模式,利用这些工具来进行扩展开发,正所谓“要站在巨人的肩膀上”。创建分支Release分支如何创建,何时创建,分支命名规范定义如何约束?创建Release分支,本质上是从Stage新建一个分支,当前提前一个周期创建新的发版分支,例如在10.1.1版本Release后,创建10.1.3版本的分支,此时10.1.2版本处于开发测试阶段。业务方所有的分支命名和平台的分支命名保持一致,采用Release/x.x.x的格式,但同时需要升级成为即将发布的Release版本号,例如10.1.3。现在交通业务线多达十几个仓库,每个仓库每周都要操作一次需要耗费大量人力。之前每个分支的创建都是通过Stash或者手工创建,能不能自动化批处理的创建呢?答案是肯定的。对此,我们采用了Jenkins的方式,需要建立一个Jenkins Job, 基本原理就是通过命令行的方式进行Branch的创建,然后通过Job管理,批处理建立所有仓库的Release分支,这样就收敛了Branch的创建,即采用统一的命名规范,并且同时升级版本号。这就解决了创建分支的难点,实现了自动化创建分支,并且实现了规范化命名。如何知道Stage分支有变化,变化后需要做什么,不做会怎样?一个好的开发习惯,就是每天写码之前都同步主分支,但是还是需要一个机制来确保同步。这里做了三个措施来确保各个分支和Stage是保持同步的:一个通知,一个警告,一次拦截。这三个步骤解决主干变化通知、检测主干同步、未同步拦截的问题。一个通知:具体路径如下,建立了一个内部推送公众账号和一个Jenkins监听Job,当所有交通业务仓库Stage分支有代码改动,通知所有对应的开发人员,该仓库有代码变化,请及时合入。 一次警告:本地开发过程中,每次提交代码到远端仓库时,会触发一个Stage分支代码同步检测的脚本,如果发现未同步,会通过内部通讯系统通知提交者存在未同步主分支问题。但这里目前并不做强制拦截,保证分支代码开发的整体流畅性。最终拦截:在开发分支打包的过程中强制拦截,最终功能代码上线还是需要打包操作。在打包操作时统一收口,由于之前打包也是在Jenkins上来完成的,这里我们也是通过在打包Jenkins上接入了分支合并检测的插件,这样每次打包时会再次检测和主分支的同步情况。如果发现未同步则打包失败,确保每次发版都包含当前线上已有代码的功能,防止新版本丢失功能。如何合并分支,如何保证漏合?和上面提到的第一个如何创建分支的问题类似,通过Jenkins Job来进行批量操作,可以一键创建所有分支的Pull Request;在每个版本发版之前,统一进行一次打包,合入美团的主分支,防止多个仓库有漏合的情况。公共基础库版本策略?公共基础和业务分支保持同样的策略,通过批处理脚本同时建立分支,合并分支,监听分支变化,需要注意的是,每次版本升级,公共基础库也需要同步的打包,并且强制业务库升级。不然,如果基础仓库存在接口变动,有的业务升级了,有的业务没升级,最终会导致无法合入主分支,进而无法打出App包。热修复的版本管理策略?热修复确实是一种非常规的处理方式。从原则上来讲,热修复需要在对应的Release分支上进行修改,然后把修改合入Stage分支,同时需要同步到其它正在开发的分支。实际的处理需要根据具体情况来分析,是否需要对线上多个版本热修复。如果多版本都要修复就不能再合入Stage分支,否则会导致Stage分支冲突,如果把Stage分支合入需要热修复的其它分支,会把线上当前最新代码带入历史旧版本,会导致版本兼容性问题。最终执行起来可能还是对热修复版本进行单独处理,不一定要进行Stage主分支的同步,热修复的版本管理成本会比较高,更多的需要人工介入。未来展望目前整体的分支发版流程已经基本完成,现在已经稳定运行了10个小版本,同时没有出现因为分支管理问题而引发的线上问题。不过,当前整体流程的自动化程度还有待提高,每周需要人工去触发,很多代码合并过程中的冲突问题还需要人工去解决。未来我们希望能够自动化地根据平台的版本号自动创建分支,并且对于一些简单的冲突问题拥有自动化的处理能力。随着单周发版的不断成熟,未来对于持续交付能力也将不断提升,发版节奏可以不限于单周,一周两版或是更快的发版节奏也成为一种新的可能。作者介绍王坤,美团客户端开发工程师,2016年加入美团,目前主要负责大交通业务的客户端架构、版本管理及相关工作。

January 11, 2019 · 1 min · jiezi

「移动开发」iuap mobile玩转前端自动化构建

提起iuapmobile studio,相信大家已经不再陌生了,是进行移动应用开发的强大集成工具与必备神器。借助强大的iuap mobile studio,可以实现对开发、测试、调试、打包、发布全程管理。同时iuap mobile studio又是一个极具开放性的平台工具,可以与绝大多数优秀的前端框架、技术很好地集成。今天我们就来看看在iuap mobile上如何玩转这些如火如荼的前端自动化构建技术。说起目前主流前端自动化构建技术,少不了npm、grunt、gulp、browserify、webpack的身影,它们不但可以省去你开发时代码合并、压缩等工作,还可以让你使用SASS、LESS、ES6、React、Vue等进行开发新体验。我们来简单认识一下这些技术吧,熟悉这些技术同学可直接略过下面一段。iuap mobile在进行移动开发时如何引入这些前端自动化构建技术呢?接下来我们通过两个例子来看一下在iuapmoile之上如何使用这些自动化构建工具。iuapmobile + gulp首先我们来看一下gulp如何在iuapmobile中使用。总的来说,分为如下几步:(1)环境搭建(2)创建gulp项目(3)本地项目依赖配置及开发(4)编译打包第一步、环境搭建这块网上介绍性的很多,总的来说就是使用npm进行全局安装和本地项目开发依赖安装使用如下步骤来进行安装运行,涉及的命令如下:全局安装 gulp:$ npm install -g gulp作为项目的开发依赖(devDependencies)安装:$ npm install –save-dev gulp在项目根目录下创建一个名为 gulpfile.js 的文件:var gulp = require(‘gulp’);gulp.task(‘default’, function(){// 将你的默认的任务代码放在这})运行 gulp:$ gulp第二步,创建gulp项目通过iuapmobile studio新建项目的向导页创建gulp项目,studio会自动为你生成一个典型的gulp目录结构。整个过程如下图所示:自动化构建开发我们通过在gulpfile.js中,通过编写各种task,使用gulp插件(例如gulp-concat、gulp-sass、gulp-less、gulp-uglify等)将我们项目中的js、css、sass、less等文件统一压缩合并并输出成新的目标文件,在HTML页面中引用压缩合并后的文件即可。例如下图就是一个基于iuapmobile studio开发的一个使用gulp的页面示例。其中左边图展示一个iuapmobile移动项目的目录结构,其中根目录下的js、css、img等文件夹都是源代码文件夹。dist文件夹是使用gulp进行自动化构建的目标文件夹。右边图展示了使用gulp来对项目中的JS、CSS、LESS、SASS进行合并压缩,主要是通过JavaScript借助gulp的require机制加载各种gulp插件,来编写gulp task来实现自动化构建。下图(左)html页面引用的dist/css/all.css、dist/js/all.min.js都是通过gulp合并压缩后的文件,加快了页面资源加载速度。例如all.min.css实际上是sass和less编译后的合并压缩文件,如下图(中)展示的代码是SASS语法编写的样式,下图(右)展示的是用LESS语法编写的样式。gulp不仅提供了针对SASS、LESS的插件,还提供了针对JSX、ES6、TypeScript的插件,可以这些插件可以使用新的语法形式来编写JavaScript代码,这些还靠大家自己去亲身实践,这里就不过多赘述了。第四步、编译打包所有开发打包完毕后,可以使用iuapmobile的一键打包功能进行本地编译打包功能生成安卓应用了。通过云打包进行IOS编译打包可以生成IOS应用。生成后的效果图如下:这一步,没有什么额外的工作,只需要像往常使用iuapmobile进行编译打包一样进行操作即可,运行后在模拟器或真机上查看后的结果如下图。通过以上可以看出在iuapmobile上,是可以使用gulp来完成移动开发中的自动化构建工作的,避免了容易出错的手工操作环节,在编写代码方式上也有了更多的选择,将开发环境和生产环境进行分离,最终页面加载的也都是合并压缩后的,,加载效率自然会提升不少。iuapmobile + webpack我们再来看一下webpack如何在iuapmobile中使用。首先来看一下iuapmobilei前端js框架如何支持模块化。iuapmobile中前端js框架是支持模块化编写的代码,因此在webpack中可以通过require机制来加载,例如var summer = require(‘summer.js’);var um = require(‘iuapmobile.framework.ui.js’);其中,iuapmobile.framework.ui.js是iuapmobile提供的前端UI插件JS文件合并而成的插件集合,其中每一个插件来自一个独立模块化的js文件,因此也支持直接独单加载某个模块插件,例如var modal = require(‘um.modal.js’);modal.toast(‘这是一个toast’);adfadf总的来说分为如下几步:(1)环境搭建(2)创建webpack项目(3)本地项目依赖配置(4)开发测试打包运行第一步、环境搭建全局安装webpack:$ npm install -gwebpack作为项目的开发依赖(devDependencies)安装:$ npm install –save-devwebpack配置webpack.config.js的文件通过该配置文件指定入口文件路径和存放打包后文件的地方的路径等4。通过安装各种使用到的加载器、插件、开发服务器等。$ npm install –save-devstyle-loader css-loader$ npm install –save-dev babel-core babel-loader babel-runtime$ npm install –save-devbabel-preset-es2016 babel-preset-react$ npm install –save-devwebpack-dev-serve运行webapck:$ webpack第二步、创建wenpack项目通过iuapmobile studio新建项目的向导页创建webpack项目,studio会自动为你生成一个典型的webpack目录结构。整个过程如下图所示:我们可以看到在项目根目录下有package.json、webpack.config.js等文件其中,package.json是npm管理配置的,webpack.config.js来webpack进行项目管理配置的。node_modules是你的引入的插件模块所在的目录,熟悉前端自动化构建的同学对此一定很熟悉吧。public文件夹是该配置文件中定义的输出文件夹路径以上时iuapmobile默认为你生成的目录结构,当然你也可以在此基础上改成你自己习惯的目录结构。第三步、使用webpack进行自动化构建开发我们webpack提供了针对JS、CSS、JSON进行模块化加载的各种loader,通过安装这些loader后,我们便可以对项目中的各种资源进行打包。如下图所示,左边图为webpack.config.js中定义的各种loader,右边图中dist文件夹为打包后目标文件夹,其中indexBundle.js等为各对应HTML页面打包后的js文件。除了打包之外,使用webpack的另一个有意义的事情便是可以使用ES6、React、Vue等来编写自己的代码了。如下所示左边图展示了该页面仅仅需要引用一个打包后的indexBundle.js即可。右边图展示了indexBunlde.js中对各种资源文件的统一模块化加载,其中包含了ES6语法写的js,还包括css、scss等样式文件以及引用的第三方插件js文件。下面左图则是使用ES6语法来编写的JS代码,通过import被模块化加载到indexBundle.js中。右图则是使用SASS语法编写的样式,通过require被打包到indexBundle.js中,页面无需单独再引用转化为css后的文件。第四步、编译打包所有开发打包完毕后,可以使用iuapmobile的一键打包功能进行本地编译打包功能生成安卓应用了。通过云打包进行IOS编译打包可以生成IOS应用。生成后的效果图如下:看了这两个案例有没有心动?原来你的iuapmobile项目也可以使用自动化构建自己的项目了,有没有眼前一亮的感觉?实际开发中,也可以根据需要将gulp和webpack组合使用,通过gulp插件gulp-webpack来完成资源打包和模块化加载工作,在gulpfile中定义task来完成整个项目自动化构建,例如iuapmobile的开发者使用者们要不要赶紧去实践一下呢?关于iuapmobileiuapmobile开发平台是一个开放的平台,好的前端开源框架、技术工具都可以集成使用。开源、开放模式是我们一贯的理念。至于在实际应用开发过程中是否引入前端自动化构建完全取决于聪明的开发者自身,平台、框架说到底都是工具、方法,软件开发中的人才是最核心因素。什么是好的技术、什么时机引用什么技术,都取决于人的因素,在某种意义上,软件、硬件,归根结蒂都是人件。iuap mobile普及:iuap mobile为企业提供了完整的从跨平台开发到管理运维到安全整合的一体化解决方案。帮助企业迅速达到较高级别的移动信息化成熟度水平,是企业移动化建设的基础设施。iuap mobile成功服务于政府机关以及不同行业的大型企业,比如:中国海关总署、中国核电工程有限公司、国家开发银行、长城汽车、港华燃气、贵州茅台、旭阳集团、广泽乳业、等。iuap mobile包含如下产品:1、 移动开发平台:跨平台开发2、 移动运行支撑平台:对接第三方业务系统、各种移动服务、移动接入安全、数据缓存、应用多版本容器管理等3、 移动安全管理系统:移动安全、设备管理、应用管理、设备应用统计分析等等4、 移动应用商店:移动应用分发与管理 ...

December 26, 2018 · 1 min · jiezi

crontab 在mac上不执行问题研究

crontab是个管理定时任务的工具,作用是在特定时间(通过crontab的语法配置),自动执行特定任务(想让它执行什么,就写个脚本或bash命令)。当你每天都需要执行脚本干一些重复工作的时候,这个东西就派上用场了。不了解这个东西怎么用的朋友,可以通过点击这里进行一个基本了解。这篇文章主要是为了记录自己在写crontab时踩得一些坑,当我按照顺序做完配置之后,却发现crontab中的task怎么也跑步起来,于是google了一下问题,找到了几个相关blog,结合在一起验证,终于解决了问题。crontab 为啥不执行呢?问了一下谷歌,OS X的定时任务统统由 launchctl 来管理的,看看 cron 任务有没有在里面 ➜ ~ sudo launchctl list | grep cron 208 0 com.vix.cron 有记录。查看一下启动项的配置。 ➜ ~ locate com.vix.cron WARNING: The locate database (/var/db/locate.database) does not exist. To create the database, run the following command: sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist Please be aware that the database can take some time to generate; once the database has been created, this message will no longer appear.database 不存在啊,那就创建一个吧。 sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist // 这个指令会花费一定时间一段时间后,创建成功,然后查看~ cat /System/Library/LaunchDaemons/com.vix.cron.plist<?xml version=“1.0” encoding=“UTF-8”?><!DOCTYPE plist PUBLIC “-//Apple Computer//DTD PLIST 1.0//EN” “http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version=“1.0”><dict> <key>Label</key> <string>com.vix.cron</string> <key>ProgramArguments</key> <array> <string>/usr/sbin/cron</string> </array> <key>KeepAlive</key> <dict> <key>PathState</key> <dict> <key>/etc/crontab</key> <true/> </dict> </dict> <key>QueueDirectories</key> <array> <string>/usr/lib/cron/tabs</string> </array> <key>EnableTransactions</key> <true/></dict></plist>注意里面有个keepAlive的条件是 /etc/crontab 是否存在: <key>KeepAlive</key> <dict> <key>PathState</key> <dict> <key>/etc/crontab</key> <true/> </dict> </dict>查看 /etc/crontab 是否存在➜ ~ ll /etc/crontabls: /etc/crontab: No such file or directory查看得知,该文件不存在,创建该文件。➜ ~ sudo touch /etc/crontab最终,就可以成功执行了。需要注意的是,sh脚本中的路径,最好使用绝对路径,否则脚本很可能将无法正确执行 ...

December 24, 2018 · 1 min · jiezi

ui2code中的深度学习+传统算法应用

背景在之前的文章中,我们已经提到过团队在UI自动化这方面的尝试,我们的目标是实现基于 单一图片到代码 的转换,在这个过程不可避免会遇到一个问题,就是为了从单一图片中提取出足够的有意义的结构信息,我们必须要拥有从图片中切割出想要区块(文字、按钮、商品图片等)的能力,而传统切割算法遇到复杂背景图片往往就捉襟见肘了(见下图),这个时候,我们就需要有能力把复杂前后景的图片划分为各个层级图层,再交给切割算法去处理,拿到我们期望的结构信息。经过传统切割算法处理,会无法获取图片结构信息,最终只会当成一张图片处理。在业界,图片前后景分离一直是个很麻烦的命题,业界目前比较普遍采用的解决方案是计算机视觉算法提取,或是引入人工智能来解决,但直到现在,都没有百分百完美的解决方案。那是否能引入AI来解决这个问题呢,我们来看一下,目前使用AI并拿到比较不错结果的解法是fcn+crf,基本上能够把目标物体的前景轮廓框出来,但缺点也很明显:准确率只有80%左右边缘切割无法达到像素级别打标成本非常大难以训练AI是个黑盒,结果不可控在考虑到使用AI伴随的问题之外,咱们也一起来思考下,难道AI真的是解决前后景分离的最佳解法吗?其实不是的,我们知道,一个页面,或者说设计稿,一个有意义的前景,是具有比较明显特征的,比如说:规则的形状:线段、矩形、圆形、圆角、是否对称等形状上是否有文字,或者说是类似于文字的信息是否闭合让我们一起来验证下这个思路的可行性。实践结果在尝试了非常的多计算机视觉算法之后,你会发现,没有一种算法是能够解决掉这个问题的,基本上是可能一种算法,在某种场景下是有效的,到了另外一个场景,就又失效了,而且就算是有效的场景,不同颜色复杂度下,所需要的最佳算法参数又是不相同的。如果case by case来解决的话,可以预期未来的工程会变得越来越冗杂且不好维护。那是不是可以这样呢,找到尽可能多的前景区域,加一层过滤器过滤掉前景可能性低的,再加一层层级分配器,对搜索到的全部前景进行前后层级划分,最后对图像进行修复,填补空白后景。咱们先来看看效果,以下查找前景的过程:为了避免有的前景被忽略(图片大部分是有多层的,前景里面还会嵌套前景),所以一个前景被检测到之后不会去隐藏它,导致会出现一个前景被多次检测到的情况,不过这块加一层层级分配算法就能解决了,最终得到出来的分离结果如下:逻辑概要文字处理OCR获取文字粗略位置来看看例子,以下左图是闲鱼首页,右图是基于OCR给出的文字位置信息对文字区域进行标记(图中白色部分),可以看到,大致上位置是准确的 但比较粗糙 无法精确到每个文字本身 而且同一行的不同文字片段 OCR会当成一行去处理。同时,也会有部分非文字的部分 也被当成文字,比如图中的banner文案:切割、CNN鉴别器对以上结果标注的位置进行切割,切割出尽可能小的单个文字区域,交给CNN判断,该文字是否是可编辑的文字,还是属于图片文案,后者将当作图片进行处理,以下是CNN代码:""" ui基础元素识别"""# TODO 加载模型with ui_sess.as_default(): with g2.as_default(): tf.global_variables_initializer().run() # Loads label file, strips off carriage return ui_label_lines = [line.rstrip() for line in tf.gfile.GFile(“AI_models/CNN/ui-elements-NN/tf_files/retrained_labels.txt”)] # Unpersists graph from file with tf.gfile.FastGFile(“AI_models/CNN/ui-elements-NN/tf_files/retrained_graph.pb”, ‘rb’) as f: ui_graph_def = tf.GraphDef() ui_graph_def.ParseFromString(f.read()) tf.import_graph_def(ui_graph_def, name=’’) # Feed the image_data as input to the graph and get first prediction ui_softmax_tensor = ui_sess.graph.get_tensor_by_name(‘final_result:0’)# TODO 调用模型with ui_sess.as_default(): with ui_sess.graph.as_default(): # UI原子级元素识别 def ui_classify(image_path): # Read the image_data image_data = tf.gfile.FastGFile(image_path, ‘rb’).read() predictions = ui_sess.run(ui_softmax_tensor, {‘DecodeJpeg/contents:0’: image_data}) # Sort to show labels of first prediction in order of confidence top_k = predictions[0].argsort()[-len(predictions[0]):][::-1] for node_id in top_k: human_string = ui_label_lines[node_id] score = predictions[0][node_id] print(’%s (score = %s)’ % (human_string, score)) return human_string, score文字抽离如果是纯色背景,文字区域很好抽离,但如果是复杂背景就比较麻烦了。举个例子:基于以上,我们能拿到准确的文本信息,我们逐一对各个文本信息做处理,文本的特征还是比较明显的,比如说含有多个角点,在尝试了多种算法:Harris角点检测、Canny边缘检测、SWT算法,KNN算法(把区域色块分成两部分)之后,发现KNN的效果是最好的。代码如下:Z = gray_region.reshape((-1,1)) Z = np.float32(Z)criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) ret,label,center=cv2.kmeans(Z,K,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)center = np.uint8(center) res = center[label.flatten()]res2 = res.reshape((gray_region.shape))抽离后结果如下:查找前景强化图片边缘,弱化非边缘区域使用卷积核对原图进行卷积,该卷积核可以强化边缘,图像平滑区域会被隐藏。conv_kernel = [ [-1, -1, -1], [-1, 8, -1], [-1, -1, -1]]卷积后,位与操作隐藏文字区域,结果如下:降噪对卷积后的图,加一层降噪处理,首先把图像转为灰度图,接着二值化,小于10像素值的噪点将被隐藏,最后使用cv2.connectedComponentsWithStats()算法消除小的噪点连通区域。基于文字位置,开始查找轮廓我们基于前面拿到的文字信息,选中文字左上角坐标,以这个点为种子点执行漫水填充算法,之后我们会得到一个区域,我们用cv2.findContours()来获取这个区域的外部轮廓,对轮廓进行鉴别,是否符合有效前景的特征,之后对区域取反,重新执行cv2.findContours()获取轮廓,并鉴别。判断内外部轮廓如果文字在轮廓内部,那拿到的区域将不会包含该区域的border边框,如果文字在轮廓外部,就能拿到包含边框的一整个有效区域(边框应该隶属于前景),所以咱们要判断文字和轮廓的位置关系(cv2.pointPolygonTest),如果在内部,会使轮廓往外扩散,知道拿到该轮廓的边框信息为止。前景鉴别器基于前面的步骤,我们会拿到非常多非常多的轮廓,其实绝大部分是无效轮廓以及重复检测到的轮廓,咱们需要加一层鉴别器来对这些轮廓进行过滤,来判断它是否是有效前景。定义有效shape我们会预先定义我们认为有意义的形状shape,比如说矩形、正方形、圆形,只要检测到的轮廓与这三个的相似度达到了设定的阀值要求,并且轮廓中还包含了文字信息,我们就认为这是一个有意义的前景,见代码:# TODO circlecircle = cv2.imread(os.getcwd()+’/fgbgIsolation/utils/shapes/circle.png’, 0), contours, _ = cv2.findContours(circle, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)self.circle = contours[0]# TODO squaresquare = cv2.imread(os.getcwd()+’/fgbgIsolation/utils/shapes/square.png’, 0), contours, _ = cv2.findContours(square, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)self.square = contours[0]# TODO rectrect = cv2.imread(os.getcwd()+’/fgbgIsolation/utils/shapes/rect.png’, 0), contours, _ = cv2.findContours(rect, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)self.rect = contours[0]匹配shape相似度多次尝试之后 发现score设置为3的效果是最好的。代码如下:# TODO 检测图形相似度def detect(self, cnt): shape = “unidentified” types = [self.square, self.rect, self.circle] names = [‘square’, ‘rect’, ‘circle’] for i in range(len(types)): type = types[i] score = cv2.matchShapes(type, cnt, 1, 0.0) # score越小越相似 # TODO 一般小于3是有意义的 if score<3: shape = names[i] break return shape, score单一匹配shape相似度的鲁棒性还是不够健壮,所以还引入了其他过滤逻辑,这里不展开。图像修复可以预见的,我们传入的图片只有一张,但我们划分图层之后,底层的图层肯定会出现“空白”区域,我们需要对这些区域进行修复。计算重叠区域需要修复的区域只在于重叠(重叠可以是多层的)的部分,其他部分我们不应该去修复。计算重叠区域的解决方案沿用了mask遮罩的思路,我们只需要计算当前层有效区域和当前层之上层有效区域的交集即可,使用cv2.bitwise_and# mask是当前层的mask layers_merge是集合了所有前景的集合 i代表当前层的层级数 # inpaint_mask 是要修复的区域遮罩# TODO 寻找重叠关系UPPER_level_mask = np.zeros(mask.shape, np.uint8) # 顶层的前景UPPER_level_mask = np.where(layers_merge>i, 255, 0)UPPER_level_mask = UPPER_level_mask.astype(np.uint8), contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 查找当前层的每个前景外轮廓overlaps_mask = np.zeros(mask.shape, np.uint8) # 当前层的所有前景的重叠区域for cnt in contours: cnt_mask = np.zeros(mask.shape, np.uint8) cv2.drawContours(cnt_mask, [cnt], 0, (255, 255, 255), cv2.FILLED, cv2.LINE_AA) overlap_mask = cv2.bitwise_and(inpaint_mask, cnt_mask, mask=UPPER_level_mask) overlaps_mask = cv2.bitwise_or(overlaps_mask, overlap_mask)# TODO 将当前层重叠区域的mask赋值给修复maskinpaint_mask = overlaps_mask修复使用修复算法cv2.INPAINT_TELEA,算法思路是:先处理待修复区域边缘上的像素点,然后层层向内推进,直到修复完所有的像素点。# img是要修复的图像 inpaint_mask是上面提到的遮罩 dst是修复好的图像dst = cv2.inpaint(img, inpaint_mask, 3, cv2.INPAINT_TELEA)延展本文大概介绍了通过计算机视觉为主,深度学习为辅的图片复杂前后景分离的解决方案,除了文中提到的部分,还有几层轮廓捕获的逻辑因为篇幅原因,未加展开,针对比较复杂的case,本方案已经能够很好的实现图层分离,但对于更加复杂的场景,比如边缘颜色复杂度高,噪点多,边缘轮廓不明显等更复杂的case,分离的精确度还有很大的提升空间。本文作者:闲鱼技术-云听阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 21, 2018 · 2 min · jiezi

PyQt5 内嵌浏览器注入 Javascript 脚本实现自动化操作

概要应同学邀请,演示如何使用 PyQt5 内嵌浏览器浏览网页,并注入 Javascript 脚本实现自动化操作。sg 原贴地址: 如何在Python利用runJavaScript模拟鼠标移动页面的某个元素https://segmentfault.com/q/10…下面测试的是一个廉价机票预订网站(http://www.flyscoot.com/),关键点如下使用 QWebEngineView 加载网页,并显示进度。在默认配置(QWebEngineProfile)中植入 Javascript 内容,这样脚本会在所有打开的网页中执行,不论跳转到哪个网址。Javascript 脚本使用网址中的路径名,判断当前网页位置,从而决定执行哪种操作。python 代码示例#!/usr/bin/env python3# -- coding: utf-8 --‘‘‘使用 PyQt5 内嵌浏览器浏览网页,并注入 Javascript 脚本实现自动化操作。‘‘‘import osimport sysfrom datetime import datetimefrom PyQt5.QtWidgets import ( QWidget, QApplication, QVBoxLayout, QHBoxLayout, QDesktopWidget, QTextEdit, QLabel, QLineEdit, QPushButton, QFileDialog, QProgressBar,)from PyQt5.QtCore import QUrl, pyqtSlotfrom PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEngineScript, QWebEnginePageclass Browser(QWidget): def init(self): super().init() self.init_ui() # 脚本 self.profile = QWebEngineProfile.defaultProfile() self.script = QWebEngineScript() self.prepare_script() def init_ui(self): self.webView = QWebEngineView() self.logEdit = QTextEdit() self.logEdit.setFixedHeight(100) self.addrEdit = QLineEdit() self.addrEdit.returnPressed.connect(self.load_url) self.webView.urlChanged.connect( lambda i: self.addrEdit.setText(i.toDisplayString())) self.jsEdit = QLineEdit() self.jsEdit.setText(‘inject.js’) loadUrlBtn = QPushButton(‘加载’) loadUrlBtn.clicked.connect(self.load_url) chooseJsBtn = QPushButton(‘选择脚本文件’) chooseJsBtn.clicked.connect(self.choose_js_file) # 导航/工具 top = QWidget() top.setFixedHeight(80) topBox = QVBoxLayout(top) topBox.setSpacing(0) topBox.setContentsMargins(5, 0, 0, 5) progBar = QProgressBar() progBox = QHBoxLayout() progBox.addWidget(progBar) topBox.addLayout(progBox) naviBox = QHBoxLayout() naviBox.addWidget(QLabel(‘网址’)) naviBox.addWidget(self.addrEdit) naviBox.addWidget(loadUrlBtn) topBox.addLayout(naviBox) naviBox = QHBoxLayout() naviBox.addWidget(QLabel(‘注入脚本文件’)) naviBox.addWidget(self.jsEdit) naviBox.addWidget(chooseJsBtn) topBox.addLayout(naviBox) self.webView.loadProgress.connect(progBar.setValue) # 主界面 layout = QVBoxLayout(self) layout.addWidget(self.webView) layout.addWidget(top) layout.addWidget(self.logEdit) self.show() self.resize(1024, 900) self.center() def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) @pyqtSlot() def load_url(self): url = self.addrEdit.text().strip() if not url.lower().startswith(‘http://’) and not url.lower().startswith(‘https://’): url = ‘http://{}’.format(url) self.load(url) @pyqtSlot() def choose_js_file(self): f, _ = QFileDialog.getOpenFileName(filter=“Javascript files(*.js)”) if os.path.isfile(f): self.jsEdit.setText(f) self.prepare_script() def prepare_script(self): path = self.jsEdit.text().strip() if not os.path.isfile(path): self.log(‘invalid js path’) return self.profile.scripts().remove(self.script) with open(path, ‘r’) as f: self.script.setSourceCode(f.read()) self.profile.scripts().insert(self.script) self.log(‘injected js ready’) def log(self, msg, *args, **kwargs): m = msg.format(*args, **kwargs) self.logEdit.append(’{} {}’.format( datetime.now().strftime(’%H:%M:%S’), m)) def load(self, url): self.log(f’loading {url}’) self.addrEdit.setText(url) self.webView.load(QUrl(url))if name == ‘main’: app = QApplication(sys.argv) b = Browser() b.load(‘http://www.flyscoot.com/') sys.exit(app.exec_())Javascript 脚本示例// 简单起见,这里只演示部分页面,脚本内容摘自 Heng丶原贴文。function handle(path) { // 首页 if (path == ‘/zh’) { document.getElementsByClassName(‘radio-inline’)[1].click(); document.getElementById(‘oneway_from’).value=‘广州 (CAN)’; document.getElementById(‘oneway_to’).value=‘新加坡 (SIN)’; document.getElementById(‘oneway_departuredate’).value=‘2018年9月10日’; document.getElementsByClassName(‘btn–booking’)[1].click(); return; } // 选择航班 if (path == ‘/Book/Flight’) { document.getElementsByClassName(‘price–sale’)[0].click(); document.getElementsByClassName(‘heading-4’)[0].click(); document.getElementsByClassName(‘btn-submit’)[0].click(); return; } // 乘客信息 if (path == ‘/BookFlight/Passengers’) { document.getElementsByClassName(‘fname1’)[0].value = “匿名”; }}let host = document.location.hostname;if (host.endsWith(’.flyscoot.com’)) { handle(document.location.pathname);} ...

September 1, 2018 · 2 min · jiezi