首先介绍算法思路:图像对应方向的投影,就是在该方向取一条直线,统计垂直于该直线(轴)的图像上的像素的黑点数量,累加求和作为该轴该位置的值;基于图像投影的切割就是将图像映射成这种特征后,基于这种特征判定图像的切割位置(坐标),用这个坐标来切割原图像,得到目标图像。
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代码实现:
推荐阅读
- opencv|图像处理之椒盐噪声的添加与去除
- OpenCV|【OpenCV 完整例程】89. 带阻滤波器的传递函数
- OpenCV|【OpenCV 完整例程】90. 频率域陷波滤波器
- OpenCV|【OpenCV 完整例程】22. 图像添加非中文文字
- OpenCV|【OpenCV 完整例程】91. 高斯噪声、瑞利噪声、爱尔兰噪声
- 灰度世界算法(Gray World Algorithm)和White Patch Retinex算法
- 安卓开发|通过RenderScript 实现 NV21转Bitmap、两张Bitmap按照透明度混合的工具类
- ace File-Input实现图片上传功能+colorBox显示图片
- java学习|【算法学习】链表数相加(Java)
- Java学习|ArrayList源码浅析