IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> 图像识别——(java)opencv使用 -> 正文阅读

[人工智能]图像识别——(java)opencv使用

相关代码

package com.yuxue.util;

import java.io.File;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.ml.ANN_MLP;
import org.opencv.ml.SVM;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.yuxue.constant.Constant;
import com.yuxue.entity.PlateRecoResult;
import com.yuxue.enumtype.Direction;
import com.yuxue.enumtype.PlateColor;
import com.yuxue.enumtype.PlateHSV;
import com.yuxue.train.SVMTrain;

/**
 * 车牌处理工具类 车牌切图按字符分割 字符识别
 * @author yuxue
 * @date 2020-05-28 15:11
 */
public class PlateUtil {

    private static SVM svm = null;
    // 简单测试了一下,发现绿牌跟蓝牌分开识别,准确率更高
    private static ANN_MLP ann_blue = null;
    private static ANN_MLP ann_green = null;
    private static ANN_MLP ann_cn = null;

    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        svm = SVM.create();
        ann_blue = ANN_MLP.create();
        ann_green = ANN_MLP.create();
        ann_cn = ANN_MLP.create();
        loadSvmModel(Constant.DEFAULT_SVM_PATH);
        loadAnnBlueModel(Constant.DEFAULT_ANN_PATH);
        loadAnnGreenModel(Constant.DEFAULT_ANN_GREEN_PATH);
        loadAnnCnModel(Constant.DEFAULT_ANN_CN_PATH);
    }

    public static void loadSvmModel(String path) {
        svm.clear();
        svm = SVM.load(path);
    }

    public static void loadAnnBlueModel(String path) {
        ann_blue.clear();
        ann_blue = ANN_MLP.load(path);
    }

    public static void loadAnnGreenModel(String path) {
        ann_green.clear();
        ann_green = ANN_MLP.load(path);
    }

    public static void loadAnnCnModel(String path) {
        ann_cn.clear();
        ann_cn = ANN_MLP.load(path);
    }

    /**
     * 根据正则表达式判断字符串是否是车牌
     *
     * @param str
     * @return
     */
    public static Boolean isPlate(String str) {
        Pattern p = Pattern.compile(Constant.plateReg);
        Boolean bl = false;
        Matcher m = p.matcher(str);
        while (m.find()) {
            bl = true;
            break;
        }
        return bl;
    }

    /**
     *
     * @param imagePath
     * @param dst
     * @param debug
     * @param tempPath
     * @return
     */
    public static Vector<Mat> findPlateByContours(String imagePath, Vector<Mat> dst, Boolean debug, String tempPath) {
        Mat src = Imgcodecs.imread(imagePath);
        final Mat resized = ImageUtil.narrow(src, 600, debug, tempPath); // 调整大小,加快后续步骤的计算效率
        return findPlateByContours(src, resized, dst, debug, tempPath);
    }

    /**
     * 根据图片,获取可能是车牌的图块集合
     *
     * @param src 输入原图
     * @param inMat 调整尺寸后的图
     * @param dst 可能是车牌的图块集合
     * @param debug 是否保留图片的处理过程
     * @param tempPath 图片处理过程的缓存目录
     */
    public static Vector<Mat> findPlateByContours(Mat src, Mat inMat, Vector<Mat> dst, Boolean debug, String tempPath) {
        // 灰度图
        Mat gray = new Mat();
        ImageUtil.gray(inMat, gray, debug, tempPath);

        // 高斯模糊
        Mat gsMat = new Mat();
        ImageUtil.gaussianBlur(gray, gsMat, debug, tempPath);

        // Sobel 运算,得到图像的一阶水平方向导数
        Mat sobel = new Mat();
        ImageUtil.sobel(gsMat, sobel, debug, tempPath);

        // 图像进行二值化
        Mat threshold = new Mat();
        ImageUtil.threshold(sobel, threshold, debug, tempPath);

        // 使用闭操作 同时处理一些干扰元素
        Mat morphology = threshold.clone();
        ImageUtil.morphologyClose(threshold, morphology, debug, tempPath); // 闭操作

        // 边缘腐蚀,边缘膨胀,可以多执行两次
        morphology = ImageUtil.erode(morphology, debug, tempPath, 4, 4);
        morphology = ImageUtil.dilate(morphology, debug, tempPath, 4, 4, true);

        // 将二值图像,resize到原图的尺寸; 如果使用缩小后的图片提取图块,可能会出现变形,影响后续识别结果
        ImageUtil.enlarge(morphology, morphology, src.size(), debug, tempPath);

        // 获取图中所有的轮廓
        List<MatOfPoint> contours = ImageUtil.contours(src, morphology, debug, tempPath);
        // 根据轮廓, 筛选出可能是车牌的图块
        Vector<Mat> blockMat = ImageUtil.screenBlock(src, contours, false, debug, tempPath);

        // 找出可能是车牌的图块,存到dst中, 返回结果
        hasPlate(blockMat, dst, debug, tempPath);

        return dst;
    }

    /**
     *
     * @param imagePath
     * @param dst
     * @param plateHSV
     * @param debug
     * @param tempPath
     * @return
     */
    public static Vector<Mat> findPlateByHsvFilter(String imagePath, Vector<Mat> dst, PlateHSV plateHSV, Boolean debug, String tempPath) {
        Mat src = Imgcodecs.imread(imagePath);
        final Mat resized = ImageUtil.narrow(src, 600, debug, tempPath); // 调整大小,加快后续步骤的计算效率
        return findPlateByHsvFilter(src, resized, dst, plateHSV, debug, tempPath);
    }

    /**
     *
     * @param src 输入原图
     * @param inMat 调整尺寸后的图
     * @param dst 可能是车牌的图块集合
     * @param debug 是否保留图片的处理过程
     * @param tempPath 图片处理过程的缓存目录
     * @return
     */
    public static Vector<Mat> findPlateByHsvFilter(Mat src, Mat inMat, Vector<Mat> dst, PlateHSV plateHSV, Boolean debug, String tempPath) {
        // hsv取值范围过滤
        Mat hsvMat = ImageUtil.hsvFilter(inMat, debug, tempPath, plateHSV.minH, plateHSV.maxH);
        // 图像均衡化
        Imgproc.cvtColor(hsvMat, hsvMat, Imgproc.COLOR_HSV2BGR);
        Mat equalizeMat = ImageUtil.equalizeHist(hsvMat, debug, tempPath);
        hsvMat.release();

        // 二次hsv过滤,二值化
        Mat threshold = ImageUtil.hsvThreshold(equalizeMat, debug, tempPath, plateHSV.equalizeMinH, plateHSV.equalizeMaxH);
        Mat morphology = threshold.clone();
        ImageUtil.morphologyClose(threshold, morphology, debug, tempPath); // 闭操作
        threshold.release();

        Mat rgb = new Mat();
        Imgproc.cvtColor(morphology, rgb, Imgproc.COLOR_BGR2GRAY);

        // 将二值图像,resize到原图的尺寸; 如果使用缩小后的图片提取图块,可能会出现变形,影响后续识别结果
        ImageUtil.enlarge(rgb, rgb, src.size(), debug, tempPath);
        // 提取轮廓
        List<MatOfPoint> contours = ImageUtil.contours(src, rgb, debug, tempPath);
        // 根据轮廓, 筛选出可能是车牌的图块 // 切图的时候, 处理绿牌,需要往上方扩展一定比例像素
        Vector<Mat> blockMat = ImageUtil.screenBlock(src, contours, plateHSV.equals(PlateHSV.GREEN), debug, tempPath);

        // 找出可能是车牌的图块,存到dst中, 返回结果
        hasPlate(blockMat, dst, debug, tempPath);
        return dst;
    }

    /**
     * 输入车牌切图集合,判断是否包含车牌
     *
     * @param inMat
     * @param dst
     *            包含车牌的图块
     */
    public static void hasPlate(Vector<Mat> inMat, Vector<Mat> dst, Boolean debug, String tempPath) {
        for (Mat src : inMat) {
            if (src.rows() == Constant.DEFAULT_HEIGHT && src.cols() == Constant.DEFAULT_WIDTH) { // 尺寸限制; 已经结果resize了,此处判断一下
                Mat samples = SVMTrain.getFeature(src);
                float flag = svm.predict(samples);
                if (flag == 0) { // 目标符合
                    dst.add(src);
                    ImageUtil.debugImg(true, tempPath, "platePredict", src);
                }
            }
        }
        return;
    }

    /**
     * 判断车牌切图颜色
     *
     * @param inMat
     * @return
     */
    public static PlateColor getPlateColor(Mat inMat, Boolean adaptive_minsv, Boolean debug, String tempPath) {
        // 判断阈值
        final float thresh = 0.5f;
        // 转到HSV空间,对H均衡化之后的结果
        Mat hsvMat = ImageUtil.equalizeHist(inMat, debug, tempPath);

        if (colorMatch(hsvMat, PlateColor.GREEN, adaptive_minsv, debug, tempPath) > thresh) {
            return PlateColor.GREEN;
        }
        if (colorMatch(hsvMat, PlateColor.YELLOW, adaptive_minsv, debug, tempPath) > thresh) {
            return PlateColor.YELLOW;
        }
        if (colorMatch(hsvMat, PlateColor.BLUE, adaptive_minsv, debug, tempPath) > thresh) {
            return PlateColor.BLUE;
        }
        return PlateColor.UNKNOWN;
    }

    /**
     * 颜色匹配计算
     *
     * @param inMat
     * @param r
     * @param adaptive_minsv
     * @param debug
     * @param tempPath
     * @return
     */
    public static Float colorMatch(Mat hsvMat, PlateColor r, Boolean adaptive_minsv, Boolean debug, String tempPath) {
        final float max_sv = 255;
        final float minref_sv = 64;
        final float minabs_sv = 95;

        Integer countTotal = hsvMat.rows() * hsvMat.cols();
        Integer countMatched = 0;

        // 匹配模板基色,切换以查找想要的基色
        int min_h = r.minH;
        int max_h = r.maxH;
        float diff_h = (float) ((max_h - min_h) / 2);
        int avg_h = (int) (min_h + diff_h);

        for (int i = 0; i < hsvMat.rows(); i++) {
            for (int j = 0; j < hsvMat.cols(); j++) {
                int H = (int) hsvMat.get(i, j)[0];
                int S = (int) hsvMat.get(i, j)[1];
                int V = (int) hsvMat.get(i, j)[2];

                boolean colorMatched = false;
                if (min_h < H && H <= max_h) {
                    int Hdiff = Math.abs(H - avg_h);
                    float Hdiff_p = Hdiff / diff_h;
                    float min_sv = 0;
                    if (adaptive_minsv) {
                        min_sv = minref_sv - minref_sv / 2 * (1 - Hdiff_p);
                    } else {
                        min_sv = minabs_sv;
                    }
                    if ((min_sv < S && S <= max_sv) && (min_sv < V && V <= max_sv)) {
                        colorMatched = true;
                    }
                }
                if (colorMatched) {
                    countMatched++;
                }
            }
        }
        return countMatched * 1F / countTotal;
    }

    /**
     * 车牌切图,分割成单个字符切图
     * @param inMat 输入原始图像
     * @param charMat 返回字符切图vector
     * @param debug
     * @param tempPath
     */
    public static String charsSegment(Mat inMat, PlateColor color, Boolean debug, String tempPath) {

        int charCount = 7; // 车牌字符个数
        if (color.equals(PlateColor.GREEN)) {
            charCount = 8;
        }

        // 切换到灰度图
        Mat gray = new Mat();
        Imgproc.cvtColor(inMat, gray, Imgproc.COLOR_BGR2GRAY);
        ImageUtil.gaussianBlur(gray, gray, debug, tempPath);

        // 图像进行二值化 // 图像二值化阈值选取--未完成yuxue
        Mat threshold = new Mat();
        switch (color) {
        case BLUE:
            Imgproc.threshold(gray, threshold, 10, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY);
            break;
        default: // GREEN YELLOW
            Imgproc.threshold(gray, threshold, 10, 255, Imgproc.THRESH_OTSU + Imgproc.THRESH_BINARY_INV);
            break;
        }
        ImageUtil.debugImg(debug, tempPath, "plateThreshold", threshold); // 输出二值图

        // 边缘腐蚀
        threshold = ImageUtil.erode(threshold, debug, tempPath, 2, 2);

        // 垂直方向投影,错切校正 // 理论上,还可以用于分割字符
        Integer px = getShearPx(threshold);
        ImageUtil.shearCorrection(threshold, threshold, px, debug, tempPath);

        // 前面已经结果错切校正了,可以按照垂直、水平方向投影进行精确定位
        // 垂直投影 + 垂直分割线,分割字符 // 水平投影,去掉上下边框、铆钉干扰
        threshold = sepAndClear(threshold, px, charCount, debug, tempPath);

        // 边缘膨胀 // 还原腐蚀操作产生的影响 // 会影响中文字符的精确度
        threshold = ImageUtil.dilate(threshold, debug, tempPath, 2, 2, true);

        // 提取外部轮廓
        List<MatOfPoint> contours = Lists.newArrayList();

        Imgproc.findContours(threshold, contours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_NONE);

        Vector<Rect> charRect = new Vector<Rect>(); // 字符轮廓集合

        Mat dst = null;
        if (debug) {
            dst = inMat.clone();
            Imgproc.cvtColor(threshold, dst, Imgproc.COLOR_GRAY2BGR);
        }
        for (int i = 0; i < contours.size(); i++) { // 遍历轮廓
            MatOfPoint contour = contours.get(i);
            Rect mr = Imgproc.boundingRect(contour); // 得到包覆此轮廓的最小正矩形
            if (checkCharSizes(mr)) { // 验证尺寸,主要验证高度是否满足要求,去掉不符合规格的字符,中文字符后续处理
                charRect.add(mr);
                if (debug) {
                    ImageUtil.drawRectangle(dst, mr);
                    ImageUtil.debugImg(debug, tempPath, "boundingRect", dst);
                }
            }
        }
        if (null == charRect || charRect.size() <= 0) { // 未识别到字符
            return null;
        }
        // 字符个数不足,要按照分割的区域补齐 // 同时处理中文字符
        // System.out.println("字符个数===>" + charRect.size());

        // 遍历轮廓,修正超高超宽的字符,去掉铆钉的干扰, 并排序
        Vector<Rect> sorted = new Vector<Rect>();
        sortRect(charRect, sorted);

        // 定位省份字母位置
        Integer posi = getSpecificRect(sorted, color);
        Integer prev = posi - 1 <= 0 ? 0 : posi - 1;

        // 定位中文字符 // 中文字符可能不是连续的轮廓,需要特殊处理
        Rect chineseRect = getChineseRect(sorted.get(posi), sorted.get(prev));

        Mat chineseMat = new Mat(threshold, chineseRect);
        chineseMat = preprocessChar(chineseMat);
        ImageUtil.debugImg(debug, tempPath, "chineseMat", chineseMat);

        // 识别字符,计算置信度
        List<PlateRecoResult> result = Lists.newArrayList();

        // 一般来说,中文字符预测比较不准确,所以参考业界的做法,设置默认所在的省份字符,如果置信度较低, 则默认该字符
        PlateRecoResult chinese = new PlateRecoResult();
        chinese.setSort(0);
        chinese.setRect(chineseRect);
        predictChinese(chineseMat, chinese); // 预测中文字符
        result.add(chinese);
        charCount--;

        for (int i = posi; i < sorted.size() && charCount > 0; i++, charCount--) { // 预测中文之外的字符
            Mat img_crop = new Mat(threshold, sorted.get(i));
            img_crop = preprocessChar(img_crop);
            PlateRecoResult chars = new PlateRecoResult();
            chars.setSort(i+1);
            chars.setRect(chineseRect);
            predict(img_crop, color, chars); // 预测数字、字符
            result.add(chars);
            ImageUtil.debugImg(debug, tempPath, "charMat", img_crop);
        }
        String plate = "";  // 车牌识别结果
        Double fonfidence = 0.0D; // 置信度
        for (PlateRecoResult p : result) {
            plate += p.getChars();
            fonfidence += p.getConfi();
        }
        System.out.println(plate + "===>" + fonfidence);
        return plate;
    }

    /**
     *
     * @param threshold
     * @param dst
     * @param px
     * @param sep
     * @param debug
     * @param tempPath
     */
    public static Mat sepAndClear(Mat threshold, Integer px, Integer charCount, Boolean debug, String tempPath) {
        Mat dst = threshold.clone();
        Set<Integer> rows = Sets.newHashSet();
        int ignore = 10;
        // 水平方向投影 // 按rows清除干扰 // 去掉上下边框干扰像素
        // 垂直方向投影; 按cols清楚干扰; 意义不大; 直接分割,提取字符更简单
        for (int i = 0; i < threshold.rows(); i++) {
            int count = Core.countNonZero(threshold.row(i));
            if (count <= 15) {
                rows.add(i);
                if (i < ignore) {
                    for (int j = 0; j < i; j++) {
                        rows.add(j);
                    }
                }
                if (i > threshold.rows() - ignore) {
                    for (int j = i + 1; j < threshold.rows(); j++) {
                        rows.add(j);
                    }
                }
            }
        }

        Integer minY = 0;
        for (int i = 0; i < threshold.rows(); i++) {
            if (rows.contains(i)) {
                if (i <= threshold.rows() / 2) {
                    minY = i;
                }
                for (int j = 0; j < threshold.cols(); j++) {
                    dst.put(i, j, 0);
                }
            }
        }

        threshold.release();
        ImageUtil.debugImg(debug, tempPath, "sepAndClear", dst);

        // 分割字符,返回所有字符的边框,Rect(x, y, width, height)
        // 在这里提取,估计比轮廓提取方式更准确,尤其在提取中文字符方面
        /*Integer height = dst.rows() - rows.size();
        Integer y = minY + 1;   // 修正一个像素
        Integer x = 0;
        Integer width = 0;
        Boolean bl = false; // 是否是全0的列
        Vector<Rect> rects = new Vector<Rect>();    // 提取到的轮廓集合,可用于后续的字符识别,也可用于去除垂直方向的干扰
        for (int i = 0; i < dst.cols(); i++) {
            int count = Core.countNonZero(dst.col(i));
            if(count <= 0) { // 黑色的列; 因为前面就行了边缘腐蚀,这里选择全黑色的列作为分割
                bl = true;
            } else {
                if(bl) {
                    x = i;
                }
                bl =false;
                width++;
            }
            if(bl && width > 0) {   // 切割图块
                Rect r = new Rect(x, y, width, height); // 提取到的轮廓
                // 按轮廓切图
                Mat img_crop = new Mat(dst, r);
                ImageUtil.debugImg(debug, tempPath, "sepAndClear-crop", img_crop);
                rects.add(r);
                width = 0;
            }
        }*/
        return dst;
    }

    /**
     * 基于字符垂直方向投影,计算错切值,用于错切校正 上下均去掉6个像素;
     * 去掉上下边框的干扰 最大处理25px的错切校;
     * 左右去掉25像素,保证每列都能取到完整的数据 相当于实现任意角度的投影计算;
     * @param threshold 二值图像, 0黑色 255白色; 136 * 36
     * @return sep 可以用于字符分割的列
     * @return 错切像素值
     */
    public static Integer getShearPx(Mat threshold) {
        int px = 25; // 最大处理25像素的错切
        int ignore = 6; // 去掉上下边框干扰像素

        int maxCount = 0; // 取count值最大
        int minPx = px; // 取绝对值最小
        Integer result = 0;

        for (int i = -px; i <= px; i++) {
            int colCount = 0; // 计数满足条件的列
            // 计算按像素值倾斜的投影
            for (int j = px; j < threshold.cols() - px; j++) {
                int cellCount = 0; // 计数每列为0的值
                float c = i * 1F / threshold.rows();
                for (int k = ignore; k < threshold.rows() - ignore; k++) {
                    double d = threshold.get(k, Math.round(j + k * c))[0];
                    if (d <= 10) {
                        cellCount++;
                    }
                }
                if (cellCount >= 24) {
                    colCount++;
                }
            }
            // System.out.println(i + "===>" + minPx + "===>" + colCount + "===>" + maxCount);
            if (colCount == maxCount) {
                if (Math.abs(i) <= minPx) {
                    minPx = Math.abs(i);
                    result = i;
                }
            }
            if (colCount > maxCount) {
                maxCount = colCount;
                minPx = Math.abs(i);
                result = i;
            }
        }
        System.err.println("错切校正像素值===>" + result);
        return result;
    }

    /**
     * 根据字符的外接矩形倾斜角度计算错切值,
     * 用于错切校正 提取最小正矩形的时候,同时提取最小外接斜矩形;
     * 计算错切像素值 在切割字符之后,进行预测之前,对每个字符进行校正
     * @param angleRect 字符最小外接矩形 集合
     * @return
     */
    public static Integer getShearPx(Vector<RotatedRect> angleRect) {
        Integer posCount = 0;
        Integer negCount = 0;
        Float posAngle = 0F;
        Float negAngle = 0F;
        for (RotatedRect r : angleRect) {
            if (Math.abs(r.angle) >= 45) { // 向右倾斜 需要向左校正
                posCount++;
                posAngle = posAngle + 90F + (float) r.angle;
            } else {
                negCount++;
                negAngle = negAngle - (float) r.angle;
            }
        }
        Integer px = 0;
        if (posCount > negCount) {
            px = -posAngle.intValue() / posCount / 2;
        } else {
            px = negAngle.intValue() / negCount / 2;
        }
        if (Math.abs(px) > 10) {
            px = px % 10;
        }
        return px;
    }

    /**
     * 预测数字、字母 字符
     *
     * @param img
     * @return
     */
    public static void predict(Mat img, PlateColor color, PlateRecoResult chars) {
        Mat f = PlateUtil.features(img, Constant.predictSize);

        int index = 0;
        Double maxVal = -2D;
        Mat output = new Mat(1, Constant.strCharacters.length, CvType.CV_32F);
        if(color.equals(PlateColor.GREEN)) {
            ann_green.predict(f, output); // 预测结果
        } else {
            ann_blue.predict(f, output); // 预测结果
        }
        for (int j = 0; j < Constant.strCharacters.length; j++) {
            double val = output.get(0, j)[0];
            if (val > maxVal) {
                maxVal = val;
                index = j;
            }
        }
        String result = String.valueOf(Constant.strCharacters[index]);
        chars.setChars(result);
        chars.setConfi(maxVal);
    }

    /**
     * 预测中文字符
     *
     * @param img
     * @return
     */
    public static void predictChinese(Mat img, PlateRecoResult chinese) {
        Mat f = PlateUtil.features(img, Constant.predictSize);
        int index = 0;
        Double maxVal = -2D;

        Mat output = new Mat(1, Constant.strChinese.length, CvType.CV_32F);
        ann_cn.predict(f, output); // 预测结果
        for (int j = 0; j < Constant.strChinese.length; j++) {
            double val = output.get(0, j)[0];
            if (val > maxVal) {
                maxVal = val;
                index = j;
            }
        }
        String result = Constant.strChinese[index];
        chinese.setChars(Constant.KEY_CHINESE_MAP.get(result));
        chinese.setConfi(maxVal);
    }

    /**
     * 找出指示城市的字符的Rect,
     * 例如 苏A7003X,就是A的位置
     * 之所以选择城市的字符位置,是因为该位置不管什么字母,占用的宽度跟高度的差不多,而且字符笔画是连续的,能大大提高位置的准确性
     * @param vecRect
     * @return
     */
    public static Integer getSpecificRect(Vector<Rect> vecRect, PlateColor color) {
        List<Integer> xpositions = Lists.newArrayList();

        int maxHeight = 0;
        int maxWidth = 0;
        for (int i = 0; i < vecRect.size(); i++) {
            xpositions.add(vecRect.get(i).x);
            if (vecRect.get(i).height > maxHeight) {
                maxHeight = vecRect.get(i).height;
            }
            if (vecRect.get(i).width > maxWidth) {
                maxWidth = vecRect.get(i).width;
            }
        }
        int specIndex = 0;
        for (int i = 0; i < vecRect.size(); i++) {
            Rect mr = vecRect.get(i);
            int midx = mr.x + mr.width / 2;

            if (PlateColor.GREEN.equals(color)) {
                if ((mr.width > maxWidth * 0.8 || mr.height > maxHeight * 0.8) && (midx < Constant.DEFAULT_WIDTH * 2 / 8 && midx > Constant.DEFAULT_WIDTH / 8)) {
                    specIndex = i;
                }
            } else {
                // 如果一个字符有一定的大小,并且在整个车牌的1/7到2/7之间,则是我们要找的特殊车牌
                if ((mr.width > maxWidth * 0.8 || mr.height > maxHeight * 0.8) && (midx < Constant.DEFAULT_WIDTH * 2 / 7 && midx > Constant.DEFAULT_WIDTH / 7)) {
                    specIndex = i;
                }
            }
        }
        return specIndex;
    }

    /**
     * 根据特殊车牌来构造猜测中文字符的位置和大小
     *
     * @param rectSpe
     * @return
     */
    public static Rect getChineseRect(Rect rectSpe, Rect rectPrev) {
        int height = rectSpe.height;
        float newwidth = rectSpe.width * 1.15f;
        int x = rectSpe.x;
        int y = rectSpe.y;

        // 判断省份字符前面的位置,是否有宽度符合要求的中文字符
        if (rectPrev.width >= rectSpe.width && rectPrev.x <= rectSpe.x - rectSpe.width) {
            return rectPrev;
        }
        // 如果没有,则按照车牌尺寸来切割
        int newx = x - (int) (newwidth * 1.15);
        newx = Math.max(newx, 0);
        Rect a = new Rect(newx, y, (int) newwidth, height);
        return a;
    }

    /**
     * 字符预处理: 统一每个字符的大小
     *
     * @param in
     * @return
     */
    final static int CHAR_SIZE = 20;

    private static Mat preprocessChar(Mat in) {
        int h = in.rows();
        int w = in.cols();
        // 生成输出对角矩阵(2, 3)
        // 1 0 0
        // 0 1 0
        Mat transformMat = Mat.eye(2, 3, CvType.CV_32F);
        int m = Math.max(w, h);
        transformMat.put(0, 2, (m - w) / 2f);
        transformMat.put(1, 2, (m - h) / 2f);

        Mat warpImage = new Mat(m, m, in.type());
        Imgproc.warpAffine(in, warpImage, transformMat, warpImage.size(), Imgproc.INTER_LINEAR, Core.BORDER_CONSTANT, new Scalar(0));
        Mat resized = new Mat(CHAR_SIZE, CHAR_SIZE, CvType.CV_8UC3);
        Imgproc.resize(warpImage, resized, resized.size(), 0, 0, Imgproc.INTER_CUBIC);
        return resized;
    }

    /**
     * 字符尺寸验证;
     * 去掉尺寸不符合的图块 此处计算宽高比意义不大,因为字符 1 的宽高比干扰就已经很大了
     * @param r
     * @return
     */
    public static Boolean checkCharSizes(Rect r) {
        float minHeight = 15f;
        float maxHeight = 35f;
        double charAspect = r.size().width / r.size().height;
        return charAspect < 1 && minHeight <= r.size().height && r.size().height < maxHeight;
    }

    /**
     * 将Rect按位置从左到右进行排序 遍历轮廓,修正超高的字符,去掉铆钉的干扰, 并排序
     *
     * @param vecRect
     * @param out
     * @return
     */
    public static void sortRect(Vector<Rect> vecRect, Vector<Rect> out) {
        Map<Integer, Integer> map = Maps.newHashMap();
        Integer avgY = 0; // 所有字符的平均起点Y值 // 小于平均值的,削脑袋
        Integer avgHeight = 0; // 所有字符的平均身高 //大于平均值的,剁脚
        Integer avgWidth = 0; // 计算所有大于8像素(去掉【1】字符的干扰)轮廓的均值 // 大于平均值的,进行瘦身操作
        Integer wCount = 0;

        for (int i = 0; i < vecRect.size(); ++i) {
            map.put(vecRect.get(i).x, vecRect.indexOf(vecRect.get(i)));
            avgY += vecRect.get(i).y;
            avgHeight += vecRect.get(i).height;
            if (vecRect.get(i).width >= 10) {
                wCount++;
                avgWidth += vecRect.get(i).width;
            }
        }
        avgY = avgY / vecRect.size();
        avgHeight = avgHeight / vecRect.size();
        avgWidth = avgWidth / wCount;
        Set<Integer> set = map.keySet();
        Object[] arr = set.toArray();
        Arrays.sort(arr);
        for (Object key : arr) {
            Rect r = vecRect.get(map.get(key));
            if (Math.abs(avgY - r.y) >= 2 || Math.abs(r.height - avgHeight) >= 2) {
                r = new Rect(r.x, avgY - 1, r.width, avgHeight); // 身材超高或者超矮的,修正一下
            }
            if (r.width > avgWidth) { // 轮廓偏宽
                r = new Rect(r.x, r.y, avgWidth, r.height); // 瘦身
            }
            out.add(r);
        }
        return;
    }

    public static float[] projectedHistogram(final Mat img, Direction direction) {
        int sz = img.rows();
        if (direction.equals(Direction.VERTICAL)) {
            sz = img.cols();
        }

        // 统计这一行或一列中,非零元素的个数,并保存到nonZeroMat中
        float[] nonZeroMat = new float[sz];
        Core.extractChannel(img, img, 0);
        for (int j = 0; j < sz; j++) {
            Mat data = direction.equals(Direction.VERTICAL) ? img.row(j) : img.col(j);
            int count = Core.countNonZero(data);
            nonZeroMat[j] = count;
        }
        float max = 0;
        for (int j = 0; j < nonZeroMat.length; ++j) {
            max = Math.max(max, nonZeroMat[j]);
        }
        if (max > 0) {
            for (int j = 0; j < nonZeroMat.length; ++j) {
                nonZeroMat[j] /= max;
            }
        }
        return nonZeroMat;
    }

    public static Mat features(Mat in, int sizeData) {
        float[] vhist = projectedHistogram(in, Direction.VERTICAL);
        float[] hhist = projectedHistogram(in, Direction.HORIZONTAL);
        Mat lowData = new Mat();
        if (sizeData > 0) {
            Imgproc.resize(in, lowData, new Size(sizeData, sizeData));
        }
        int numCols = vhist.length + hhist.length + lowData.cols() * lowData.rows();
        Mat out = new Mat(1, numCols, CvType.CV_32F);

        int j = 0;
        for (int i = 0; i < vhist.length; ++i, ++j) {
            out.put(0, j, vhist[i]);
        }
        for (int i = 0; i < hhist.length; ++i, ++j) {
            out.put(0, j, hhist[i]);
        }
        for (int x = 0; x < lowData.cols(); x++) {
            for (int y = 0; y < lowData.rows(); y++, ++j) {
                double[] val = lowData.get(x, y);
                out.put(0, j, val[0]);
            }
        }
        return out;
    }

    /**
     * 根据图片,获取可能是车牌的图块集合 多种方法实现:
     *   1、网上常见的轮廓提取车牌算法
     *   2、hsv色彩分割算法
     *   3、 参考人脸识别算法,实现特征识别算法 --参考 PlateCascadeTrain
     * @param dst 可能是车牌的图块集合
     * @param debug 是否保留图片的处理过程
     * @param tempPath 图片处理过程的缓存目录
     */
    public static Vector<Mat> getPlateMat(String imagePath, Vector<Mat> dst, Boolean debug, String tempPath) {
        Mat src = ImageUtil.imread(imagePath, CvType.CV_8UC3);

        final Mat resized = ImageUtil.narrow(src, 600, debug, tempPath); // 调整大小,加快后续步骤的计算效率

        CompletableFuture<Vector<Mat>> f1 = CompletableFuture.supplyAsync(() -> {
            Vector<Mat> r = findPlateByContours(src, resized, dst, debug, tempPath);
            return r;
        });
        CompletableFuture<Vector<Mat>> f2 = CompletableFuture.supplyAsync(() -> {
            Vector<Mat> r = findPlateByHsvFilter(src, resized, dst, PlateHSV.BLUE, debug, tempPath);
            return r;
        });
        CompletableFuture<Vector<Mat>> f3 = CompletableFuture.supplyAsync(() -> {
            Vector<Mat> r = findPlateByHsvFilter(src, resized, dst, PlateHSV.GREEN, debug, tempPath);
            return r;
        });
        CompletableFuture<Vector<Mat>> f4 = CompletableFuture.supplyAsync(() -> {
            Vector<Mat> r = findPlateByHsvFilter(src, resized, dst, PlateHSV.YELLOW, debug, tempPath);
            return r;
        });
        CompletableFuture<Vector<Mat>> f5 = CompletableFuture.supplyAsync(() -> {
            Vector<Mat> r = new Vector<Mat>(); // 参考人脸识别算法,实现特征识别算法,--未完成; 参考PlateCascadeTrain
            return r;
        });

        // 这里的 join() 将阻塞,直到所有的任务执行结束
        CompletableFuture.allOf(f1, f2, f3, f4, f5).join();
        try {
            Vector<Mat> result = f1.get();
            result.addAll(f2.get());
            result.addAll(f3.get());
            result.addAll(f4.get());
            result.addAll(f5.get());
            return result;
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        Instant start = Instant.now();

        String tempPath = Constant.DEFAULT_TEMP_DIR;
        String filename = Constant.DEFAULT_DIR + "test/11.jpg";
        File f = new File(filename);
        if (!f.exists()) {
            File f1 = new File(filename.replace("jpg", "png"));
            File f2 = new File(filename.replace("png", "bmp"));
            filename = f1.exists() ? f1.getPath() : f2.getPath();
        }

        Boolean debug = true;
        Vector<Mat> dst = new Vector<Mat>();
        // 提取车牌图块
        // getPlateMat(filename, dst, debug, tempPath);
        // findPlateByContours(filename, dst, debug, tempPath);
        // findPlateByHsvFilter(filename, dst, PlateHSV.BLUE, debug, tempPath);
        findPlateByHsvFilter(filename, dst, PlateHSV.GREEN, debug, tempPath);

        Set<String> result = Sets.newHashSet();
        dst.stream().forEach(inMat -> {
            // 识别车牌颜色
            PlateColor color = PlateUtil.getPlateColor(inMat, true, debug, tempPath);
            // 识别车牌字符
            String plateNo = PlateUtil.charsSegment(inMat, color, debug, tempPath);
            result.add(plateNo + "\t" + color.desc);
        });
        System.out.println(result.toString());

        Instant end = Instant.now();
        System.err.println("总耗时:" + Duration.between(start, end).toMillis());
    }

}

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2022-03-22 20:35:19  更:2022-03-22 20:37:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 13:31:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码