2024-03-01 OpenCV QT 圖像旋轉(zhuǎn)后點(diǎn)還原到原圖坐標(biāo)

背景

根據(jù)業(yè)務(wù)相機(jī)獲取到的目標(biāo)圖像要進(jìn)行處理,目標(biāo)在圖像中會(huì)出現(xiàn)角度偏移,需要先將圖像旋轉(zhuǎn)為正方向后做之后的操作。旋轉(zhuǎn)后做后續(xù)操作的結(jié)果有點(diǎn)坐標(biāo)數(shù)據(jù),但是這些結(jié)果數(shù)據(jù)是在旋轉(zhuǎn)后圖像上,需要還原到原圖中。

常規(guī)做法

OpenCV提供了仿射變換旋轉(zhuǎn)函數(shù),定義仿射矩陣,然后旋轉(zhuǎn)圖像。代碼中旋轉(zhuǎn)后顯示了原圖和旋轉(zhuǎn)圖像,從結(jié)果來看,圖像是圍繞中心點(diǎn)旋轉(zhuǎn)了45度,但是并不是我們想要的。這里旋轉(zhuǎn)后圖像大小保持不變,轉(zhuǎn)出去的部分丟掉,空白的部分填充黑色,空白部分填充黑色能接受,但是轉(zhuǎn)出去的部分丟掉不是我們想要的,那我們的處理目標(biāo)要是被丟掉了怎么辦?所以需要對(duì)旋轉(zhuǎn)的方法進(jìn)行改進(jìn)。

cv::Mat src = cv::imread("D:/zx.jpg");
float angle = 45;
double scale = 1.0;
cv::Point2f centerSrc(src.size().width / 2, src.size().height / 2);
//定義仿射矩陣
cv::Mat M = cv::getRotationMatrix2D(centerSrc, angle, scale);
cv::Mat rotate;
//旋轉(zhuǎn)
cv::warpAffine(src, rotate, M, src.size());
cv::imshow("src", src);
cv::imshow("rotate", rotate);
2024-03-01_093500.jpg

改進(jìn)做法

根據(jù)上述做法,我們需要改進(jìn)旋轉(zhuǎn)函數(shù),圖像旋轉(zhuǎn)后要保留圖像所有數(shù)據(jù)。我們預(yù)想的以下狀態(tài),旋轉(zhuǎn)90度時(shí)圖像整個(gè)旋轉(zhuǎn),寬高交換,旋轉(zhuǎn)45度時(shí)圖像轉(zhuǎn)出去的部分保留,空白的部分黑色填充。

2024-03-01_093732.jpg

以下是改進(jìn)后的旋轉(zhuǎn)代碼,這里的旋轉(zhuǎn)與上述旋轉(zhuǎn)的區(qū)別是旋轉(zhuǎn)矩陣使用了旋轉(zhuǎn)圖像的最大外切矩形,也就是圖像旋轉(zhuǎn)其實(shí)沒有變化,只是矩陣和圖像大小發(fā)生變化,這個(gè)變化是為了保留圖像旋轉(zhuǎn)出去的部分,如果我們將這個(gè)旋轉(zhuǎn)圖像按照原圖尺寸進(jìn)行裁剪會(huì)發(fā)現(xiàn)與上述旋轉(zhuǎn)結(jié)果是一樣的,區(qū)別就是通過改變圖像大小對(duì)旋轉(zhuǎn)部分進(jìn)行保留。

還有一個(gè)傳進(jìn)來的角度參數(shù)進(jìn)行了取反,是因?yàn)镺penCV旋轉(zhuǎn),默認(rèn)正數(shù)逆時(shí)針,負(fù)數(shù)順時(shí)針。這本身也沒什么問題,后面你按照其規(guī)則就行,但是筆者的習(xí)慣是順時(shí)針正數(shù),所以在這里取反,你也可以不這樣操作,注意前后業(yè)務(wù)關(guān)聯(lián)就行。

/**
 * 圖像旋轉(zhuǎn)
 * @brief rotate
 * @param src
 * @param angle
 * @return
 */
static cv::Mat rotate(cv::Mat src,  float angle);

cv::Mat U::rotate(cv::Mat src,  float angle)
{
    //旋轉(zhuǎn)默認(rèn)正角度是逆時(shí)針,與我的常用操作相反,所以我在這里取反,不是必要操作
    angle = -angle;
    double scale = 1.0;
    //原圖中心點(diǎn)
    cv::Point2f center(src.size().width / 2.0f, src.size().height / 2.0f);
    //定義旋轉(zhuǎn)矩陣
    cv::Mat rot = cv::getRotationMatrix2D(center, angle, scale);
    //計(jì)算旋轉(zhuǎn)后圖像最大外切矩形
    cv::Rect2f bbox = cv::RotatedRect(cv::Point2f(), src.size(), angle).boundingRect2f();
    rot.at<double>(0, 2) += bbox.width / 2.0f - src.cols / 2.0f;
    rot.at<double>(1, 2) += bbox.height / 2.0f - src.rows / 2.0f;
    cv::Mat result;
    //旋轉(zhuǎn)出結(jié)果
    cv::warpAffine(src, result, rot, bbox.size());
    return result;
}

測(cè)試代碼

cv::Mat src = cv::imread("D:/zx.jpg");
float angle = 45;
cv::Mat rotate  = U::rotate(src,  angle);
cv::Point2f centerSrc(src.size().width / 2, src.size().height / 2);
cv::Point2f centerRotate(rotate.size().width / 2, rotate.size().height / 2);
//定義旋轉(zhuǎn)圖像上幾個(gè)測(cè)試點(diǎn)
cv::Point2f pa1(281, 256);
cv::Point2f pb1(309, 324);
cv::Point2f pc1(277, 407);
cv::Point2f pd1(204, 314);
//定義點(diǎn)繪制在旋轉(zhuǎn)圖上
U::drawCircle(rotate, pa1);
U::drawCircle(rotate, pb1);
U::drawCircle(rotate, pc1);
U::drawCircle(rotate, pd1);
//計(jì)算還原后的點(diǎn)
cv::Point2f pa2 = U::restorePoint(pa1, rotate, src, angle);
cv::Point2f pb2 = U::restorePoint(pb1, rotate, src, angle);
cv::Point2f pc2 = U::restorePoint(pc1, rotate, src, angle);
cv::Point2f pd2 = U::restorePoint(pd1, rotate, src, angle);
//還原點(diǎn)繪制在原圖上
U::drawCircle(src, pa2);
U::drawCircle(src, pb2);
U::drawCircle(src, pc2);
U::drawCircle(src, pd2);
cv::imshow("src", src);
cv::imshow("rotate", rotate);
2024-03-01_094745.jpg

測(cè)試代碼中讀取一個(gè)圖像,并旋轉(zhuǎn)45度得到旋轉(zhuǎn)圖,在旋轉(zhuǎn)圖中定義4個(gè)點(diǎn)并繪制在旋轉(zhuǎn)圖中,計(jì)算旋轉(zhuǎn)圖中的點(diǎn)還原到原圖的坐標(biāo)并繪制在原圖上,從結(jié)果中看出還原點(diǎn)計(jì)算是正確的。代碼中旋轉(zhuǎn)函數(shù)rotate()在上文,還有個(gè)函數(shù)restorePoint(),這個(gè)函數(shù)是計(jì)算一個(gè)點(diǎn)圍繞另一個(gè)點(diǎn)旋轉(zhuǎn)一定角度后的坐標(biāo)。函數(shù)中拿到旋轉(zhuǎn)圖像和原圖的中心點(diǎn),計(jì)算2個(gè)圖像坐標(biāo)點(diǎn)的差值,將旋轉(zhuǎn)點(diǎn)按照固定值差值到原圖中,可能這個(gè)點(diǎn)在原圖中是不存在的,因?yàn)樾D(zhuǎn)圖可能比原圖大。不存在也沒關(guān)系,有這個(gè)數(shù)據(jù)就行,即使不存在也應(yīng)該是在那里。然后將這個(gè)差值點(diǎn)做反角度旋轉(zhuǎn),就是還原的坐標(biāo)。

/**
 * 旋轉(zhuǎn)圖像中的點(diǎn)還原到原圖坐標(biāo)
 * @brief restorePoint
 * @param point 旋轉(zhuǎn)圖像的點(diǎn)
 * @param rotate 旋轉(zhuǎn)圖像
 * @param src 原圖
 * @param angle 旋轉(zhuǎn)角度
 * @return
 */
static cv::Point2f restorePoint(cv::Point2f point, cv::Mat rotate, cv::Mat src, float angle);

cv::Point2f U::restorePoint(cv::Point2f point, cv::Mat rotate, cv::Mat src, float angle)
{
    cv::Point2f centerSrc(src.size().width / 2, src.size().height / 2);
    cv::Point2f centerRotate(rotate.size().width / 2, rotate.size().height / 2);
    //因?yàn)樾D(zhuǎn)圖像和原圖不一樣大,先計(jì)算旋轉(zhuǎn)點(diǎn)與原圖的差值
    cv::Point2f difference(point.x - (centerRotate.x - centerSrc.x), point.y - (centerRotate.y - centerSrc.y));
    //差值點(diǎn)做反角度旋轉(zhuǎn)
    cv::Point2f result = U::rotatePoint(centerSrc, difference, -angle);
    return result;
}

此函數(shù)中又用到了rotatePoint()函數(shù),計(jì)算旋轉(zhuǎn)點(diǎn)坐標(biāo)

/**
 * 一個(gè)點(diǎn)繞另中心點(diǎn)旋轉(zhuǎn)一定角度后的坐標(biāo)
 * @brief rotatePoint
 * @param center 中心點(diǎn)
 * @param rotater 旋轉(zhuǎn)點(diǎn)
 * @param angle 角度,順時(shí)針
 * @return
 */
static cv::Point2f rotatePoint(cv::Point2f center, cv::Point2f rotater, float angle);

cv::Point2f U::rotatePoint(cv::Point2f center, cv::Point2f rotater, float angle)
{
    //角度轉(zhuǎn)弧度
    float radian = angle * CV_PI / 180;
    //三角函數(shù)計(jì)算坐標(biāo)
    float x = (rotater.x - center.x) * cos(radian) - (rotater.y - center.y) * sin(radian) + center.x ;
    float y = (rotater.x - center.x) * sin(radian) + (rotater.y - center.y) * cos(radian) + center.y ;
    return cv::Point2f(x, y);
}

還有一個(gè)drawCircle()函數(shù),就是在圖像中繪制圓(點(diǎn))的函數(shù),將圓的半徑設(shè)置足夠小,這里是2,畫筆設(shè)置足夠粗,這里是2,繪制的圓就是一個(gè)實(shí)心圓,也就等于一個(gè)點(diǎn)。

/**
 * 繪制點(diǎn)
 * @brief drawCircle
 * @param canvas
 * @param point
 */
static void drawCircle(cv::Mat canvas, cv::Point2f point);

void U::drawCircle(cv::Mat canvas, cv::Point2f point)
{
    cv::circle(canvas, point, 2, cv::Vec3b(0, 255, 0), 2);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容