背景
根據(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);

改進(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)出去的部分保留,空白的部分黑色填充。

以下是改進(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);

測(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);
}