1. 程式人生 > >opencv人臉檢測,旋轉處理

opencv人臉檢測,旋轉處理

不能 ont sin 賦值 修改配置 裁剪 net ogg 超過

年會簽到,拍自己的大頭照,有的人可能會拍成橫向的,需要旋轉,用人臉檢測並修正它(圖片)。

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

測試三張不同角度照片,和一張不需旋轉的圖:

技術分享圖片

結果為:

技術分享圖片

過程:

技術分享圖片

參考

  • 手把手教你如何用 OpenCV + Python 實現人臉識別
  • Rotate images (correctly) with OpenCV and Python 旋轉函數優化
  • OpenCV實踐之路——人臉檢測(C++/Python) detectMultiScale 參數說明
  • 使用Python和OpenCV檢測圖像中的物體並將物體裁剪下來 裁剪方式 image[y1:y1+hight, x1:x1+width]
  • OPENCV中的圖像坐標和行列的關系

opencv人臉檢測,旋轉處理