使用众数来获取图片中文字的高度

1、使用BufferedImage  的 ImageIO.read 读取本地图片信息,将其放入缓冲区以便操作

2、将图片垂直等分几份 分区分段扫描以提高准确率

3、通过getRGB()方式获得像素矩阵 : 此方式为沿width方向扫描,来依次向上扫描此区域 统计连续的黑色像素的高度

4、获得每段的数据数组,去掉极端数据(明显不属于一个字体的高度) 用来去掉 标点,------,和一些数学图形

5、用一个较大的数组去存下扫描的所有数据(要符合一个字体的合适的高度范围,比如最低20,最高200)

5、每段去平均值组成一个新的数组,得到平均值

6、判断该平均值 是否大于一个字体的 最大高度, 大于 :将图片选中90度,再次扫描

7、 取较大数组里的众数

8、如果 一个众数也没有,返回平均

9、有众数,如果有多个,得到多个众数取平均,最终获得的众数如果与平均值相差很大,依次向后面取,这个差值暂定30

代码:

package testDemo;

import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.print.attribute.Size2DSyntax;

/**
 * 识别图片文字高度 (返回0 表示无效图片)
 * @author huangxq
 *
 */
public class TruncatImgStudy {
    private final int part =10;//需要切割成几份扫描
    private final float minFontHeight = 25; // 允许最小误差
    private final float maxFontHeight = 200;//允许最大误差舍弃
    private final int twoCycles=2;//是否二次循环过:用于上传过来的图片是倒过来的

    public static void main(String [] args){
        TruncatImgStudy JustStudy = new TruncatImgStudy();
        JustStudy.initImage("F:/错题图片/1.jpg");
    }
    
    /**
     * 得到文字行与图片高度
     */
    public int getFontHeight(int cycles,BufferedImage bimg){
        if(null == bimg){
            return 0;
        }
        //用来求众数:
        ArrayList<Integer> multipleList = new ArrayList<>();
        //计众数和
        int multipleSum = 0;
        //将该图片垂直等分几段
        List<BufferedImage> cutImage = randomCut(bimg,part);
        if(null == cutImage || cutImage.isEmpty()){
            return 0;//未切割成功
        }
        //用来求最终平均值【判断是否需要旋转扫描】
        int[] lastHignNums = new int[cutImage.size()];
        for(int j=0;j<cutImage.size();j++){
            BufferedImage tempBimg = cutImage.get(j);
            //获取所有不为全白行的高度坐标
            int[] notWhiteRow = this.getBlackRow(tempBimg);
            // 计算所有连续行的行数即文字高度
            int[] hignSpace = this.getAllHeight(notWhiteRow);
            //去掉极端数据(即:不满足一个字体的合适的最大最小高度 如:黑点)
            int[] newArray = this.cutInvalidNum(hignSpace,minFontHeight);
            //若该段未扫描到一个字体,跳出本次循环
            if(null == newArray || newArray.length<=0){
                continue;
            }
            //将该段统计到的符合合理高度的数据都存到【众数集合】
            for (int i : newArray) {
                //这里指考虑最高高度,最低高度前面已经剔除了
                if(i< this.maxFontHeight){
                    multipleList.add(i);
                    multipleSum +=i;
                }
            }
            lastHignNums[j] = this.getAverage(newArray);
        }
        //该数组去极端值【有的段没有扫到字体,去掉该段的0记录】
        lastHignNums = this.cutInvalidNum(lastHignNums, minFontHeight);
        int lastAvg = this.getAverage(lastHignNums);
        //平均值与一个字体高度偏差太高,旋转过来扫描
        if(lastAvg<= this.minFontHeight || lastAvg>= this.maxFontHeight){
            //扫描次数超过两次,已经扫描过了,直接返回0,为无效图片
            if(cycles >= this.twoCycles){
                return 0;
            }
            //第一次旋转,执行旋转
            cycles++;
            return this.rotateImage(bimg);
        }
        //返回众数
        //一个字体高度都没统计出来,表示图片无效
        if(null == multipleList || multipleList.isEmpty()){
            return 0;
        }
        //去除极端数据取平均数
        int multipleAvg =multipleSum/multipleList.size();
        int result = this.multipleNum(multipleList,multipleAvg,this.minFontHeight,this.maxFontHeight);
        return result;
    }
   
    
    /**
     * 把一张图片垂直等分
     *
     * @param bimg
     * @param time
     * @return
     */
    private List<BufferedImage> randomCut(BufferedImage bimg, int time) {
        if(null == bimg || time<=0){
            return Collections.emptyList();
        }
        ArrayList list = new ArrayList();
        int width = bimg.getWidth();
        int sPerTime = width / time;
        for (int i = 0; i < time; i++) {
            BufferedImage newBuffer = bimg.getSubimage(i * sPerTime, 0, sPerTime, bimg.getHeight());
            list.add(newBuffer);
        }
        return list;
    }

    /**
     * 获取拥有黑色像素的高度坐标 沿width方向扫描
     *
     * @return
     */
    private int[] getBlackRow(BufferedImage bimg) {
        if(null == bimg){
            return null;
        }
        int [][]data = new int[bimg.getWidth()][bimg.getHeight()];
        int[] startRow = new int[bimg.getHeight()];
        int n = 0;
        //通过getRGB()方式获得像素矩阵 : 此方式为沿width方向扫描
        for(int y=0;y<bimg.getHeight();y++){
            for(int x=0;x<bimg.getWidth();x++){
                data[x][y]=bimg.getRGB(x,y);
                if (!("ffffffff".equals(String.format("%x",data[x][y])))){// 该行像素不是 ("#ffffffff")即全白 就归类为一个字
                    startRow[n] = y;//统计该字体高度
                    n++;
                    break;//一行只记一个
                }
            }
        }
        return startRow;
    }
    
    /**
     * 计算所有连续行的高度即文字的高度
     */
    public int[] getAllHeight(int[] startRow){
        if(null == startRow || startRow.length<=0){
            return null;
        }
        int k = 0;
        //用来计一个字的向上延展有多少像素
        int[] hignSpace = new int[startRow.length];
        int temp = 0;
        for (int i=0;i<startRow.length;i++) {
            //除第一行,需要连续
            if (i != startRow.length-1 && !((startRow[i+1]-startRow[i]) == 1)){
                if (k ==0){
                    hignSpace[k] = startRow[i] - startRow[0];
                }else {
                    hignSpace[k] = startRow[i] - temp;
                }
                temp = startRow[i+1];
                k++;
            }
        }
        return hignSpace;
    }
    
    /**
     * 去除指定误差外的无效数据
     *
     * @param oldArr
     * @param t
     * @return
     */
    private int[] cutInvalidNum(int[] oldArr, float min) {
        if (null == oldArr || oldArr.length <= 0) {
            return null;
        }
        int zero = 0;
        for (int i = 0; i < oldArr.length; i++) {
            if (oldArr[i] < min) {
                zero++;
            }
        }
        // 定义新的数组 长度是 原来旧的数组的长度减去(要去掉的极端数据)的个数
        int newArr[] = new int[oldArr.length - zero];
        int j = 0; // 新数组的索引
        for (int i = 0; i < oldArr.length; i++) { // 遍历原来旧的数组
            if (oldArr[i] >= min) {
                newArr[j] = oldArr[i]; // 赋值给新的数组
                j++;
            }
        }
        Arrays.sort(newArr);
        return newArr;
    }
    
    
    /**
     * 取平均值
     *
     * @param
     */
    private int getAverage(int[] array) {
        if (null == array || array.length <= 0) {
            return 0;
        }
        int leg = 0;
        int sum = 0;
        for (int i = 0; i < array.length; i++) {
            if (0 != array[i]) {
                sum = sum + array[i];
                leg++;
            }
        }
        if(leg<=0){
            return 0;
        }
        return sum / leg;
    }
    
    /**
     * 顺时针旋转90度(通过交换图像的整数像素RGB 值)
     * @param BufferedImage bi
     * @return
     */
    public int rotateImage(BufferedImage bi) {
        if(null == bi){
            return 0;
        }
        int width = bi.getWidth();
        int height = bi.getHeight();
        BufferedImage bufferedImage = new BufferedImage(height, width, bi.getType());
        for (int i = 0; i < width; i++){
            for (int j = 0; j < height; j++){
                bufferedImage.setRGB(height - 1 - j, width - 1 - i, bi.getRGB(i, j));
            }
        }
        return getFontHeight(2,bufferedImage);
    }
    
    /**
     * 取众数 如果一个众数也没有,返回改组数据平均值,如果众数不止一个,返回众数平均值
     * 
     * @param list
     * @return
     */
    public int multipleNum(ArrayList<Integer> list,int multipleAvg,float minFontHeight,float maxFontHeight){
        int result;
        if(null == list || list.isEmpty()){
            return 0;
        }
        //计value 重复次数:map集合 map<value,该value重复的个数>
        Map<Integer, Integer> multiMap = new HashMap<>();
        for (int i : list) {
            if(multiMap.containsKey(i)){
                multiMap.put(i, multiMap.get(i)+1);
            }else{
                multiMap.put(i, 1);
            }
        }
        
        // 将 multiMap 中所有的键值对(键为数,值为数出现的频率)放入一个 ArrayList 按 value 重复次数排序
        List<Map.Entry<Integer, Integer>> entries = new ArrayList<>(multiMap.entrySet());
        // 对 entries 按出现频率从大到小排序
        Collections.sort(entries, new Comparator<Map.Entry<Integer, Integer>>() {
            @Override
            public int compare(Map.Entry<Integer, Integer> e1, Map.Entry<Integer, Integer> e2) {
                return e2.getValue() - e1.getValue();
            }
        });
        
        if(null == entries || entries.isEmpty()){
            return 0;
        }
        //如果这个数组没有众数 返回平均数
        if(entries.get(0).getValue()==1){
            return multipleAvg;
        }
        return this.getSuitMultipleNum(entries,multipleAvg);
    }
        

    /**
     * 获取合适的众数
     * @param entries
     * @param multipleAvg
     * @return
     */
    public int getSuitMultipleNum(List<Map.Entry<Integer, Integer>> entries,int multipleAvg){
        int size = entries.size();
        for(int i=0 ; i<size ;i++){
            int result = 0;
            int sumNum = 0;
            List<Integer> modalNums = new ArrayList<>();
            // 本来排序后第一个 entry 的键肯定是一个众数,如果这个众数不合理,依次向后取,越靠前越合理
            int firstNum = entries.get(i).getKey();
            modalNums.add(firstNum); 
            sumNum+=firstNum;
            //处理有多个众数情况
            //统计众数是否唯一
            for (int j = (i+1); j < size; j++) {
                // 如果之后的 entry 与第一个 entry 的 value 相等,那么这个 entry 的键也是众数
                if (entries.get(j).getValue().equals(entries.get(i).getValue())) {
                    int tempNum = entries.get(j).getKey();
                    modalNums.add(tempNum);
                    sumNum+=tempNum;
                } else {
                    break;
                }
            }
            //如果数组为空,继续循环
            if(null == modalNums || modalNums.isEmpty()){
                continue;
            }
            if(modalNums.size()<=1){
                //只有一个众数
                result = modalNums.get(0);
            }else{
                //多个众数取平均值
                result = sumNum/modalNums.size();
            }
            //这里经过找规律比较,暂定30
            if(Math.abs((result - multipleAvg)) < 30){
                return result;
            }
        }
        //都没有
        return multipleAvg;
    }
    
    /**
     * 获取图片信息
     * @param openUrl 文件路径
     * @return
     */
    public int initImage(String openUrl){
        File file = new File(openUrl);
        BufferedImage img=null;// 将图片缓冲到内存中,BufferedImage生成的图片在内存里有一个图像缓冲区
        if(null!=file){
            try {
                img= ImageIO.read(file);
                return getFontHeight(1,img);
            } catch (IOException e) {
                e.printStackTrace();
                return 0;
            }
        }
        return 0;
    }
}
 


 

猜你喜欢

转载自blog.csdn.net/weixin_42178492/article/details/81736616
今日推荐