学完课程后的几个疑问:
- 如果背景不是黑白的纯色,如何处理?——尝试用像素值进行阈值化,尝试看能否把头饰抠出来。但是这种方式不能保证有效,如果无效,则建议用ps先把背景处理掉。
- 如何处理非常复杂的背景?——同上,利用ps
- 是否可以针对图像的某个通道进行二值化处理?——可以,但是只适用于部分情况。见后面的第一小节(针对单通道的二值化处理)
- 对眼镜旋转的做法,是否和对人脸旋转的做法一致?——待尝试
其他的脑洞大开
0、背景
我在逛淘宝的时候看到了一个可爱的鱼形状的头饰,然后就想把它写到课后作业中。原图是这样的
1、遇到问题&尝试通过针对“单通道的二值化处理”解决问题
由于背景过于复杂且前景背景区分度不够高,所以没办法用作业中的方法将它抠出来。
老师和同学都建议我用ps把背景去掉之后再试试,但是老师的“背景也有黄色,那可以直接PS试试”这句话让我动了基于某个对比度高的通道进行处理的念头。
以上分别是蓝、绿、红色通道的可视化图像。经对比我发现红色通道图像的前背景区分度最高,所以基于它进行了二值化处理。
处理后得到的图像如下(和灰度化再做二值化的图像进行比较),总之基本是把完整的图像拿出来了,但是这个上面有手以及其他的背景。
所以还是去淘宝重新找了张美工去掉背景的图来写作业,毕竟专业的事情确实需要专业的人来做。
重新找到的图背景是白色的,方便抠图(但是背景色和大白老师的样图相反,所以后面的步骤可能需要调整一下):
2、开始作业
2.1 缕清思路
① 本次是一个鱼头饰,因此不需要知道五官的位置。逻辑和兔耳朵很像,可以基于那个代码进行改写。
② 我希望人脸出现的位置如红框所示,所以我需要找到这个线索去计算这个鱼头饰需要在人脸原图上的裁剪范围。(和兔耳不同的是 ,这里涉及到人脸的宽和高)
③ 大白老师针对头饰越界的操作是压缩,但是我选的这个鱼头饰如果压缩了就不好匹配人脸进行展示(压缩后,它可能会遮挡五官)。
④ 头饰的背景颜色会影响代码的逻辑。
2.2 代码呈现
import cv2 # 图像处理的opencv库import dlib # 加载dlib算法模块import argparse # 加载解析模块def fish_face(opt):detector = dlib.get_frontal_face_detector()img = cv2.imread(opt.image_path)img_fish = cv2.imread(opt.fish_path)faceRects = detector(img, 0)for box_info in faceRects:x0, y0, width_face, height_face = box_info.left(), box_info.top(), box_info.right() - box_info.left(), box_info.bottom() - box_info.top()fish_h = int(height_face * 2.8) # 1.81fish_w = int(width_face * 2.63)imgComposeSize = cv2.resize(img_fish, (fish_w, fish_h), interpolation=cv2.INTER_NEAREST)# 如果配饰超出原图像需要做截断处理top = int(y0 - height_face * 1.2) # 0.55bottom = top + fish_hleft = int(x0 - width_face * 0.69)right = left + fish_wprint(left, bottom, left, right)if bottom > img.shape[0]:imgComposeSize = imgComposeSize[:-(bottom - img.shape[0]), :]bottom = img.shape[0]if right > img.shape[1]:imgComposeSize = imgComposeSize[:, :-(right - img.shape[1])]right = img.shape[0]if top < 0:imgComposeSize = imgComposeSize[-top:, :]top = 0if left < 0:imgComposeSize = imgComposeSize[:, -left:]left = 0small_img_fish = img[top:bottom, left:right]small_img_fish_gray = cv2.cvtColor(imgComposeSize, cv2.COLOR_RGB2GRAY)ret, mask_fish = cv2.threshold(small_img_fish_gray, 240, 255, cv2.THRESH_BINARY)mask_fish_inv = cv2.bitwise_not(mask_fish)img1_bg = cv2.bitwise_and(small_img_fish, small_img_fish, mask=mask_fish)img2_fg = cv2.bitwise_and(imgComposeSize, imgComposeSize, mask=mask_fish_inv)dst = cv2.add(img1_bg, img2_fg)img[top:bottom, left:right] = dstimg = cv2.resize(img, (500, 600))cv2.imshow("image", img)cv2.waitKey(0)if __name__ == '__main__':parser = argparse.ArgumentParser()parser.add_argument('--image_path', default="1.jpg", help='path of read image')parser.add_argument('--fish_path', default="fish2.jpg", help='path of fish image')opt = parser.parse_args()fish_face(opt)
2.3 详细解释
关于人脸定位这类的常规操作(7~10行,39~49行)这里不讲,这里主要解释一下上述代码中的一些特有操作(和之前课程中不一样的内容)。
2.3.1 计算鱼头饰需要缩放的大小
由于大多数人脸的宽高不一样,如果想让鱼头饰和人脸匹配的话就必须要分别针对人脸的宽高进行缩放。
- 如何缩放?大约将2.1中红色的框框缩放为人脸大小即可。
- 如何让这个框框和人脸大小相同?通过计算,发现最好将鱼头饰的高放缩为人脸高的2.8倍;宽放缩为人脸宽的2.63倍。(这里主要是代码的13/14行)
2.3.2 关于计算在原图中需要抠图的坐标信息
鱼头饰缩放完了,如何将人脸放在上述的红框框中呢?这里就需要计算鱼头饰的左上角和人脸左上角的位置关系了。
- 如何计算?计算框框的顶部离鱼头饰图像的顶部距离,并将其比上人脸高度(这里计算出来是1.2倍);计算框框左边缘与鱼头饰图像的左边缘的距离,并将其比上人脸宽度(这里计算出来是0.69)(这里主要体现在代码的18/20行)
2.3.3 头饰越界怎么办?
经上面的分析,鱼头饰需要和人脸成比例才好看,因此不能因为担心图像越界而进行随意的压缩。
- 那么越界的部分怎么处理呢?将鱼头饰应该出现的左边计算出来,如果坐标小于0或者大于原图,则对头饰图像进行裁剪。(详见代码18~37行)
2.4 遇到的问题
在代码实现过程中遇到了参数错误的情况,定位发现计算的坐标忘记转为int类型就直接传给了cv2的函数。代码报错如下,其实改一下就可以了:
2.5 效果展示


