图像文本选择通常是由于扫描仪在进行图像扫描时,未能正常按照其行列水平垂直扫描引起的现象。在现实场景中,我们需要对旋转文本进行几何矫正。这次利用傅立叶变换中时域与频域的变换关系,实现选择文本图像矫正。
旋转文本图像的明显特征就是存在分行间隔,当文本图像旋转时,其频域中的频谱也会随之旋转。根据这一特征来计算文本图像的DFT变换,DFT变换的结果是低频位于边界四角,高频集中在中心区域,将低频与高频互换,实现中心的移动,进而可看到文本图像的频谱有明显的倾斜直线,再通过计算图像直线的倾斜角度,利用仿射变换就可以完成选择文本的图像矫正。旋转文本图像矫正的具体步骤如下:
<1>图像DFT尺寸转换。快速傅立叶变换是基于图像尺寸2、3或5倍数完成的,因此对于输入源图像,首先应将其变成DFTSize,Opencv中提供了函数getOptimalDFTSize()来实现尺寸转换。
PS:copyMakeborder函数用来复制图像,超过边界区域填充为0。
<2>DFT变换。该步骤中首先将处理的输入图像转换为实部与虚部,接着通道合并,通过DFT变换得到实、虚部两通道,然后计算实部与虚部的幅度值,并完成数据归一化映射。
<3>频域中心移动,傅立叶变换得到的低频部分在边缘角中,高频部分位于图像中心,对于倾斜文本图像,需要将低频部分与高频部分互换中心。通常采用的方法是将图像等分成4分,然后将区域进行互调,完成中心移动。
<4>倾斜角检测:经过频域中心移动后,只需要检测出图像中直线的倾斜角就可以对旋转文本进行校正。计算直线倾斜角有很多方法,这里介绍利用霍夫变换线检测方法进行直线倾斜角的计算,首先将傅立叶变换后的频谱图进行固定二值化处理,这里阈值的选择和场景有很大关系,读者可根据实际应用场景进行合理调整;然后根据霍夫变换检测直线的步骤来完成中的直线检测,计算得到图像直线的角度;最后判断图像中检测到的线角度是否符合要求,对符合要求的线角度进行图像的角度转换。
<5>仿射变换矫正
对得到的线角度计算旋转矩阵,利用仿射变换完成旋转文本矫正。
给个源代码:
cv::Mat check(cv::Mat check_Mat)
{
cv::Mat srcImage(check_Mat);
cv::Mat dstImage;
cv::cvtColor(srcImage, srcImage,cv::COLOR_RGB2GRAY);
/******************图像DFT尺寸转换****************************/
const int nRows = srcImage.rows;
const int nCols = srcImage.cols;
int cRows = cv::getOptimalDFTSize(nRows);//图片尺寸转换,获取DFT尺寸
int cCols = cv::getOptimalDFTSize(nCols);
cv::Mat sizeConvMat;
cv::copyMakeBorder(srcImage, sizeConvMat, 0, cRows - nRows, 0, cCols - nCols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
/***********************图像DFT变换*************************/
cv::Mat groupMats[] = { cv::Mat_(sizeConvMat),cv::Mat::zeros(sizeConvMat.size(),CV_32F)};
cv::Mat mergeMat;
cv::merge(groupMats, 2, mergeMat);//通道合并
cv::dft(mergeMat, mergeMat);//DFT变换
cv::split(mergeMat, groupMats);//分离通道
cv::magnitude(groupMats[0], groupMats[1], groupMats[0]);//计算幅度
cv::Mat magnitudeMat = groupMats[0].clone();
magnitudeMat += cv::Scalar::all(1);//归一化操作,赋值加1
cv::log(magnitudeMat, magnitudeMat);//对数变换
cv::normalize(magnitudeMat, magnitudeMat, 0, 1, CV_MINMAX);//归一化
magnitudeMat.convertTo(magnitudeMat, CV_8UC1, 255, 0);//图像类型转换
/*********************频域中心移动*******************/
int cx = magnitudeMat.cols / 2;
int cy = magnitudeMat.rows / 2;
cv::Mat tmp;
cv::Mat q0(magnitudeMat, cv::Rect(0, 0, cx, cy));
cv::Mat q1(magnitudeMat, cv::Rect(cx, 0, cx, cy));
cv::Mat q2(magnitudeMat, cv::Rect(0, cy, cx, cy));
cv::Mat q3(magnitudeMat, cv::Rect(cx, cy, cx, cy));
q0.copyTo(tmp);//交换象限
q3.copyTo(q0);
tmp.copyTo(q3);
q1.copyTo(tmp);
q2.copyTo(q1);
tmp.copyTo(q2);
/*******************倾斜角检测*********************/
cv::Mat binaryMagnMat;
cv::threshold(magnitudeMat, binaryMagnMat, 133, 255, CV_THRESH_BINARY);
std::vector lines;
binaryMagnMat.convertTo(binaryMagnMat, CV_8UC1, 255, 0);
cv::HoughLines(binaryMagnMat, lines, 1, CV_PI / 180, 100, 0, 0);
cv::Mat houghMat(binaryMagnMat.size(), CV_8UC3);
float theta = 0;
for (size_t i = 0; i < lines.size(); i++)//检测线角度判断
{
float thetaTemp = lines[i][1] * 180 / CV_PI;
if (thetaTemp > 0 && thetaTemp < 90)
{
theta = thetaTemp;
break;
}
}
float angelT = nRows * tan(theta / 180 * CV_PI);//角度转换
theta = atan(angelT) * 180 / CV_PI;
/*******************仿射变换校正*******************/
cv::Point2f centerPoint = cv::Point2f(nCols / 2 , nRows / 2);//取图像中心
double Scale = 1;
cv::Mat warpMat = cv::getRotationMatrix2D(centerPoint,theta,Scale);//计算旋转矩阵
cv::Mat resultImage(srcImage.size(), srcImage.type());
cv::warpAffine(srcImage, resultImage, warpMat, resultImage.size());//仿射变换
dstImage = resultImage;
return dstImage;
}