热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

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

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


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





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

  • 简单图像修改

  • Slack集成

  • 面部检测

  • 以往我曾经使用过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包(可在电脑端查看完整代码,下同)

  • 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 } 

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

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

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

  • 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 } 

  • 图像修改

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

  • 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, ) } 

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



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

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



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

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

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

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

  • 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, ) } 

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





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

    Slack集成

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

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

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



    不错!

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

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

  • 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, ) } 

  • 现在的结果是:



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

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



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



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



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

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

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

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

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



    推荐阅读
    • [译]技术公司十年经验的职场生涯回顾
      本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
    • 学习SLAM的女生,很酷
      本文介绍了学习SLAM的女生的故事,她们选择SLAM作为研究方向,面临各种学习挑战,但坚持不懈,最终获得成功。文章鼓励未来想走科研道路的女生勇敢追求自己的梦想,同时提到了一位正在英国攻读硕士学位的女生与SLAM结缘的经历。 ... [详细]
    • 云原生边缘计算之KubeEdge简介及功能特点
      本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
    • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
      本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
    • sklearn数据集库中的常用数据集类型介绍
      本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
    • GPT-3发布,动动手指就能自动生成代码的神器来了!
      近日,OpenAI发布了最新的NLP模型GPT-3,该模型在GitHub趋势榜上名列前茅。GPT-3使用的数据集容量达到45TB,参数个数高达1750亿,训练好的模型需要700G的硬盘空间来存储。一位开发者根据GPT-3模型上线了一个名为debuid的网站,用户只需用英语描述需求,前端代码就能自动生成。这个神奇的功能让许多程序员感到惊讶。去年,OpenAI在与世界冠军OG战队的表演赛中展示了他们的强化学习模型,在限定条件下以2:0完胜人类冠军。 ... [详细]
    • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
    • 如何用UE4制作2D游戏文档——计算篇
      篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
    • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
      本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
    • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
    • Android开发实现的计时器功能示例
      本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
    • 3年半巨亏242亿!商汤高估了深度学习,下错了棋?
      转自:新智元三年半研发开支近70亿,累计亏损242亿。AI这门生意好像越来越不好做了。近日,商汤科技已向港交所递交IPO申请。招股书显示& ... [详细]
    • 当写稿机器人真有了观点和感情,我们是该高兴还是恐惧?
      目前,写稿机器人多是撰写以数据为主的稿件,当它们能够为文章注入观点之时,这些观点真的是其所“想”吗?最近,《南 ... [详细]
    • TiDB | TiDB在5A级物流企业核心系统的应用与实践
      TiDB在5A级物流企业核心系统的应用与实践前言一、业务背景科捷物流概况神州金库简介二、现状与挑战神州金库现有技术体系业务挑战应对方案三、TiDB解决方案测试迁移收益问题四、说在最 ... [详细]
    • ICRA2019最佳论文  Making Sense of Vision and Touch: SelfSupervised Learning of Multimodal Representatio
      文章目录摘要模型架构模态编码器自监督预测控制器设计策略学习控制器设计实验结论和展望会议:ICRA2019标题:《MakingSenseofVision ... [详细]
    author-avatar
    谢绝沟通
    这个家伙很懒,什么也没留下!
    PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
    Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有