创建二值图
- 对于一张黑白纸张图片,想要提取图片中的文字部分并创建二值图有几种常见的方法:使用固定阈值、使用自适应阈值等等。下面将依次介绍各类方法的优劣。
1.使用固定阈值cv::threshold
cv::Mat image = cv::imread("book.jpg", 0);
//图像逆时针旋转90°(先转置,在水平翻转)
cv::transpose(image, image);
cv::flip(image, image, 0);
//显示最初图像
cv::namedWindow("Original Image");
cv::imshow("Original Image", image);
//固定阈值70
cv::Mat binaryFixed;
cv::threshold(image, binaryFixed, 70, 255, cv::THRESH_BINARY);
cv::namedWindow("Fixed Threshold");
cv::imshow("Fixed Threshold", binaryFixed);
效果一般,无论选用什么阈值,图像都会丢失一部分文本,因此我们引入自适应阈值。
2.(推荐)使用自适应阈值cv::adaptiveThreshold
cv::Mat binaryAdaptive;
int blockSize = 21; //邻域的尺寸
int threshold = 10; //像素与邻域的平均值进行比较
//自适应阈值化函数
cv::adaptiveThreshold(image, //源图像
binaryAdaptive, //输出二值图像
255, //输出的最大值
cv::ADAPTIVE_THRESH_MEAN_C, //方法(局部平均值)
cv::THRESH_BINARY, //阈值类型
blockSize, //邻域大小
threshold); //使用的阈值
cv::namedWindow("Adaptive Threshold");
cv::imshow("Adaptive Threshold", binaryAdaptive);
效果很好。本例阈值化方法选用局部平均值,在下面的补充中会介绍高斯加权累计值。
实测消耗时间0.0063683秒。
3.用积分图像编写自适应阈值
cv::Mat binary = image.clone();
int nl = binary.rows; //行数
int nc = binary.cols; //每行的元素数
//计算积分图像
cv::Mat iimage;
cv::integral(image, iimage, CV_32S);
//逐行
int halfSize = blockSize / 2;
for (int j = halfSize; j < nl - halfSize - 1; j++) {
//得到第j行的首地址
uchar* data = binary.ptr<uchar>(j);
//得到积分图像上方j - halfSize行的首地址
int* idata1 = iimage.ptr<int>(j - halfSize);
//得到积分图像下方j + halfSize + 1行的首地址
int* idata2 = iimage.ptr<int>(j + halfSize + 1);
//每行的所有像素
for (int i = halfSize; i < nc - halfSize - 1; i++) {
//计算累加值
int sum = (idata2[i + halfSize + 1] - idata2[i - halfSize] -
idata1[i + halfSize + 1] + idata1[i - halfSize]) / (blockSize * blockSize);
//应用自适应阈值
if (data[i] < (sum - threshold))
data[i] = 0;
else
data[i] = 255;
}
}
//添加白边
for (int j = 0; j < halfSize; j++) {
uchar* data = binary.ptr<uchar>(j);
for (int i = 0; i < binary.cols; i++) {
data[i] = 255;
}
}
for (int j = binary.rows - halfSize - 1; j < binary.rows; j++) {
uchar* data = binary.ptr<uchar>(j);
for (int i = 0; i < binary.cols; i++) {
data[i] = 255;
}
}
for (int j = halfSize; j < nl - halfSize - 1; j++) {
uchar* data = binary.ptr<uchar>(j);
for (int i = 0; i < halfSize; i++) {
data[i] = 255;
}
for (int i = binary.cols - halfSize - 1; i < binary.cols; i++) {
data[i] = 255;
}
}
cv::namedWindow("Adaptive Threshold (integral)");
cv::imshow("Adaptive Threshold (integral)", binary);
由于算法没有包括白边修理,最后手动添加了白边。效果与方法2几乎一模一样,就是通过计算邻域平均值和阈值来比较。
实测消耗时间0.001875秒。
4.利用图像运算符编写自适应阈值化
cv::Mat filtered;
cv::Mat binaryFiltered;
//cv::boxFilter计算矩形区域内像素的平均值
cv::boxFilter(image, filtered, CV_8U, cv::Size(blockSize, blockSize));
//检查像素是否大于(mean + threshold)
binaryFiltered = image >= (filtered - threshold);
cv::namedWindow("Adaptive Threshold (filtered)");
cv::imshow("Adaptive Threshold (filtered)", binaryFiltered);
本算法使用cv::boxFilter
计算矩形区域内像素的平均值,再通过>=号生成二值图,也非常巧妙,但运算较慢。
实测消耗时间0.0140532秒。
补充:cv::transpose
函数的使用
函数签名
CV_EXPORTS_W void transpose(InputArray src, OutputArray dst);
参数很简单,分别为:输入矩阵,输出矩阵。
函数用于图像的转置(即图像矩阵转置),配合cv::flip可实现图像的旋转。如:
//图像逆时针旋转90°(先转置,再水平翻转)
cv::transpose(image, image);
cv::flip(image, image, 0);
//图像顺时针旋转90°(先转置,再垂直翻转)
cv::transpose(image, image);
cv::flip(image, image, 1);
//图像旋转180°(先水平翻转,再垂直翻转)
cv::flip(image, image, -1);
cv::adaptiveThreshold
函数的使用
函数签名
CV_EXPORTS_W void adaptiveThreshold( InputArray src, OutputArray dst,
double maxValue, int adaptiveMethod,
int thresholdType, int blockSize, double C );
参数分别为:输入矩阵,输出二值图像,输出的最大值,方法,阈值类型,邻域大小,使用的阈值。
函数用于使用自适应阈值生成二值图。本例中的函数还可以使用高斯(Gaussian)加权累计值,方法的标志位cv::ADAPTIVE_THRESH_GAUSSIAN_C
。
cv::integral
函数的使用
函数签名
CV_EXPORTS_W void integral( InputArray src, OutputArray sum, int sdepth = -1 );
参数分别为:输入矩阵,输出积分图像(与源图像通道数相同),depth类型(常用CV_32S
)。
积分图像可用于非常便利的统计矩形框内的像素和,只需要先计算出积分图像,再经过简单的加减法即可得出结果。
cv::boxFilter
函数的使用
函数签名
CV_EXPORTS_W void boxFilter( InputArray src, OutputArray dst, int ddepth,
Size ksize, Point anchor = Point(-1,-1),
bool normalize = true,
int borderType = BORDER_DEFAULT );
参数分别为:输入矩阵,输出的邻域平均值矩阵,depth类型,邻域大小cv::Size
,后面默认即可。
函数用于生成邻域平均值矩阵,后期可通过运算符生成二值图。
cv::sum
函数的使用
函数签名
CV_EXPORTS_AS(sumElems) Scalar sum(InputArray src);
参数即为输入图像。
函数用于得到图像像素和。函数返回cv::Scalar
类型向量,如果输入图为灰度图,返回的向量只有第一个像素不为0,其余均为0;如果输入图为彩色图,返回向量各元素表示各通道的像素和。