图像简史——程序员眼中的图像发展史
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图像简史——程序员眼中的图像发展史相关的知识,希望对你有一定的参考价值。
参考技术A 人,是感官的动物。我们的大脑,像一块复杂度极高的CPU,每天在接收着各种格式的数据,进行着无休止的计算。我们以各种感官接触着这个世界,抽取着不同感官下的信息,从而认知了世界。而图像作为承载信息最为丰富的一种媒介,在人类探索智慧的历史中,一直占据着重要的位置。人用这样一双肉眼如何识别不同类别的图像(image classification and pattern recognition),如何在图像中分割出形形色色的物体(semantic segmentation and object detection),如何从模糊的图像中想象出物体的轮廓(image super-resolution),如何创作出天马行空的图画(image synthesis),都是目前 机器视觉图像处理领域 关注的热点问题。全世界的研究者都希望有朝一日,计算机能代替人眼来识别这一幅幅图像,发现在图像中隐藏的密码。
图像分类是图像处理中的一个重要任务 。在传统机器学习领域,去识别分类一个一个图像的标准流程是特征提取、特征筛选,最后将特征向量输入合适的分类器完成特征分类。直到2012年Alex Krizhevsky突破性的提出AlexNet的网络结构, 借助深度学习的算法,将图像特征的提取、筛选和分类三个模块集成于一体 ,设计5层卷积层加3层全连接层的深度卷积神经网络结构,逐层对图像信息进行不同方向的挖掘提取,譬如浅层卷积通常获取的是图像边缘等通用特征,深层卷积获取的一般是特定数据集的特定分布特征。AlexNet以15.4%的创纪录低失误率夺得2012年ILSVRC(ImageNet大规模视觉识别挑战赛)的年度冠军,值得一提的是当年亚军得主的错误率为26.2%。 AlexNet超越传统机器学习的完美一役被公认为是深度学习领域里程碑式的历史事件,一举吹响了深度学习在计算机领域爆炸发展的号角 。
时间转眼来到了2014年,GoogleNet横空出世,此时的深度学习,已经历ZF-net,VGG-net的进一步精炼,在网络的深度,卷积核的尺寸,反向传播中梯度消失问题等技术细节部分已有了详细的讨论,Google在这些技术基础上引入了Inception单元,大破了传统深度神经网络各计算单元之间依次排列,即卷积层->激活层->池化层->下一卷积层的范式,将ImageNet分类错误率提高到了6.7%的高水平。
在网络越来越深,网络结构越来越复杂的趋势下,深度神经网络的训练越来越难,2015年Microsoft大神何恺明(现就职于Facebook AI Research)为了解决训练中准确率先饱和后降低的问题,将residual learning的概念引入深度学习领域,其核心思想是当神经网络在某一层达到饱和时,利用接下来的所有层去映射一个f(x)=x的函数,由于激活层中非线性部分的存在,这一目标几乎是不可能实现的。
但ResNet中,将一部分卷积层短接,则当训练饱和时,接下来的所有层的目标变成了映射一个f(x)=0的函数,为了达到这一目标,只需要训练过程中,各训练变量值收敛至0即可。Resdiual learning的出现,加深网络深度提高模型表现的前提下保证了网络训练的稳定性。2015年,ResNet也以3.6%的超低错误率获得了2015年ImageNet挑战赛的冠军,这一技术也超越了人类的平均识别水平,意味着人工智能在人类舞台中崛起的开始。
图像分类任务的实现可以让我们粗略的知道图像中包含了什么类型的物体,但并不知道物体在图像中哪一个位置,也不知道物体的具体信息,在一些具体的应用场景比如车牌识别、交通违章检测、人脸识别、运动捕捉,单纯的图像分类就不能完全满足我们的需求了。
这时候,需要引入图像领域另一个重要任务: 物体的检测与识别 。在传统机器领域,一个典型的案例是利用HOG(Histogram of Gradient)特征来生成各种物体相应的“滤波器”, HOG滤波器 能完整的记录物体的边缘和轮廓信息,利用这一滤波器过滤不同图片的不同位置,当输出响应值幅度超过一定阈值,就认为滤波器和图片中的物体匹配程度较高,从而完成了物体的检测。这一项工作由Pedro F. Felzenszalb,Ross B. Girshick,David Mcallester还有Deva Ramanan以Object Detection with Discriminatively Trained Part-Based Models共同发表在2010年9月的IEEE Transactions on Pattern Analysis and Machine Interlligence期刊上。
时间如白驹过隙,惊鸿一瞥,四年过去,Ross B. Girishick已由当年站在巨人肩膀上的IEEE Student Member成长为了AI行业内独当一面的神级人物,继承了深度学习先驱的意志,在2014年CVPR会议上发表题为Rich Feature Hirarchies for Accurate Object Detection and Semantic Segmentation文章。RCNN,一时无两,天下皆知。
RCNN 的核心思想在于将一个物体检测任务转化为分类任务 ,RCNN的输入为一系列利用selective search算法从图像中抽取的图像块,我们称之为region proposal。经过warping处理,region proposals被标准化到相同的尺寸大小,输入到预先训练好并精细调参的卷积神经网络中,提取CNN特征。得到了每一个proposal的CNN特征后,针对每一个物体类别,训练一个二分类器,判断该proposal是否属于该物体类别。2015年,为了缩短提取每一个proposal的CNN特征的时间,Girishick借鉴了Spatial Pooling Pyramid Network(SPPnet)中的pooling技术,首先利用一整幅图像提取CNN特征图谱,再在这张特征图谱上截取不同的位置的proposal,从而得到不同尺寸的feature proposals,最后将这些feature proposals通过SPPnet标准化到相同的尺寸,进行分类。这种改进,解决了RCNN中每一个proposal都需要进行CNN特征抽取的弊端,一次性在整图上完成特征提取,极大的缩短了模型的运行时间,因而被称作“Fast R-CNN”,同名文章发表于ICCV 2015会议。
2015年,Girishick大神持续发力,定义RPN(region-proposal-network)层,取代传统的region proposal截取算法,将region proposal的截取嵌入深度神经网络中,进一步提高了fast R-CNN的模型效率,因而被称作“Faster R-CNN”,在NIPS2015上Girishick发表了题为“Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks”的关键文章,完成了RCNN研究领域的三级跳壮举。
随着时代的发展, 科学家们不仅仅是技术的研究者,更是艺术的创造者 。
在人工智能领域的另一位新一代灵魂人物,Ian Goodfellow在2014年提出了Generative Adversarial Net的概念,通过定义一个生成器(generator)和一个判别器(discriminator)来完成图像生成任务。其原理在于生成器的任务是从随机噪声中“创造”出接近目标图像的“假图像”去欺骗判别器,而判别器的任务是去甄别哪一些图像是来自于真实的数据集,哪一些图像是来自于生成器,在生成器和判别器的互相对抗中,通过合理的损失函数设计完成训练,最终模型收敛后,判别器的概率输出为常数0.5,即一幅图像来自于生成器和真实数据集的概率相同,生成器生成的图像的概率分布无限趋近于真实数据集。
GAN技术成为2015,2016年深度学习研究的热门领域,在图像恢复、降噪、超分辨重建等方向获得了极佳的表现,衍生出一系列诸如WGAN,Info-GAN,DCGAN,Conditional-GAN等技术,引领了一波风潮。
当我们把一帧帧图像串联在一起,变成流动的光影,我们研究的问题就从空间维度上扩展到了时间维度,我们不仅需要关心物体在图像中的位置、类别、轮廓形状、语义信息,我们更要关心图像帧与帧之间的时间关系,去捕捉、识别一个物体的运动,去提取视频的摘要,去分析视频所表达的含义,去考虑除了图像之外的声音、文本标注,去处理一系列的自然语言,我们的研究一步一步,迈向了更广阔的星辰与大海。
图像和视频,都是虚拟的一串串数字,一个个字节,但却让这个世界更加真实 。
前端模块化发展简史
前端发展日新月异,短短不过 10 年已经从原始走向现代,甚至引领潮流。网站逐渐变成了互联网应用程序,代码量飞速增长,为了支撑这种需求和变化,同时兼顾代码质量、降低开发成本,接入模块化势在必行。伴随这一变化的是相对应的构建工具的快速成长,或是为了优化、或是为了转义,都离不开这类工具。
所谓温故而知新,本篇回顾总结下前端模块化的发展历程及辅助工具。在回顾中可以更清晰的看到当前我们用的方案所处的位置,为什么会发展到这一步,目前模块化方案带来的优势等。
1. 没有模块化的日子
最开始 JavaScript 承担的任务量并不多,表单验证基本上就是他的全部,最多就是简短的前端交互,这个时期 JavaScript 组织结构非常凌乱,大部分都是后端哥哥们顺手代劳,那时候还没有“前端”这一职位。 一般都是写到一个文件或者直接写到 jsp、asp 的后端模板页面上就完事了。这个阶段没啥可说的,跳过吧。。。
2. 传统模块化
随着 ajax 的流行,前端能做的东西一夜之间暴涨,代码量飞速增加,单文件维护代码已经太沉重,于是拆之,进而引入模块化,将负责不同功能的代码拆分成小粒度的模块,方便维护。
这里要说的模块化是抛开现在你所熟知的 require,amd,seajs 等,不借助任何的模式和工具,由 JavaScript 直接完成的代码结构化。JavaScript 天生没有模块化的概念(直到 ES6), 而不像后端语言源生自带模块功能, 比如 Java 的 import、C++的 include、Node 的 require(下文说到),所以需要通过其他的方式来实现模块化。
应用模块化开发的主要目的是为了复用代码、代码结构清晰、便于维护等,比如在开发过程中,我们往往会将一些重复用到的代码提取出来,封装到一个 function 里,然后在需要的地方调用,那么这可以看做是一种模块化。
我们看一段代码 例 – 1:
function show(element) { // 展示一个元素 }
function close(element) { // 隐藏一个元素 }
上面的代码非常直观,就是要显示、隐藏一个 dom 元素,往往这种方法需要大范围多次调用,一般我们可能会放到 util.js 这样的文件里,这是第一步。接下来在业务代码中引用,例 – 2
<body>
<script src="lib/utils.js"></script>
<script src="lib/page-1.js"></script>
<script src="lib/page-2.js"></script>
</body>
2.1 存在的问题
以现在的经验来看,上面的写法会带来非常明显的问题,当然也是在这个模块化引入阶段逐步暴露的。
- 全局变量冲突风险:如果编写 page-1 的同学不知道 utils 里面有一个 show/close 方法,然后他自个也写了一个,同时还添加了额外逻辑,自然就覆盖了原来的方法,那么 page-2 同学在不知道的情况下调用了这方法,自然会发生错误.
- 人工维护依赖关系:因为存在依赖关系,所以必须先加载 util,然后才能加载 page-1/2,这里的例子非常简单,但在实际项目场景了,这样的依赖会非常多且复杂,维护非常困难,很难建立清晰的依赖关系。想想当时高大上的校内网,那种程度的页面得需要多少的模块去支撑。后续项目迭代往往会带来意料之外的问题.
2.2 尝试解决问题
针对问题 1,可以做下面这些改进:
2.2.1 代码提取
把这些方法放到一个 object 里面对外输出,例 - 3
var utils = {
_name: ‘baotong.wang’,
show: function(element) {},
close: function(element) {}
}
但这样依然不能避免我们的 utils 被覆盖的可能性,孱弱的英语积累让我们想不出什么更高级的词来命名 utils,var fuzhufangfa = {}?。。。
不过这种写法同时还带来了暴露内部变量的问题,外部可以访问到 _name。
2.2.2 命名空间
然后部分开发者引入了命名空间,这个东西牛逼了,例 – 4:
var com.company.departure.team.utils = {}
代码模块通过严格的命名规则做了规范,可以按照实际情况具体到部门、team、类库。如果一个公司在代码规范上做了这样的约束,基本上可以避免变量名冲突的问题,但同时带来的需要输入过多单词的负担,目前还没有哪个 IDE 能支持 JavaScript 像 Java 一样可以一路点点点下去,这些都是需要打出来的。当然我们也可以不设计成这么复杂的命名空间,var Company.ProjectName.Module = {}; 同时结合局部变量减少输入的长度。
2.2.3 闭包封装
为了解决封装内部变量的问题,就该有请立即执行的函数登场了,这也是我们接触的最多的一种模块化方式,公司内部有点年纪的项目多少都能看到这样的写法,结合命名空间如下,例 – 5:
(function() {
var Company = Company || {};
Company.Base = Company.Base || {};
var _name = ‘baotong.wang’
function show () {}
function close () {}
Company.Base.Util = {
show: show,
close: close
}
})();
上述写法通过一个立即执行的函数表达式,赋予了模块的独立作用域,同时通过全局变量配置了我们的 module,从而达到模块化的目的。基本上到这一步,问题 1 就解决了。
2.2.4 关于依赖关系
针对问题 2,代码的组织依赖关系,这块我不是很了解,向司徒求证了一下。大概情况如下。
当时业界也是有不少方案的,比如百度的 Tangram 与 Qwrap,查了下他们的 github 地址,最后一次更新是在五年前。它解决依赖关系的方式是在类库中什么依赖,类似 depend=[“com.qunar.dujia.lib”, “”, …],然后通过配套的工具去解析。
同时也有一些后端大牛为了解决前端工程化的问题发明创造了各种方案,但当时的氛围并没有现在这么重视前端,前端从业人员的水平也没现在高;同时后端哥哥们往往对前端问题、痛点了解的不深入,所以开发出来的方案很难推广。比如搞 Java 和搞 Ruby 的后端做的方案基本不太会一样。 用司徒的话来讲叫生不逢时。
2.3 这个时代的工具
2.3.1 代码合并
例 – 2 中的代码引用方式相信肯定存在于一些站点上。虽然不会带来功能问题,但是却带来了很多不必要的 http 请求,特别是复杂页面需要引用很多独立 JavaScript 的时候,从而延长了页面的 ready 时间。所以这里需要合对文件进行合并处理,将可以合并的业务代码连接到一个文件里。
需要注意的是,合并并不是所有的文件合并为一个为好,比如公共文件 jquery 文件、功能公共方法可以单独引用,利用浏览器的缓存机制,减少多页面情况下总的下载量。如果站点一共就是一个 SPA,合并为一个为好。
2.3.2 代码混淆压缩
另外一个就是代码压缩,现在的同学对这个肯定非常熟悉了,但是即便现在找一个你熟悉的网站看一下,也不敢说一定做到了这一步。
走到这有了这两步,网站看起来就挺像那么回事了。
2.3.3 代表性工具
YUI compressor,出自雅虎,在那个时期雅虎可以说是网站优化的风向标,同样出自雅虎的前端优化 34 条(数量不同版本不一样)在业界也是鼎鼎大名,为前端做出了很大贡献。
这个时候适合模块化的通用工具并未出现,相信有实力的大公司都有内部的一条工具去做类似的事情,这里个人所知有限,没啥发言权,欢迎大家交流讨论。
3. Node 来了
2009 年,node 的发布给前端同学带来了无限可能,npm 生态的逐渐成熟给了我们更多选择,以往需要通过其他语言工具执行的编译过程也可以由前端一手接管。同时 node 也带来了 commonJS,给前端的模块化提供了新的思路,我们这里首先关注 node 实现的 commonJS 规范。
3.1 commonJS 概述
作为后端语言,没有模块化加载机制是运转不起来的,node 选择实现了 commonJS 作为它的模块加载方案,整体非常简单。注:commonJS 并不是 node 发明的,他只是按照该规范做了一套实现。
3.2 npm 生态
npm 生态让 node 有了自己的模块仓库,各种类库的不断支持让我们也有了更多选择。commonJS 一开始就提供了对 npm module 的支持,在路径查找的时候内部配置了对 node_modules 文件夹的查找支持。
3.3 说说 node
对前端来说幸运的是 node 的设计者 Ryan Dahl 选择了 JavaScript 作为他的支持语言,这也说明了 JavaScript 事件驱动的魅力所在。大批后端的加入丰富了作为一门后端语言的各种基本功能。
对于前端同学来说 node 有着天然的亲和力,让我们多了一个全新施展本领的领域;同时对于懂后端的同学来说视乎可以大干一场了。目前我们用的最多的有两部分,node 布置站点、数据接口集成维护,这个和本篇没啥关系,不展开说;另外一部分就是利用 node 开发工作工具,提高前端的工作效率,社区里解析 commonJS 的、构建工程工具不断喷涌而出, 具有代表性的有 grunt、gulp、browserify,webpack,前端模块化可以更进一步。
4. 模块化方案
4.1 commonJS
简单概括下 commonJS 的几个概念,还是非常简单的
- 每个文件是一个模块,有自己的作用域。这里面定义到函数、变量、类都是私有的,对其他文件不可见;
- 每个模块内部,module 变量代表当前模块,它是一个对象;
- module 的 exports 属性(即 module.exports)是对外的接口;加载某个模块,其实是加载该模块的 module.exports 属性如果文件中没有 exports 属性,那么外部引用不到任何东西;
- 使用 require 关键字加载对应的文件,也就是模块;
- require 命令的基本功能是,读入并执行一个 JavaScript 文件,然后返回该模块的 exports 对象,如果没有发现该模块,报错。
这里对上面的代码做了模块化的改进,文件内部设置的对外输出,下面的写法在 node 环境中源生支持。
// 设置文件输出
module.exports = {
func: function() {},
field: "string"
}
// 添加单个 export
module.exports.show = function() {}
//这里引入几个模块、文件
require("modulepath");
var Base = require("../base.js");
var page = require("./file.js");
page.show();
4.2 其它模式
除了 commonJS,当前留下的模块化模式还有以 requireJS 为代表的 AMD 和以 seaJS 为代表 CMD, 在去哪儿网内部始终是 commonJS 占据主流,我个人也是喜欢 commonJS 更多一些,requireJS 可以做到在浏览器端执行动态异步模块加载,仅从首次代码下载量的角度讲,这种方案更好一些,但我们完全有其他办法在 commonJS 模式下解决这有个问题,所以本篇主要介绍 commonJS 和编译工具支持的一个思路
4.3 代码改造
基于 commonJS,回过头来再看下例 -5 中的代码应该怎么改造。
- 首先,那层闭包可以不用加了,你没看到有谁在 node 里面加这个东西吧,这层其实还是需要有点,但是我们交给工具自动帮我们加上。
- 其次,我们需要在模块内部写上对外输出的内容,module.exports = *;
- 然后,在业务代码中添加对模块的引用,var module = require(“modulepath”), 有了这个之后就能引用 module export 出来的功能了
- 最后,通过打包工具的编译,解析 commonJS,分析入口文件得到最终输出。
最终得到的代码如下:
// module.js
var _name = ‘baotong.wang‘;
function show() { alert(_name); }
function close() {}
module.exports = {
show,
close
}
// page.js
var module = require(‘./module.js‘);
module.show();
4.4 浏览器端支持
commonJS 是服务器端的模块化方案,浏览器端是不支持的,单是 require 就没有,所以就需要辅助工具来替我们完成 commonJS 代码向浏览器代码的转换。
社区成熟的解析类库有 browserify,能够完美解析 commonJS;因为公司内部业务的特点需要,browserify 并不能满足实际需求,因此去哪儿网内部先后推出了 fekit、ykit 两款针对 commonJS 的前端工具,来执行代码的编译。前者是自己实现的一套解析 commonJS 的工具集,对一些规范的实现不是很规范,同时面向的是内部的 module 仓库,导致和主流 npm 环境脱节,于是有了 ykit;后者是基于 webpack 和公司业务特点封装的一个工具集,核心打包交给了 webpack,同时做了部分优化,具体前面发过一篇文章,介绍过实现机制板面。
在这我说下 fekit 的编译过程,介绍下这个工具处理 commonJS 的一般思路。
4.5 fekit 编译过程
fekit 是一个基于 node 的命令行工具集,在支持 commonJS 的过程中也做了一些修改和扩展,比如支持在 css 文件中通过 require 加载文件,做到和 JS 文件一样;增加对内部 module 仓库的支持,下图介绍了一次 pack 的具体执行过程。下面的流程适合模块解析相关的部分,其他业务构建部分在这里跳过。
module 处理模板代码
;(function(__context) {
var module = {
id : "{{md5Key}}" ,
filename : "{{fileName}}" ,
exports : {}
};
if( !__context.____MODULES ) {
__context.____MODULES = {};
}
var r = (function( exports , module , global ) {
//----------原始文件代码----------
{source}
//----------原始文件代码----------
})( module.exports , module , __context );
__context.____MODULES[ "{{md5Key}}" ] = module.exports;
})(this);
在业务代码中的 require 会变成,即通过一个 object 拿到 module.exports。 var module = context.__MODULES[“md5Key”]; 以上就是对一个 commonJS 文件的解析过程了。
4.6 ES6 的模块化方案
ES6 中给出了 import export 这样的方案,目前为止我们都是通过 babel 将 ES6 代码转为 ES5,import 转为了 require,export 转为了 module.exports,即 commonJS。
他的实现原理和 commonJS 这种引用即引用整个类不一样,它是用啥就引用啥,export 输出的也不是一个类,这里往下说就比较多了,阮一峰老师的 ES6 教程对这块也有比较详细的说明。限于篇幅,本篇不针对这个展开来说了。
5. 总结
本篇简单回顾了模块化的发展历程,介绍了以往存在的问题。然后到现代模块化方案的时候,讲解了 commonJS,同时介绍了解析 commonJS 的一种方法。通过一个例子串连,讲述了模块化带来的改变。部分知识点没展开来说,大家有兴趣可以深入学习一下。同时感谢司徒指点,希望本篇能对大家有所帮助,best regards。
以上是关于图像简史——程序员眼中的图像发展史的主要内容,如果未能解决你的问题,请参考以下文章