کالیبراسیون دوربین و بازسازی سه‌بعدی

تخمین وضعیت قرارگیری (Pose Estimation)

۴ دقیقه مطالعهOpenCV 5.xبه‌روزرسانی:

هدف#

در این بخش،

  • یاد خواهیم گرفت که از ماژول سه‌بعدی برای ایجاد برخی جلوه‌های سه‌بعدی در تصاویر استفاده کنیم.

مفاهیم پایه#

این یک بخش کوتاه خواهد بود. در طول جلسه گذشته در مورد کالیبراسیون دوربین، ماتریس دوربین، ضرایب اعوجاج و غیره را پیدا کردید. با داشتن تصویر یک الگو، می‌توانیم از اطلاعات فوق برای محاسبه وضعیت قرارگیری (Pose) آن استفاده کنیم، یعنی نحوه قرار گرفتن شیء در فضا، مانند نحوه چرخش، نحوه جابجایی آن و غیره. برای یک شیء صفحه‌ای، می‌توانیم Z=0 را فرض کنیم، به طوری که مسئله اکنون به این تبدیل می‌شود که دوربین چگونه در فضا قرار گرفته است تا تصویر الگوی ما را ببیند. بنابراین، اگر بدانیم شیء چگونه در فضا قرار دارد، می‌توانیم نمودارهای دوبعدی در آن رسم کنیم تا جلوه سه‌بعدی را شبیه‌سازی نماییم. بیایید ببینیم چگونه این کار را انجام دهیم.

مسئله ما این است: می‌خواهیم دستگاه مختصات سه‌بعدی خود (محورهای X، Y، Z) را روی اولین گوشه صفحه شطرنج خود رسم کنیم. محور X به رنگ آبی، محور Y به رنگ سبز و محور Z به رنگ قرمز. بنابراین در عمل، محور Z باید عمود بر صفحه شطرنج احساس شود.

ابتدا بیایید ماتریس دوربین و ضرایب اعوجاج را از نتیجه کالیبراسیون قبلی بارگذاری کنیم.

import numpy as np
import cv2 as cv
import glob

# Load previously saved data
with np.load('B.npz') as X:
    mtx, dist, _, _ = [X[i] for i in ('mtx','dist','rvecs','tvecs')]

اکنون تابعی به نام draw ایجاد می‌کنیم که گوشه‌های صفحه شطرنج (به دست آمده با استفاده از ()cv.findChessboardCorners) و نقاط محور (axis points) را برای رسم محور سه‌بعدی دریافت می‌کند.

def draw(img, corners, imgpts):
    corner = tuple(corners[0].ravel().astype("int32"))
    imgpts = imgpts.astype("int32")
    img = cv.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5)
    img = cv.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5)
    img = cv.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5)
    return img

سپس همانند مورد قبل، معیار خاتمه، نقاط شیء (نقاط سه‌بعدی گوشه‌ها در صفحه شطرنج) و نقاط محور را ایجاد می‌کنیم. نقاط محور نقاطی در فضای سه‌بعدی برای رسم محور هستند. ما محوری به طول ۳ رسم می‌کنیم (واحدها بر حسب اندازه مربع شطرنج خواهند بود زیرا بر اساس آن اندازه کالیبره کرده‌ایم). بنابراین محور X ما از (0,0,0) به (3,0,0) کشیده می‌شود، برای محور Y نیز همین‌طور است. برای محور Z، از (0,0,0) به (0,0,-3) رسم می‌شود. علامت منفی نشان می‌دهد که به سمت دوربین رسم شده است.

criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
cols = 6
rows = 7
objp = np.zeros((cols*rows,3), np.float32)
objp[:,:2] = np.mgrid[0:cols,0:rows].T.reshape(-1,2)

axis = np.float32([[3,0,0], [0,3,0], [0,0,-3]]).reshape(-1,3)

اکنون طبق معمول هر تصویر را بارگذاری می‌کنیم. به دنبال شبکه 6x7 می‌گردیم. در صورت یافتن، آن را با پیکسل‌های زیرگوشه اصلاح می‌کنیم. سپس برای محاسبه دوران و انتقال، از تابع ()cv.solvePnPRansac استفاده می‌کنیم. هنگامی که آن ماتریس‌های تبدیل را به دست آوردیم، از آن‌ها برای پرتاب و نگاشت نقاط محور خود به صفحه تصویر استفاده می‌کنیم. به بیان ساده، ما نقاط روی صفحه تصویر متناظر با هر یک از نقاط (3,0,0)، (0,3,0) و (0,0,3) در فضای سه‌بعدی را پیدا می‌کنیم. هنگامی که آن‌ها را به دست آوردیم، با استفاده از تابع ()generateImage از اولین گوشه به هر یک از این نقاط خطوطی رسم می‌کنیم. تمام شد !!!

for fname in glob.glob('left*.jpg'):
    img = cv.imread(fname)
    gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
    ret, corners = cv.findChessboardCorners(gray, (cols, rows), None)

    if ret == True:
        corners2 = cv.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)

        # Find the rotation and translation vectors.
        ret,rvecs, tvecs = cv.solvePnP(objp, corners2, mtx, dist)

        # project 3D points to image plane
        imgpts, jac = cv.projectPoints(axis, rvecs, tvecs, mtx, dist)

        img = draw(img,corners2,imgpts)
        cv.imshow('img',img)
        k = cv.waitKey(0) & 0xFF
        if k == ord('s'):
            cv.imwrite(fname[:6]+'.png', img)

cv.destroyAllWindows()

برخی از نتایج را در زیر مشاهده کنید. دقت کنید که طول هر محور به اندازه ۳ مربع است:

تخمین وضعیت قرارگیری (Pose Estimation) — تصویر ۱

رندر کردن یک مکعب#

اگر می‌خواهید یک مکعب رسم کنید، تابع ()generateImage و نقاط محور را مطابق زیر اصلاح کنید.

تابع ()generateImage اصلاح‌شده:

def draw(img, corners, imgpts):
    imgpts = np.int32(imgpts).reshape(-1,2)

    # draw ground floor in green
    img = cv.drawContours(img, [imgpts[:4]],-1,(0,255,0),-3)

    # draw pillars in blue color
    for i,j in zip(range(4),range(4,8)):
        img = cv.line(img, tuple(imgpts[i]), tuple(imgpts[j]),(255),3)

    # draw top layer in red color
    img = cv.drawContours(img, [imgpts[4:]],-1,(0,0,255),3)

    return img

نقاط محور اصلاح‌شده. آن‌ها ۸ گوشه یک مکعب در فضای سه‌بعدی هستند:

axis = np.float32([[0,0,0], [0,3,0], [3,3,0], [3,0,0],
                   [0,0,-3],[0,3,-3],[3,3,-3],[3,0,-3] ])

و به نتیجه زیر نگاه کنید:

تخمین وضعیت قرارگیری (Pose Estimation) — تصویر ۲

اگر به گرافیک، واقعیت افزوده (Augmented Reality) و غیره علاقه‌مندید، می‌توانید از OpenGL برای رندر کردن اشکال پیچیده‌تر استفاده کنید.