天天看点

OpenCV 实现图片的水平投影与垂直投影,并进行行分割

前言:对于印刷体图片来说,进行水平投影和垂直投影可以很快的进行分割,本文在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 }      

 输入图片:

OpenCV 实现图片的水平投影与垂直投影,并进行行分割

二值图片:

OpenCV 实现图片的水平投影与垂直投影,并进行行分割

 水平投影:

OpenCV 实现图片的水平投影与垂直投影,并进行行分割

垂直投影:

OpenCV 实现图片的水平投影与垂直投影,并进行行分割

 行切割:

OpenCV 实现图片的水平投影与垂直投影,并进行行分割
OpenCV 实现图片的水平投影与垂直投影,并进行行分割

该处理方法,对印刷体有较好的效果,因为印刷体的行列区分明显,因此可以很快的进行行与列的分割。

by Shawn Chen,2017.12.13日,晚。

=========================================================

比你优秀的人比你还努力,你有什么资格不去奋斗!

__一个有理想的程序员。