前言:对于印刷体图片来说,进行水平投影和垂直投影可以很快的进行分割,本文在OpenCV中如何进行水平投影和垂直投影通过代码进行说明。
水平投影:二维图像在y轴上的投影
垂直投影:二维图像在x轴上的投影
由于投影的图像需要进行二值化,本文采用积分二值化的方式,对图片进行处理。
具体代码如下:
1 //积分二值化
2 void thresholdIntegral (Mat inputMat, Mat& outputMat)
3 {
4
5 int nRows = inputMat.rows;
6 int nCols = inputMat.cols;
7
8 // create the integral image
9 Mat sumMat;
10 integral (inputMat, sumMat);
11
12 int S = MAX (nRows, nCols) / 8;
13 double T = 0.15;
14
15 // perform thresholding
16 int s2 = S / 2;
17 int x1, y1, x2, y2, count, sum;
18
19 int* p_y1, *p_y2;
20 uchar* p_inputMat, *p_outputMat;
21
22 for (int i = 0; i < nRows; ++i)
23 {
24 y1 = i - s2;
25 y2 = i + s2;
26
27 if (y1 < 0)
28 {
29 y1 = 0;
30 }
31 if (y2 >= nRows)
32 {
33 y2 = nRows - 1;
34 }
35
36 p_y1 = sumMat.ptr<int> (y1);
37 p_y2 = sumMat.ptr<int> (y2);
38 p_inputMat = inputMat.ptr<uchar> (i);
39 p_outputMat = outputMat.ptr<uchar> (i);
40
41 for (int j = 0; j < nCols; ++j)
42 {
43 // set the SxS region
44 x1 = j - s2;
45 x2 = j + s2;
46
47 if (x1 < 0)
48 {
49 x1 = 0;
50 }
51 if (x2 >= nCols)
52 {
53 x2 = nCols - 1;
54 }
55
56 count = (x2 - x1)* (y2 - y1);
57
58 // I(x,y)=s(x2,y2)-s(x1,y2)-s(x2,y1)+s(x1,x1)
59 sum = p_y2[x2] - p_y1[x2] - p_y2[x1] + p_y1[x1];
60
61 if ((int) (p_inputMat[j] * count) < (int) (sum* (1.0 - T)))
62 {
63 p_outputMat[j] = 0;
64 }
65 else
66 {
67 p_outputMat[j] = 255;
68 }
69 }
70 }
71 }
72 //垂直方向投影
73 void picshadowx (Mat binary)
74 {
75 Mat paintx (binary.size(), CV_8UC1, Scalar (255)); //创建一个全白图片,用作显示
76
77 int* blackcout = new int[binary.cols];
78 memset (blackcout, 0, binary.cols * 4);
79
80 for (int i = 0; i < binary.rows; i++)
81 {
82 for (int j = 0; j < binary.cols; j++)
83 {
84 if (binary.at<uchar> (i, j) == 0)
85 {
86 blackcout[j]++; //垂直投影按列在x轴进行投影
87 }
88 }
89 }
90 for (int i = 0; i < binary.cols; i++)
91 {
92 for (int j = 0; j < blackcout[i]; j++)
93 {
94 paintx.at<uchar> (binary.rows-1-j, i) = 0; //翻转到下面,便于观看
95 }
96 }
97 delete blackcout;
98 imshow ("paintx", paintx);
99
100 }
101 //水平方向投影并行分割
102 void picshadowy (Mat binary)
103 {
104 //是否为白色或者黑色根据二值图像的处理得来
105 Mat painty (binary.size(), CV_8UC1, Scalar (255)); //初始化为全白
106
107 //水平投影
108 int* pointcount = new int[binary.rows]; //在二值图片中记录行中特征点的个数
109 memset (pointcount, 0, binary.rows * 4);//注意这里需要进行初始化
110
111 for (int i = 0; i < binary.rows; i++)
112 {
113 for (int j = 0; j < binary.cols; j++)
114 {
115 if (binary.at<uchar> (i, j) == 0)
116 {
117 pointcount[i]++; //记录每行中黑色点的个数 //水平投影按行在y轴上的投影
118 }
119 }
120 }
121
122 for (int i = 0; i < binary.rows; i++)
123 {
124 for (int j = 0; j < pointcount[i]; j++) //根据每行中黑色点的个数,进行循环
125 {
126
127 painty.at<uchar> (i, j) = 0;
128 }
129
130 }
131
132 imshow ("painty", painty);
133
134 vector<Mat> result;
135 int startindex = 0;
136 int endindex = 0;
137 bool inblock = false; //是否遍历到字符位置
138
139 for (int i = 0; i < painty.rows; i++)
140 {
141
142 if (!inblock&&pointcount[i] != 0) //进入有字符区域
143 {
144 inblock = true;
145 startindex = i;
146 cout << "startindex:" << startindex << endl;
147 }
148 if (inblock&&pointcount[i] == 0) //进入空白区
149 {
150 endindex = i;
151 inblock = false;
152 Mat roi = binary.rowRange (startindex, endindex+1); //从而记录从开始到结束行的位置,即可进行行切分
153 result.push_back (roi);
154 }
155 }
156
157 for (int i = 0; i < result.size(); i++)
158 {
159 Mat tmp = result[i];
160 imshow ("test"+to_string (i), tmp);
161 }
162 delete pointcount;
163
164 }
165 int main (int argc, char* argv[])
166 {
167
168 Mat src = cv::imread ("test.jpg");
169
170 if (src.empty())
171 {
172 cerr << "Problem loading image!!!" << endl;
173 return -1;
174 }
175
176 imshow("in",src);
177
178 Mat gray;
179
180 if (src.channels() == 3)
181 {
182 cv::cvtColor (src, gray, CV_BGR2GRAY);
183 }
184 else
185 {
186 gray = src;
187 }
188
189
190 Mat bw2 = Mat::zeros (gray.size(), CV_8UC1);
191 thresholdIntegral (gray, bw2);
192
193 cv::imshow ("binary integral", bw2);
194
195 //picshadowx (bw2);
196 picshadowy (bw2);
197 waitKey (0);
198
199 return 0;
200 }
输入图片:
二值图片:
水平投影:
垂直投影:
行切割:
该处理方法,对印刷体有较好的效果,因为印刷体的行列区分明显,因此可以很快的进行行与列的分割。
by Shawn Chen,2017.12.13日,晚。