【.com速译】我喜欢用Photoshop修改各种东西,然后在Slack公司发布结果,每时间我可以带来我喜欢的新想法。但是反复打开Photoshop并复制/粘贴面部图像可能会非常乏味。当我第一次想到这个想法时,我意识到这个项目将主要由三个主要部分组成:1.简单的图像修改2.Slack集成3.人脸检测过去我在Go中使用图像和图像/绘图包,并阅读了几篇关于它的文章,所以我对这项任务很有信心。这就是组件1的内容。我还曾使用从Google找到的一些命令,用Go构建了一个玩具Slack机器人。虽然缺少官方的GoSlack单体客户端使问题复杂化,但对于最基本的需求,我相信我可以通过Slack完成下载和上传图片等工作。组件2不再是问题。我唯一不确定的是人脸检测工作的难易程度。我用google搜索了golang人脸检测内容并点击了第一个结果,它指向了StackOverflow上关于go-opencv计算机视觉库的一个问题。在查阅了图书馆的人脸检测示例项目后,我知道了我需要知道的一切。还解决了组件3。人脸检测很熟悉,所以我决定先从人脸检测入手。这是项目中最棘手的问题,所以打算看看能不能搞定,搞不定其他的工作就没有意义了。我决定尽可能包装go-opencv库。可以肯定的是,opencv的数据类型不同于Go标准库,至少在Image和Rectangle这两个接口的定义上是不同的,所以必须做一些调整。在里面我找到了opencv.FromImage方法的引用,这个方法负责将Go的image.Image转换成opencv库的形式。这意味着我不再需要将文件路径传递给opencv.LoadImage方法进行转换,而是可以直接使用内存中存储的图像。这样就省去了从Slack接收到图像后将图像保存到文件系统的步骤。不幸的是,我无法使用相同的转换加载Haar面部识别XML文件,但我对结果没问题,所以我们就这样吧。基于此,我写了如下的facefinder包:{级联:opencv.LoadHaarClassifierCascade(xml),}}func(f*Finder)Detect(iimage.Image)[]image.Rectangle{varoutput[]image.Rectanglefaces:=f.cascade.DetectObjects(opencv.FromImage(i))for_,face:=rangefaces{output=append(output,image.Rectangle{image.Point{face.X(),face.Y()},image.Point{face.X()+face.Width(),face.Y()+face.Height()},})}returnoutput}然后我能够轻松地在图像中找到人脸区域:imageReader,_:=os.Open(imageFile)baseImage,_,_:=image.Decode(imageReader)finder:=facefinder.NewFinder(haarCascadeFilepath)faces:=finder.Detect(baseImage)for_,face:=rangefaces{//[...]}我从谷歌“绘制矩形”中复制了几段"代码来进行功能检查并确保上面的代码确实有效。有了位置信息,我鼓捣了一个图片加载过渡函数(这个函数更关心错误内容,而不是急着把所有东西都塞进去)。funcloadImage(filestring)image.Image{reader,err:=os.Open(file)iferr!=nil{log.Fatalf("errorloading%s:%s",file,err)}img,_,err:=image.Decode(reader)iferr!=nil{log.Fatalf("errorloading%s:%s",file,err)}returnimg}imagemodification接下来,我的新循环看起来像这样:baseImage:=loadImage(imageFile)chrisFace:=loadImage(chrisFaceFile)bounds:=baseImage.Bounds()finder:=facefinder.NewFinder(haarCascadeFilepath)faces:=finder.Detect(baseImage)//Convertimage.Imagetoamutableimage.ImageRGBAcanvas:=image.NewRGBA(bounds)绘制。Draw(canvas,bounds,baseImage,bounds.Min,draw.Src)for_,face:=rangefaces{draw.Draw(canvas,face,chrisFace,bounds.Min,draw.Src,)}很刺激,测试结果一切顺利.言归正传,其治疗的实际效果远远超出了我的预期。矩形绘制算法真棒!在图像修改方面,我首先必须找到一种摆脱黑色背景的方法。我以前使用过透明背景的PNG,所以我相信它会起作用。经过几次谷歌搜索,我在draw.Draw函数中偶然发现了draw.Over。我将它插入到我正在使用的draw.Src中并且它起作用了!虽然也可以用羽毛笔慢慢画出边缘,但我脑子里有个声音告诉我,差不多就够了。好的,接下来我需要稍微缩小面部图像。可以肯定的是,如果把人脸图片放到一个大小完全一样的矩形里,两者肯定是不匹配的。这只是一个人脸检测工具,不是头部检测工具,这意味着我得到的矩形不适合替换整个头部。我编写了一个快速函数来为image.Rectangle添加特定的边距,最终将其设置为30%。完成后,我开始调整/匹配图像。最终我选择了disintegration/imaging,它有简单的imaging.Fit功能,并提供水平镜像等其他变换操作。我没有很多面部源图像,所以我想这个镜像功能会提供更多图像选项。导入后,我的新循环看起来像这样:.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{//Grabaspecificfaceandresizeitto1/3thewidth//ofthebaseimageface:=imaging.Resize(chrisFaces[0],bounds.Dx()/3,0,imaging.Lanczos,)face_bounds:=face.Bounds()draw.Draw(canvas,bounds,face,//老实说,当我想出这个的时候,我是一对夫妇啤酒,我//不知道它是如何工作的,但它把脸放在//图像的底部,水平居中,脸的下半部分截止边界.ound+Min.Add(image.Pt(face_bounds.Max.X/2,-bounds.Max.Y+int(float64(face_bounds.Max.Y)/1.9),)),draw.Over,)}现在的结果是:我个人对这个方案非常满意。这里所有的工作都准备好了,就等着同事们的反应了。我在一个晚上从概念到原型,没有人知道我为他们准备了什么惊喜。我的经理是迄今为止最活跃的Chrisbot手动配置用户。抱歉,Mat,看起来自动化最终会取代人类。但是这个家伙自己很高兴。不久之后,整个办公室都在向@Chrisbot发送图片。我惊喜地发现它确实通过先绘制最远的面来正确处理面重叠。虽然这纯粹是go-opencv库返回矩形的实际顺序的副作用,但我对结果非常满意。但虽然自动换脸大大增加了克里斯在Slack中的出现次数,但仍然有人认为,人为操纵的结果更具有灵性。诚然,他们的观点确实站得住脚——至少在某些情况下是这样。【翻译稿件,合作网站转载请注明原译者和出处.com】
