国外程序员真会玩,他用这个技术整蛊了全公司的人…

译文
新闻 移动开发
我喜欢用Photoshop修改各种东西,再把结果在Slack公司内发布,每次都能带来新的想法我享受在其中。不过重复打开Photoshop再复制/粘贴面部图像确实相当乏味。

【51CTO.com快译】我喜欢用Photoshop修改各种东西,再把结果在Slack公司内发布,每次都能带来新的想法我享受在其中。

不过重复打开Photoshop再复制/粘贴面部图像确实相当乏味。

程序员

[[185213]]

在最初产生这个想法时,我就意识到这个项目将主要包含三大组成部分:

1. 简单图像修改

2. Slack集成

3. 面部检测

以往我曾经使用过Go中的image与image/draw软件包,并阅读过与之相关的几篇文章,因此我对于完成这项任务很有信心。组成部分1就此搞定。

我还曾经在Go中构建过一款玩具性质的Slack机器人,其中用到了查找自谷歌的几条指令。虽然缺少Go Slack官方整体客户端会让问题变得更为复杂,但出于最基本的需求,我相信自己能够完成通过Slack下载及上传图像这样一项工作。组成部分2也就不是问题了。

我唯一不确定的是面部检测工作到底是否易于实现。我在谷歌上查找golang面部检测内容,并点开***条结果,其内容指向StackOverflow上关于go-opencv计算机视觉库的一条问题。在查阅了该库中的面部检测示例项目后,我了解到了自己需要掌握的一切。组成部分3也同样得到了解决。

面部检测

由于熟悉度***,所以我决定首先从面部检测入手。这是项目中***的难题,因此我打算先看看自己能否搞定,如果不行那其它的工作都将毫无意义。

我决定尽可能对go-opencv库进行封装。可以肯定的是,opencv数据类型与Go标准库有所区别,至少在其定义Image与Rectangle两项接口方面存在差异,因此必须作出一些调整。

我在其中发现一项对opencv.FromImage方法的引用,其负责将Go的image.Image转换为opencv库的形式。这意味着我不再需要将文件路径传递至opencv.LoadImage方法以进行转换,而可以直接处理存储在内存中的镜像。这能够节约从Slack接收图像后将其保存在文件系统中的步骤。

遗憾的是,我无法利用同样的转换方式加载Haar面部识别XML文件,不过这样的结果我还可以接受,所以暂时先这样吧。

以此为基础,我编写出了以下facefinder包:

  1. package facefinder import ( "image""github.com/lazywei/go-opencv/opencv" ) var faceCascade *opencv.HaarCascade type Finder struct { cascade *opencv.HaarCascade } func NewFinder(xml string) *Finder { return &Finder{ cascade: opencv.LoadHaarClassifierCascade(xml), } } func (f *Finder) Detect(i image.Image) []image.Rectangle { var output []image.Rectangle faces :f.cascade.DetectObjects(opencv.FromImage(i)) for _, face :range faces { output = append(output, image.Rectangle{ image.Point{face.X(), face.Y()}, image.Point{face.X() + face.Width(), face.Y() + face.Height()}, }) } return output } 

而后,我能够轻松找到图像中的面部区域:

  1. imageReader, _ :os.Open(imageFile) baseImage, _, _ :image.Decode(imageReader) finder :facefinder.NewFinder(haarCascadeFilepath) faces :finder.Detect(baseImage) for _, face :range faces { // [...] } 

我从谷歌上复制了几段“绘制矩形”代码以进行功能检查,并确定以上代码确实能够正常工作。有了位置信息,我又鼓捣出一条图像加载转换函数(其中更关注错误内容,而非急于将一切塞进)。

  1. func loadImage(file string) image.Image { reader, err :os.Open(file) if err != nil { log.Fatalf("error loading %s: %s", file, err) } img, _, err :image.Decode(reader) if err != nil { log.Fatalf("error loading %s: %s", file, err) } return img } 

图像修改

接下来,我的新循环如下所示:

  1. baseImage :loadImage(imageFile) chrisFace :loadImage(chrisFaceFile) bounds :baseImage.Bounds() finder :facefinder.NewFinder(haarCascadeFilepath) faces :finder.Detect(baseImage) // Convert image.Image to a mutable image.ImageRGBA canvas :image.NewRGBA(bounds) draw.Draw(canvas, bounds, baseImage, bounds.Min, draw.Src) for _, face :range faces { draw.Draw( canvas, face, chrisFace, bounds.Min, draw.Src, ) } 

令人振奋,测试结果一切顺利。

[[185214]]

言归正传,其***实际效果就远超我的预期。矩形绘制算法真棒!

在图像修改方面,我首先得想办法去掉黑色背景。我以前曾使用过PNG配合透明背景的方法,因此确信其一定有效。在谷歌了几下后,我偶然发现了draw.Draw函数中的draw.Over。我将其塞进正在使用的draw.Src,确实有效!

[[185215]]

虽然也可以用羽毛笔慢慢绘边,但脑袋里的一个声音告诉我,差不多就可以了。

好的,接下来我需要把面部图像缩小一点。可以肯定的是,如果将面部图像放进尺寸完全相同的矩形,那么二者肯定无法匹配。这只是一款面部检测工具,而非头部检测工具,这意味着我获得的矩形并不适用于替换整个头部。我编写了一条快速函数以为image.Rectangle增加特定空白边缘,最终将具体值设定为30%。

完成后,我开始对图像进行大小/匹配调整。最终,我选择了disintegration/imaging,其拥有一条简单的imaging.Fit函数且提供水平镜像等其它转换操作。我的面部源图像不多,所以我想这种镜像功能可以提供多一种图像选择。

在导入后,我的新循环如下所示:

  1. for _, face :range faces { // Pad the rectangle by 30 percent rect :rectMargin(30.0, face) // Grab a random face (also 50/50 chance it's mirrored) newFace :chrisFaces.Random() chrisFace :imaging.Fit(newFace, rect.Dx(), rect.Dy(), imaging.Lanczos) draw.Draw( canvas, rect, chrisFace, bounds.Min, draw.Over, ) } 

我又进行了一轮新的测试,效果相当不错!

[[185216]]

[[185217]]

到这里,我意识到自己做出了一些真正有价值的东西。

Slack集成

我把面部修改代码转化为一个可运行的二进制文件,并打算将其打包成一个Slack机器人。之所以先转换为二进制形式,是为了方便测试并在确定一切无误后再行打包。现在时机已经成熟,我将把它变成Slack机器人。

当然,由于个人水平的限制,我又转向了谷歌。

***条结果就是我所需要的内容。我花了大量时间阅读Slack的API说明文档并加以实践,最终我得到了以下结果:

程序员

不错

***套迭代使用了Slack上传,但其作为自由Slack层意味着其不够理想。我转而将输出结果以本地方式存储在自己的服务器上,而后再将其链至Slack。由于Slack会自动扩展大部分图像链接,因此这种作法对大多数人来说并不会影响到用户体验,也不会引来顶头上司的注意。

由于访问过程更为轻松,现在我能够快速获得大量实验性面部图像。我意识到,如果其找不到任何面部图像,则会全程回复同样的原有图像——这就不好玩了。所以我将循环调整为:

  1. iflen(faces) == 0 { // Grab a specific face and resize it to 1/3 the width// of the base image face :imaging.Resize( chrisFaces[0], bounds.Dx()/3, 0, imaging.Lanczos, ) face_bounds :face.Bounds() draw.Draw( canvas, bounds, face, // I'll be honest, I was a couple beers in when I came up with this and I// have no idea how it works exactly, but it puts the face at the bottom of// the image, centered horizontally with the lower half of the face cut off bounds.Min.Add(image.Pt( -bounds.Max/2+face_bounds.Max.X/2, -bounds.Max.Y+int(float64(face_bounds.Max.Y)/1.9), )), draw.Over, ) } 

现在的结果是:

[[185218]]

我个人对这套解决方案非常满意。

到这里全部工作已经就绪,就等同事们的反应了。我只用了一个晚上就完全了从概念到原型的全部工作,没人知道我为他们准备了怎样的惊喜。

程序员

截至目前,我的经理是最为积极的Chrisbot手动配置用户。

程序员

抱歉了Mat,看来自动化方案最终一定会取代人类的职位。

[[185219]]

但这家伙自己则非常开心。

不久之后,整个办公室都在向@Chrisbot发送图片。

我惊喜地发现,它确实能够正确地处理面部重叠情况,即首先绘制最远处的面孔。虽然这纯粹属于go-opencv库返回矩形时实际顺序带来的副作用,但我对结果非常满意。

不过虽然自动化面部替换大大增加了Slack当中Chris的亮相次数,但仍有一些人认为,人为操作的结果更有灵性一些。

不得不承认,他们的观点确实站得住脚——至少在某些情况之下。

程序员

【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】

责任编辑:陈琳 来源: 51cto
相关推荐

2019-04-26 13:26:00

预测股票深度学习股票

2021-02-15 16:30:35

AI人工智能人脸识别

2020-09-25 15:43:25

程序员网站技术

2012-11-22 14:00:26

程序员

2020-10-12 08:45:25

程序员技术开发

2011-06-11 20:59:12

程序员

2016-11-29 07:53:57

科技新闻早报计算机

2020-05-08 10:28:29

Node.js程序员JavaScript

2017-01-17 12:15:14

互联网 机器

2018-06-14 09:59:48

程序员代码大公司

2020-06-15 09:32:59

程序员大公司小公司

2017-06-12 11:14:52

程序员技术停滞

2009-03-13 10:27:25

女程序员天才人生

2012-06-27 09:29:49

程序员

2019-03-04 15:09:49

程序员互联网思维模式

2012-07-20 11:16:26

程序员

2020-10-28 09:43:40

前端开发Vue

2022-08-01 09:43:19

程序员Googlefacebook

2015-09-02 13:19:55

微软Cortana

2015-09-24 09:04:36

程序员
点赞
收藏

51CTO技术栈公众号