文档图片阴影去除

一、前言

在 OCR 的预处理中需要对文档图片中有阴影的部分进行消除, 在此之前使用过图像增强的算法对其进行处理, 本质就是二值化的处理.

在这篇文章中使用了新的方法进行处理, 并且将新方法和老方法之间进行了对比. 在文章最后还有使用模型对图片进行处理的效果.

二、通过 Gamma 校正来去除阴影 (旧方法)

这是之前使用的旧方法, 但是在移植到安卓的时候的时候出现了问题.

2.1 原理

对图片进行二值化处理之后, 会产生黑色的噪声, Gamma 矫正其实是对二值化后的图片进行一个对比度的增强.

2.2 C++ 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <opencv.hpp>

using namespace std;
using namespace cv;

//Gamma校正 fGamaa=0.45是常用值
void GammaCorrection(Mat& src, Mat& dst, float fGamma)
{
CV_Assert(src.data);
// accept only char type matrices
CV_Assert(src.depth() != sizeof(uchar));
// build look up table
unsigned char lut[256];
for (int i = 0; i < 256; i++)
{
lut[i] = saturate_cast<uchar>(pow((float)(i / 255.0), fGamma) * 255.0f);
}

dst = src.clone();
const int channels = dst.channels();
switch (channels)
{
case 1:
{

MatIterator_<uchar> it, end;
for (it = dst.begin<uchar>(), end = dst.end<uchar>(); it != end; it++)
*it = lut[(*it)];

break;
}
case 3:
{

MatIterator_<Vec3b> it, end;
for (it = dst.begin<Vec3b>(), end = dst.end<Vec3b>(); it != end; it++)
{
(*it)[0] = lut[((*it)[0])];
(*it)[1] = lut[((*it)[1])];
(*it)[2] = lut[((*it)[2])];
}
break;
}
}
}

int main(int argc, char** argv)
{
string addr = "C:\\Users\\YSXCC\\Desktop\\test.jpg";
Mat image = imread(addr);
//划分算法
//如果混合色与基色相同则结果色为白色
//如混合色为白色则结果色为基色不变
//如混合色为黑色则结果色为白色
Mat src = image.clone();
src.convertTo(src, CV_32FC3, 1.0 / 255);
Mat gauss;
Mat dst = src.clone();
GaussianBlur(src, gauss, Size(101, 101), 0);
dst = src / gauss;
dst.convertTo(dst, CV_8UC3, 255);
//gamma变换
Mat ss;
Mat matGamma;
ss = dst.clone();
GammaCorrection(ss, matGamma, 1.5);
//显示最终结果
imwrite("C:\\Users\\YSXCC\\Desktop\\test_out.jpg", matGamma);
return 0;
}

2.3 运行结果对比图

三、通过自适应阈值化来去除阴影 (新方法)

3.1 原理

自适应阈值, 则是根据像素的邻域块的像素值分布来确定该像素位置上的二值化阈值. 这样做的好处:

  1. 每个像素位置处的二值化阈值不是固定不变的, 而是由其周围邻域像素的分布来决定的.

  2. 亮度较高的图像区域的二值化阈值通常会较高, 而亮度低的图像区域的二值化阈值则会相适应的变小.

  3. 不同亮度、对比度、纹理的局部图像区域将会拥有相对应的局部二值化阈值.

3.2 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <opencv.hpp>
#include <iostream>
#include <string>

using namespace cv;
using namespace std;

Mat ImageSharp(const Mat& src, int nAmount) {
Mat dst;
double sigma = 3;
float amount = nAmount / 100.0f;

Mat imgBlurred;
cv::GaussianBlur(src, imgBlurred, cv::Size(7, 7), sigma, sigma, 4);
Mat temp_sub;

cv::subtract(src, imgBlurred, temp_sub);
cv::addWeighted(src, 1, temp_sub, amount, 0, dst);
return dst;
}

Mat ReduceBackGroundAlgorithm(const Mat& src, int flag) {
Mat gauss, dst2, dst3;
if (flag == 1) {
cv::GaussianBlur(src, gauss, cv::Size(31, 31), 0, 0, 4);
}
else {
cv::blur(src, gauss, cv::Size(101, 101));
}

cv::divide(src, gauss, dst2);

dst2 = ImageSharp(dst2, 101);
dst2.convertTo(dst3, CV_8UC1, 255);
return dst3;
}

std::vector<string> readImgs(const string& path) {
vector<string> img_path;

vector<cv::String> fn;
glob(path, fn, false);
size_t count = fn.size();

for (size_t i = 0; i < count; i++) {
img_path.push_back(fn[i]);
}

return img_path;
}

void showImg(const::cv::Mat& img, std::string& w_name) {
CV_Assert(!img.empty());
cv::namedWindow(w_name, cv::WINDOW_NORMAL);
cv::imshow(w_name, img);
}

void getbw(const cv::Mat& src, Mat& dst, double th = 15) {
cv::Mat img_src = src.clone();
if (img_src.cols < 1500) {
resize(img_src, img_src, img_src.size() * 2, 0, 0, cv::INTER_CUBIC);
}

Mat gray, res, res1;
cv::cvtColor(img_src, gray, COLOR_BGR2GRAY);

cv::adaptiveThreshold(gray, res, 255, ADAPTIVE_THRESH_MEAN_C, 0, 31, th);

res.convertTo(res1, CV_32FC1, 1.0 / 255);

dst = ReduceBackGroundAlgorithm(res1, 1);
}

int main(int argc, char const* argv[])
{
string path = "C:\\Users\\YSXCC\\Desktop\\test.jpg";
string save_path = "C:\\Users\\YSXCC\\Desktop\\result";
std::vector<string> imgs = readImgs(path);

for (int i = 0; i < imgs.size(); i++) {
Mat src = imread(imgs[i]);
if (src.empty()) {
return -1;
}

std::cout << i << " - " << src.size() << std::endl;

cv::Mat result;
getbw(src, result);

string save_filename = save_path + "res_" + to_string(i) + ".png";
cv::imwrite(save_filename, result);
std::cout << "finish " + std::to_string(i) << std::endl;
}

waitKey(27);
return 0;
}

3.3 运行结果对比图

四、新旧方法对比图

左边为旧方法的效果图, 右边为新方法的效果图

可以观察到, 新方法处理图片后会在图片中留下明显黑点。

当阴影部分非常严重, 甚至整张图片光线偏暗的时候, 新方法效果就会变差. 原图如下所示:

对比图如下所示:

新方法对阴影严重的图片处理后效果不及旧方法. 可以发现旧方法处理图片后会留下很明显的边框, 新方法则使得阴影下的文字不可识别, 且还是会有明显边框.

五、BEDSR-Net 去除阴影 (模型)

..........