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 }
输入图片:
二值图片:
水平投影:
垂直投影:
行切割:
该处理方法,对印刷体有较好的效果,因为印刷体的行列区分明显,因此可以很快的进行行与列的分割。
by Shawn Chen,2017.12.13日,晚。