linuxsir首页 LinuxSir.Org | Linux、BSD、Solaris、Unix | 开源传万世,因有我参与欢迎您!
网站首页 | 设为首页 | 加入收藏
您所在的位置:主页 > Linux基础建设 >

OpenCV实现人脸检测及旋转处理

时间:2019-05-13  来源:未知  作者:admin666

年会签到,拍自己的大头照,有的人可能会拍成横向的,需要旋转,用人脸检测并修正它(图片)。

1. 无脑检测步骤为:

1. opencv 读取图片,灰度转换
2. 使用CascadeClassifier()通过训练数据训练分类器
3. detectMultiScale()检测人脸

训练数据集下最基本的人脸haarcascade_frontalface_default.xml

2. 开始检测

1) 斜脸检测失败
用了一张逃避可耻但有用剧照,不知是gakki脸斜还是不清晰的缘故,face_cascade.detectMultiScale无结果。

网上找了一张人脸图发现可以有结果[[436 142 604 604]]

2) 旋转图片,被裁剪
测试图片旋转90°,180°,270°时,发现有个问题。下面为旋转180°,90°的效果:90°(以及270°)的图片大小没变,导致横向过长,纵向太窄,图片被裁减。


粗暴的尝试,直接用:

if angle in (90,270):
 w,h = h,w

转换宽高后,输出90°为:

出现了奇怪的边框。

再来看一个45°的输出,也同样被裁剪

直到我找到了 Rotate images (correctly) with OpenCV and Python 这篇文章,写的太好了,用的动图也恰到好处。

rotating oblong pills using the OpenCV’s standard cv2.getRotationMatrix2D and cv2.warpAffine functions caused me some problems that weren’t immediately obvious.

作者检测原型药片旋转没关系,用椭圆的药片就有被裁剪的问题,我上面的那些就是被裁剪了。

使用作者给的函数实验:

def rotate_bound(image, angle):
    # grab the dimensions of the image and then determine the
    # center
    (h, w) = image.shape[:2]
    (cX, cY) = (w // 2, h // 2)
 
    # grab the rotation matrix (applying the negative of the
    # angle to rotate clockwise), then grab the sine and cosine
    # (i.e., the rotation components of the matrix)
    M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
    cos = np.abs(M[0, 0])
    sin = np.abs(M[0, 1])
 
    # compute the new bounding dimensions of the image
    nW = int((h * sin) + (w * cos))
    nH = int((h * cos) + (w * sin))
 
    # adjust the rotation matrix to take into account translation
    M[0, 2] += (nW / 2) - cX
    M[1, 2] += (nH / 2) - cY
 
    # perform the actual rotation and return the image
    return cv2.warpAffine(image, M, (nW, nH))

-45°旋转

-90°旋转

完成。

4) 自动旋转图片

作者旋转的很好看,所以也模仿了一把,将自己展示图片的函数改为:「 获取图片宽高,然后动态改变宽高显示图片,ms参数为停留的毫秒数(宽高除以3是因为图片太大了,展示不好看)」

def show(img, ms=0):
 """ 显示 """
 cv2.imshow('show', img)
 h, w = img.shape[:2]
 cv2.resizeWindow("show", w//3, h//3)
 cv2.waitKey(ms)

用自己之前不正确的旋转函数rotate_img_old测试,有裁剪

for angle in range(0,360,10):
 show(rotate_img_bad(img_bgr, angle), 200)

用正确的测试:裴斐科特

for angle in range(0,360,10):
 show(rotate_img(img_bgr, angle), 200)

3) 再试gakki图

range(0,360,45)以45°为步长检测,发现-315°(即45°)有结果:

检测到两处人脸:修改配置 每次缩减比例:scaleFactor=1.2,检测多次: minNeighbors=10

# 探测图片中的人脸
faces = face_cascade.detectMultiScale(
 img_gray,
 scaleFactor=1.2,  # 每次缩减比例
 minNeighbors=10,  # 检测多次
 flags=cv2.CASCADE_SCALE_IMAGE,
 minSize=(50,50)
)

之后检测,只有一处。

4. 生成头像

最后,想截取有效的最大头像,

90°旋转裁剪

在这次真实的年会场合实际使用的时候,不能转45°去裁剪显示,斜着头拍照的可不想转成正经的证件照,

所以只用转90°然后裁剪其中最大正方形,取高和宽中小的一个。较简单,蓝色为实际裁剪区域。

斜的图则不可这样,会裁剪黑色的区域,(避免脸在图中过小,取了**检测到的脸宽*2.5长度**)

代码段

if angle%90 == 0:
    # 90° 裁剪图片
    logging.debug("90° 裁剪图片")
    logging.debug(f"{ix}, {iy}, {x}, {y}, {w}, {h}")

    length2 = min(ix, iy)

    # 如果人脸太小,放大区域但又不超过图片长度
    length = int(w*2.5)
    length = min(length2, length)
    logging.debug(f"length: {length2} {length}")

    ow = length-w
    ow1 = ow//2
    oh = length-h
    oh1 = oh//2

    y1, y2 = y-oh1, y+h+oh1
    x1, x2 = x-ow1, x+w+ow1

    # 检测图片溢出
    logging.debug(f"{y1}, {y2}, {x1}, {x2}")
    if y1 < 0:
    logging.debug('裁剪:1 顶部溢出')
    y1 = 0
    y2 = length
    if y2 > iy:
    logging.debug('裁剪:2 底部溢出')
    y2 = iy
    y1 = iy-length
    if x1 < 0:
    logging.debug('裁剪:3 左侧溢出')
    x1 = 0
    x2 = length
    if x2 > ix:
    logging.debug('裁剪:4 右侧溢出')
    x2 = ix
    x1 = ix-length

    # 裁剪标记
    cv2.rectangle(img, (x1, y1), (x2, y2), (255, 0, 0), 2)

斜矩形裁剪探索

那么gakki那张斜的如何裁剪呢,斜矩形之外有黑色背景。举例:应该取到绿色矩形的扩大版,但又没有取到黑色背景。比如这样的蓝色区域

转换为数学方法为:todo, 画在本子上的,最后再补

编写数学函数:

else:
    # 非90°裁剪图片
    logging.debug(f"{angle}°裁剪图片")
    # 1. 求A点坐标
    origin_h, origin_w = self.img_bgr.shape[:2]

    # 旋转角度取的ab ad边不同
    if angle%180<90:
        ab = origin_w
        ad = origin_h
    else:
        ab = origin_h
        ad = origin_w
    op = ix
    ap = math.cos(math.radians(angle)) * ab
    oa = op-ap
    A = Point(oa, 0)
    logging.debug(f"ab={ab}, ad={ad}, op={op}, ap={ap}, oa={oa}, {A}")
    # 2. 人脸中心Z坐标
    face_rect = Rectangle(Point(x, y), w, h)
    z = face_rect.center_p

    logging.debug(f"{face_rect} center point is {z}")
    # 3. Z到AB、AD距离
    k = math.tan(math.radians(angle)) # tan(α)
    k2 = -1/k # 垂直
    logging.info(f"k1 = {k}, k2 = {k2}")
    z_ab_len = abs(k*z.x-z.y-oa*k)/math.sqrt(k**2+1)
    z_ad_len = abs(k2*z.x-z.y-oa*k2)/math.sqrt(k2**2+1)
    logging.debug(f"z-ab len is {z_ab_len}, z-ad len is {z_ad_len}")
    # 4. 距离四边最小距离
    h1 = z_ab_len
    h2 = z_ad_len
    h3 = ad-h1
    h4 = ab-h2
    min_len = min(h1, h2, h3, h4)
    logging.debug(f"face around len is {h1} {h2} {h3} {h4}, min:{int(min_len)}")

    # 5. 圆形标注
    #cv2.line(img, z.r_tuple(), (50, 100), (0,255,0))
    for r in (h1, h2, h3, h4):
        r = int(r)
        if int(min_len) == r:
            cv2.circle(img, z.r_tuple(), r, (255, 0, 0), 3)
        else:
            cv2.circle(img, z.r_tuple(), r, (0, 0, 255), 2)

来测试一波,最后的圆形标注只是为了辅助验证。

其实是有问题的,看效果,最小的蓝色圆形标注位置不对,超出原图片了,而且观察其他三个圆也不在图片边上。

if angle%180<90:
    ab = origin_w
    ad = origin_h
else:
    ab = origin_h
    ad = origin_w

这块处理不同角度下取的矩形ABCD边不同,数学角度看是没有错的,但是Opencv坐标圆心在左上角,所以得将数学图形画为:todo

上面赋值代码只需改为

if angle%180<90:
    ab = origin_h
    ad = origin_w
else:
    ab = origin_w
    ad = origin_h

蓝色圈已经是我们要求的矩形头像外接圆了,其他三个圆输出也是贴紧各自的边框,完美。

在蓝圈中画黑色正方形:

是想要的效果,最后裁剪即可。

多角度修复

测试初始角度为横向或者侧向时,需修复角度:

# 最后取的图片和角度无关,只取锐角
angle=angle%90

测试三张不同角度照片,和一张不需旋转的图:

结果为:

过程:

Linux公社的RSS地址:https://www.linuxidc.com/rssFeed.aspx

友情链接
  • Mozilla发布Firefox 67.0.4,修复沙箱逃逸漏洞
  • 蚂蚁金服正式成为CNCF云原生计算基金会黄金会员
  • Firefox 68将采用Microsoft BITS安装更新
  • OpenSSH增加对存储在RAM中的私钥的保护
  • 谷歌想实现自己的curl,为什么?
  • Raspberry Pi 4发布:更快的CPU、更大的内存
  • Firefox的UA将移除CPU架构信息
  • Ubuntu放弃支持32位应用程序实属乌龙,Steam会否重回Ubuntu怀抱
  • Qt 5.13稳定版发布:引入glTF 2.0、改进Wayland以及支持Lottie动
  • 红帽企业Linux 7现已内置Redis 5最新版
  • Slack进入微软内部禁用服务清单,GitHub也在其列?
  • 安全的全新编程语言V发布首个可用版本
  • Windows Terminal已上架,快尝鲜
  • 阿里巴巴微服务开源生态报告No.1
  • 面世两年,Google地球将支持所有基于Chromium的浏览器
  • 推进企业容器化持续创新,Rancher ECIC千人盛典完美收官
  • CentOS 8.0最新构建状态公布,或于数周后发布
  • Debian移植RISC
  • 微软拆分操作系统的计划初现雏形
  • Oracle发布基于VS Code的开发者工具,轻松使用Oracle数据库
  • Ubuntu 19.10停止支持32位的x86架构
  • 微软为Windows Terminal推出全新logo
  • 联想ThinkPad P系列笔记本预装Ubuntu系统
  • 微软发布适用于Win7/8的Microsoft Edge预览版
  • 启智平台发布联邦学习开源数据协作项目OpenI纵横
  • 经过六个多月的延迟,微软终于推出Hyper
  • ZFS On Linux 0.8.1 发布,Python可移植性工作
  • DragonFly BSD 5.6.0 发布,HAMMER2状态良好
  • Linux Kernel 5.2
  • CentOS 8.0 看起来还需要几周的时间
  • 百度网盘Linux版正式发布
  • PCIe 6.0宣布:带宽翻倍 狂飙至256GB/s
  • PHP 7.4 Alpha 发布,FFI扩展,预加载Opcache以获得更好的性能
  • Canonical将在未来的Ubuntu版本中放弃对32位架构的支持
  • Scala 2.13 发布,改进的编译器性能
  • 微软的GitHub收购了Pull Panda,并且使所有订阅完全免费
  • Windows Subsystem for Linux 2 (WSL 2)现在适用于Windows 10用
  • Debian 10 “Buster”的RISC
  • MariaDB宣布发布MariaDB Enterprise Server 10.4
  • DXVK 1.2.2 发布,带来微小的CPU开销优化
  • DragonFlyBSD 5.6 RC1 发布,VM优化,默认为HAMMER2
  • PrimeNG 8.0.0 发布,支持Angular 8,FocusTrap等
  • GIMP 2.10.12 发布,一些有用的改进
  • 清华大学Anaconda 镜像服务即将恢复
  • Debian GNU/Linux 10 “Buster” 操作系统将于2019年7月6日发布
  • 时时彩论坛
  • 五星体育斯诺克
  • 北单比分直播
  • 河北11选5走势图
  • 福建体彩36选7开奖结果
  • 九龙图库下载