图像处理|基于投影的字符版面分析java代码

首先介绍算法思路:图像对应方向的投影,就是在该方向取一条直线,统计垂直于该直线(轴)的图像上的像素的黑点数量,累加求和作为该轴该位置的值;基于图像投影的切割就是将图像映射成这种特征后,基于这种特征判定图像的切割位置(坐标),用这个坐标来切割原图像,得到目标图像。
java代码实现:
java的图像处理,这里大部分是由im.read读取,bufferedimage,然后转为二值bitset做处理(0,1或者说是true和false,一个bitset是整张图片的0,1像素值的一维行向量。bitset.length=width*height)

/** * 图像向x轴做投影后的数组 * * @param imagedata * @param w * @param h * @return */ public static int[] xpro(BitSet bitSet, int w, int h) { int xpro[] = new int[w]; for (int j = 0; j < w; j++) { for (int i = 0; i < h; i++) { if (bitSet.get(i * w + j) == true) xpro[j]++; } } return xpro; }/** * 图像向y轴做投影后的数组 * * @param imagedata * @param w * @param h * @return */ public static int[] ypro(BitSet bitSet, int w, int h) { int ypro[] = new int[h]; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { if (bitSet.get(i * w + j) == true) ypro[i]++; } } return ypro; }

简单的基于投影的图像分割:
public static Rectangle[] yproSegment(int[] ypro, int w, int h) {int[] L = new int[h - 1]; // 左割线集合,最多n-1条分割线,且左分割第一项取0,即图片第一行做起点(需要a行位置没有太多空白噪声) int[] R = new int[h - 1]; // 右割线集合 // 两种情况:sku区域起始位置元素为空白区域;起始位置含字符元素 int k1 = 0; int k2 = 0; if (ypro[0] == 0) { k1 = 0; k2 = 0; for (int i = 4; i < h; i++) { if (ypro[i] > 0 && ypro[i - 1] > 0 && ypro[i - 2] > 0 && ypro[i - 3] > 0 && ypro[i - 4] == 0) { L[k1] = i - 3; k1++; } else if (ypro[i] == 0 && ypro[i - 1] > 0 && ypro[i - 2] > 0 && ypro[i - 3] > 0 && ypro[i - 4] > 0) { R[k2] = i; k2 += 1; } } } else { k1 = 1; k2 = 0; for (int i = 4; i < h; i++) { if (ypro[i] > 0 && ypro[i - 1] > 0 && ypro[i - 2] > 0 && ypro[i - 3] > 0 && ypro[i - 4] == 0) { L[k1] = i - 3; k1++; } else if (ypro[i] == 0 && ypro[i - 1] > 0 && ypro[i - 2] > 0 && ypro[i - 3] > 0 && ypro[i - 4] > 0) { R[k2] = i; k2 += 1; } } L[0] = 0; } if (ypro[ypro.length - 1] != 0) { R[k2] = h; } List c = new ArrayList(); for (int i = 0; i < R.length; i++) { if (R[i] != 0 && L[i] < R[i]) { c.add(new Rectangle(0, L[i], w, R[i] - L[i])); } else { break; } } // System.out.println("yrpo"); // for(int i:ypro){ // System.out.println(i); // } // System.out.println("Llast:"+L[c.size()-1]); // System.out.println("Rast:"+R[c.size()-1]); Rectangle[] children = new Rectangle[c.size()]; for (int i = 0; i < children.length; i++) { children[i] = c.get(i); } return children; }

利用粘连区域特点(sku每行高度是相似的,也就是说高度的众数可以作为衡量是否粘连的阈值,然后以极值点进行sku的粘连自动分割:
/** * * @param ypro * @return */ public static Rectangle[] yproAutoSegment(int[] ypro, int w, int h) { int[] allline = ylinelayout(ypro, w, h); if (allline.length == 0) return null; Rectangle[] yproSegment = new Rectangle[allline.length / 2]; for (int i = 0; i < yproSegment.length; i++) { // yproSegment[i]= new Imxxyy(0, w, allline[i*2], allline[i*2+1]); yproSegment[i] = new Rectangle(0, allline[i * 2], w, allline[i * 2 + 1] - allline[i * 2]); } return yproSegment; }/** * 纵向自动版面分析(众数参考分析) * * @param ypro * @param im * @param h * @param w * @return */ public static int[] ylinelayout(int[] ypro, int w, int h) { // 投影分割图片 boolean flag = false; for (int i : ypro) { if (i == 0) { flag = true; break; } } // System.out.println("ypro:"); // for(int i:ypro){ // System.out.println(i); // } // System.out.println("flag:"+flag); if (flag == false) { int[] result = { 0, ypro.length }; return result; } int[] L = new int[h - 1]; // 左割线集合,最多n-1条分割线,且左分割第一项取0,即图片第一行做起点(需要a行位置没有太多空白噪声) int[] R = new int[h - 1]; // 右割线集合 // 两种情况:sku区域起始位置元素为空白区域;起始位置含字符元素 int k1 = 0; int k2 = 0; if (ypro[0] == 0) { k1 = 0; k2 = 0; for (int i = 4; i < h; i++) { if (ypro[i] > 0 && ypro[i - 1] > 0 && ypro[i - 2] > 0 && ypro[i - 3] > 0 && ypro[i - 4] == 0) { L[k1] = i - 3; k1++; } else if (ypro[i] == 0 && ypro[i - 1] > 0 && ypro[i - 2] > 0 && ypro[i - 3] > 0 && ypro[i - 4] > 0) { R[k2] = i; k2 += 1; } } } else { k1 = 1; k2 = 0; for (int i = 4; i < h; i++) { if (ypro[i] > 0 && ypro[i - 1] > 0 && ypro[i - 2] > 0 && ypro[i - 3] > 0 && ypro[i - 4] == 0) { L[k1] = i - 3; k1++; } else if (ypro[i] == 0 && ypro[i - 1] > 0 && ypro[i - 2] > 0 && ypro[i - 3] > 0 && ypro[i - 4] > 0) { R[k2] = i; k2 += 1; } } L[0] = 0; } if (ypro[ypro.length - 1] != 0) { R[k2 + 1] = ypro.length - 1; }ArrayList c = new ArrayList(); for (int i = 0; i < R.length; i++) { if (R[i] != 0 && L[i] < R[i]) { c.add(L[i]); c.add(R[i]); } else { break; } }int[] gap = new int[c.size() / 2]; // 间隙 for (int i = 0; i < gap.length; i++) { gap[i] = c.get(i * 2 + 1) - c.get(i * 2); } // log.info("L.length"+L.length); // log.info("L.length"+R.length); // log.info("c.size:"+c.size()); // log.info("gap.length:"+gap.length ); // 得到初次分割的所有“字符”的高度 if (gap.length == 1) { int[] result = { L[0], R[0] }; return result; } int Te = (int) (catchE(gap) + 0.5); ArrayList newc = new ArrayList(); for (int i = 0; i < gap.length; i++) { if (gap[i] >= (int) (Te * 1.5 + 0.5)) { // 对异常gap进行二次分割(粘连字符二次分割函数) int[] newline = improveSegment(c.get(i * 2), c.get(i * 2 + 1), ypro, gap[i], Te); for (int j : newline) { newc.add(j); } } } int bbegin = 0; ArrayList allline = new ArrayList(); allline.addAll(c); int time = 0; for (int i = 0; i < newc.size(); i++) { int th = newc.get(i); for (int j = bbegin; j < c.size() - 1; j++) { if (c.get(j) < th && c.get(j + 1) > th) { allline.add(j + 1 + time * 2, th); allline.add(j + 1 + time * 2, th); bbegin = j; time++; break; } } } return ArrayUtils.toPrimitive(allline.toArray(new Integer[0])); }/** * 找出众数范围(无补偿众数) * * @param gap * @return */ public static float catchE(int[] gap) {float Te = 0; int[] g = gap.clone(); Arrays.sort(g); int[] times = new int[g.length]; for (int i = 0; i < gap.length; i++) { for (int j = 0; j < g.length; j++) { if (g[j] == gap[i]) { times[j]++; } } } // 得到各gap的出现次数 int tt = 0; int num = 0; for (int i = 0; i < times.length; i++) { if (times[i] > tt) { tt = times[i]; num = i; } } Te = g[num]; return Te; } 极值点的二次分割 /** * 二次分割 * * @param a * @param b * @param ypro * @param gapE * @param Te * @return */ public static int[] improveSegment(int a, int b, int[] ypro, float gapE, int Te) { int[] localypro = Arrays.copyOfRange(ypro, a, b + 1); // 以t2作为跃迁步长,避免同一区域出现多条分割线,以t1作为分割线阈值,以找到精确分割线 ArrayList c = new ArrayList(); int t1 = Te - 8; int t2 = Te - 5; for (int i = t2; i < localypro.length - t2; i++) { int findline = findline(localypro, t2, t1); c.add(a + findline); i = i + t2; if (i > localypro.length) { break; }} return ArrayUtils.toPrimitive(c.toArray(new Integer[0])); }

【图像处理|基于投影的字符版面分析java代码】公司电脑只装了eclipse,以后补充:
matlab代码实现:
和java代码类似,只是matlab和python是将图像存为二维数组,所以简单的(i,j)遍历就行了,而java是存在一维数组里,需要(i*width,j)
python代码实现:

    推荐阅读