Opencv 几何图形颜色形状识别 计算面积 角度大小 边长比 JAVA

Opencv 几何图形颜色形状识别 计算面积 角度大小 边长比 JAVA_第1张图片
Opencv 几何图形颜色形状识别 计算面积 角度大小 边长比 JAVA_第2张图片在这里插入图片描述
Opencv 几何图形颜色形状识别 计算面积 角度大小 边长比 JAVA_第3张图片

简介
这是在Android端上面做的,所以就结了个logcat的图将,opencv3.4.1。
大致内容有,颜色、形状、边长、面积、边长比 的求法。
识别的是图一,中间进行了内容的提取,这里只给出从图二开始识别的过程。
具体提取图二过程见另一篇文章
图二是根据图一手裁的,具体程序裁完没有外边的黑边。
https://blog.csdn.net/qq_43701766/article/details/105877653

考虑到外界光照的影响,所识别的图像的曝光不平均的因素,二值化的时候采用的是自适应二值的方法,所以二值那张图片上会有一些多余的小的白斑,识别图形的时候做了一定的处理。

为了方便大家测试,代码中的logcat和图片保存部分代码已经删除。

具体调用方法:

public static void DisposeTFT(Bitmap bt){
    // 这是个用来裁剪图片的 将图一 转换到 图二的方法 若不需要 可以省去 具体实现 上边有链接
    Mat image = Cut_Out_TFT(bt); // 裁剪图片
    image = cvut.picDown(image); // 图片缩小
    Mat declarMat = cvut.cloneMat(image); // 复制图像

    cvut.toDilate(image, 3, 3, 1);// 膨胀
    cvut.toGrayMat(image);// 灰度化
    Mat binary = cvut.cloneMat(image);

    // 自适应二值化
    // 参数4 : ADAPTIVE_THRESH_MEAN_C  平均值
    // 参数4 : ADAPTIVE_THRESH_GAUSSIAN_C  高斯加权
    // blockSize : 域块大小 用来计算区域阈值 一般为奇数
    // C : 计算后减去的值
    Imgproc.adaptiveThreshold(binary, binary,255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
            Imgproc.THRESH_BINARY_INV, 55, 5);

    cvut.coverBackGroundToBlack(binary); // 背景变黑 没什么用

    List<MatOfPoint> contoursList = cvut.findContoursList(binary);// 找到轮廓

    int i = 0;
    List<MatOfPoint> approxCurves = new ArrayList<>(); // 存取拟合多边形的点集
    // 轮廓处理
    for(MatOfPoint point : contoursList) {

        if (Imgproc.contourArea(point) < 300) continue; // 判断面积小于一定值时不是所需轮廓 过滤 自适应二值化的弊端

        Mat mat = Mat.zeros(declarMat.rows(), declarMat.cols(), CvType.CV_8UC3);
        MatOfPoint2f curvesPoint = new MatOfPoint2f(point.toArray()); // 将MatOfPoint转为MatOfPoint2f
        MatOfPoint2f approxCurvesPoint = new MatOfPoint2f();

        Imgproc.approxPolyDP(curvesPoint,approxCurvesPoint,0,true); // 拟合
        MatOfPoint newPoint = new MatOfPoint(approxCurvesPoint.toArray()); // 将MatOfPoint2f转为MatOfPoint
        approxCurves.add(newPoint); // 添加轮廓
        Imgproc.drawContours(mat,approxCurves,i,new Scalar(0,255,0),1,8); // 绘制拟合后的多边形

        Point[] points = cvut.findp(mat);// 找到描边点
        Point[] checkedPoints = cvut.checkPoint(points);// 清除同一条直线上的点
	// 这里是颜色与形状的处理方法
        String shape = cvut.findShape(checkedPoints, approxCurvesPoint);// 确定形状
        String color = cvut.findColor(declarMat, checkedPoints);    // 颜色
    }

另一个类里的东西:

    /**
     *  图片缩小 
     *  INTER_AREA 弊端:图片较大时 处理速度偏慢
     */
    public static Mat picDown(Mat bit){
        Mat Sbit = new Mat();
        Imgproc.resize(bit, Sbit, new Size( bit.width()*0.5, bit.height()*0.5), Imgproc.INTER_AREA);
        Imgproc.resize(Sbit, Sbit, new Size( Sbit.width()*0.5, Sbit.height()*0.5), Imgproc.INTER_AREA);
        return Sbit;
    }
    
    /**
     * 复制 Mat
     */
    public Mat cloneMat(Mat mat) {
        return mat.clone();
    }
    
    /**
     * 膨胀
     */
    public void toDilate(Mat mat, int i, int j, int iterations) {
        Imgproc.dilate(mat, mat, Imgproc.getStructuringElement(
                Imgproc.MORPH_RECT, new Size(i, j)), new Point(-1, -1),
                iterations);
    }
    
    /**
     * 灰度化图像
     */
    public void toGrayMat(Mat from) {
        Imgproc.cvtColor(from, from, Imgproc.COLOR_BGR2GRAY);
    }
    /**
     * 填充杂色
     */
    public void coverBackGroundToBlack(Mat mat) {
        final double blackPixle[] = { 0.0 };
        for (int y = 0; y < mat.height(); y++) {
            for (int x = 0; x < mat.width(); x++) {
                double pixle[] = mat.get(y, x);
                if (pixle[0] == 255.0) {// 如果是白色
                    mat.put(y, x, blackPixle);
                } else {// 遇到黑色
                    break;
                }
            }
            for (int x = mat.width() - 1; x > 0; x--) {
                double pixle[] = mat.get(y, x);
                if (pixle[0] == 255.0) {// 如果是白色
                    mat.put(y, x, blackPixle);
                } else {// 遇到黑色
                    break;
                }
            }
        }
    }
    /**
     * 获得角点 传入单调且不能是二值化的图像
     */
    public Point[] findp(Mat mat) {
        
        MatOfPoint corners = new MatOfPoint();
        this.toGrayMat(mat);
         
        Imgproc.goodFeaturesToTrack(mat, corners, 50, 0.01, 10,
                new Mat(), 3, true, 0.04);
        Point[] pCorners = corners.toArray();
        return pCorners;
    }
    /**
     * 合并直线上的点
     */
    public Point[] checkPoint(Point[] points) {
        int lastLength = -1;
        int thisLength = 0;
        Point[] lp = points;
        Point[] np;
        while (true) {
            np = checkPointOnce(lp);
            thisLength = np.length;
            if (thisLength == lastLength) {
                break;
            }
                lastLength = thisLength;
            lp = np;
        }
        return np;
    }
    
    /**
     * 删除三点中处于中间的点
     */
    private int deleteMiddlePointToNull(Point p1, Point p2, Point p3) {
        double a = p1.x + p1.y;
        double b = p2.x + p2.y;
        double c = p3.x + p3.y;
        if ((a > b && b > c) || (a < b && b < c)) {// b在中间
            return 2;
        } else if ((c > a && a > b) || (c < a && a < b)) {// a在中间
            return 1;
        } else {
            return 3;
        }
    }
/**
 * 合并直线上的点
 */
private Point[] checkPointOnce(Point[] points) {
    int length = points.length;
    boolean flag = false;// 是否找到可删除点
    if (length < 4) {
        return points;// 如果小于四个点 免了判断
    }
    label: for (int i = 0; i < length; i++) {// 得到点1
        for (int j = 0; j < length; j++) {// 得到点2
            if (j == i) {
                continue;
            }
            for (int k = 0; k < length; k++) {// 得到点3
                if (k == j || k == i) {
                    continue;
                }
                // int slope = 0;//斜率
                double d1 = twoPointsAngel(points[i], points[j]);// i,j直线角度
                double d2 = twoPointsAngel(points[i], points[k]);// i,k直线角度
                double angelMin = d1 - d2;
                if (Math.abs(angelMin) < 10) {// 如果倾角非常接近,删除中间的点
                    int needDelete = deleteMiddlePointToNull(points[i],
                            points[j], points[k]);
                    if (needDelete == 1) {
                        points[i] = null;
                    } else if (needDelete == 2) {
                        points[j] = null;
                    } else if (needDelete == 3) {
                        points[k] = null;
                    }
                    flag = true;
                    break label;
                }
            }
        }
    }
    if (flag) {
        Point[] newPoints = new Point[length - 1];
        int index = 0;
        for (Point p : points) {// 准备一个没有空值的新数组
            if (null != p) {
                newPoints[index] = p;
                index++;
            }
        }
        return newPoints;
    } else {
        return points;
    }
}   
    /**
     * 查找轮廓并返回轮廓数组 最好传入阈值图
     */
    public List<MatOfPoint> findContoursList(Mat mat) {
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        Mat hierarchy = new Mat();
        Imgproc.findContours(mat, contours, hierarchy, Imgproc.RETR_LIST,
                Imgproc.CHAIN_APPROX_SIMPLE);
      
        return contours;
    }

接下来是颜色和形状识别的两个方法
形状检测
根据轮廓的角点数量、位置、面积 等因素判断形状。

/**
 * 形状识别
 */
public String findShape(Point[] checkedPoints, MatOfPoint2f line) {

    int length = checkedPoints.length;
    if (length >= 5){
        Point X = new Point();
        float[] R = new float[1];
        MatOfPoint Line = new MatOfPoint(line.toArray());
        // X : 圆心坐标 R : 半径
        Imgproc.minEnclosingCircle(line, X, R); // 最小外接圆 为什么需要传一个数组得到半径????
        if (Imgproc.contourArea(Line) > (R[0]*R[0]*3.1415926*0.75)){
            // 面积面积若大于最小外接圆的 0.75 则认为是圆
            return "圆形";
        }else {
            // 待添加五角星的判断
            return "五角星";
        }
    }

    if (length < 3) {
        return "ShapeError";
    } else if (length == 3) {
        return "三角";
    } else if (length == 4) {// 四边形
        double d1 = twoPointsDistance(checkedPoints[0], checkedPoints[1]);
        double d2 = twoPointsDistance(checkedPoints[0], checkedPoints[2]);
        double d3 = twoPointsDistance(checkedPoints[0], checkedPoints[3]);
        Point[] p = new Point[2];
        // 找与第一个点相邻的两个点(舍弃最远的那个点)
        if (d1 > d2 && d1 > d3) {// d1最大,舍弃下标1
            p[0] = checkedPoints[2];
            p[1] = checkedPoints[3];
        } else if (d2 > d1 && d2 > d3) {// d2最大,舍弃下标2
            p[0] = checkedPoints[1];
            p[1] = checkedPoints[3];
        } else {
            p[0] = checkedPoints[1];
            p[1] = checkedPoints[2];
        }// 现在数组p中是两个最近的点
        double angelL1 = twoPointsAngel(checkedPoints[0], p[0]);
        double angelL2 = twoPointsAngel(checkedPoints[0], p[1]);
        double angelP = Math.abs(angelL1 - angelL2);
        
        if (angelP > 80 && angelP < 100) {// 直角
            double dis1 = twoPointsDistance(checkedPoints[0], p[0]);
            double dis2 = twoPointsDistance(checkedPoints[0], p[1]);
            double distanceRatio = dis1 / dis2;
            
            if (distanceRatio > 0.80 && distanceRatio < 1.20) {
                return "正方";
            } else {
                return "长方";
            }
        } else {
            // 若不是直角 四边相等的是菱形
            // 有一对对边不等的是梯形
            // 剩下的是平行四边形
            // 这个四边形识别比较多 勤快的可以把它单独分离出去一个方法
            checkedPoints = pointSort2(checkedPoints);
            double l1 = twoPointsDistance(checkedPoints[0], checkedPoints[1]); // 上边
            double l2 = twoPointsDistance(checkedPoints[0], checkedPoints[3]); // 左边
            double l3 = twoPointsDistance(checkedPoints[2], checkedPoints[1]); // 右边
            double l4 = twoPointsDistance(checkedPoints[2], checkedPoints[3]); // 下边
            double dis1 = l1 / l2;
            double dis2 = l2 / l3; // 梯形需要的左右对边比
            double dis3 = l1 / l4; // 梯形需要的上下对边比
            if (dis1 > 0.8 && dis1 < 1.2 && dis2 > 0.8 && dis2 < 1.2 && dis3 > 0.8 && dis3 < 1.2){
                return "菱形";
            } else if (dis2 > 1.2 || dis3 > 1.2){
                return "梯形";
            }else {
                return "平行四边形";
            }
        }
    } else {
        return "ShapeError";
    }
}

识别形状用到的方法:
两点间距离、直线夹角
角点排序

/**
 * 求两点之间距离
 */
private double twoPointsDistance(Point p1, Point p2) {
    return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}

/**
 * 求两点所在直线水平夹角
 */
private double twoPointsAngel(Point p1, Point p2) {
    if (p1.y == p2.y) {
        p1.y += 0.01;
    }
    return Math.toDegrees(Math.atan((p1.x - p2.x) / (p1.y - p2.y)));
}

// 角点排序... 左上 右上 右下 左下
// 用于四边形识别
public static Point[] pointSort2(Point[] LinePoints){
    Point t;
    // 冒泡排序 Y 小的放前
    for (int j = 0; j < 3; j++){
        for (int i = 0; i < 4; i++) {
            if (LinePoints[i].y > LinePoints[i + 1].y) {
                t = LinePoints[i];
                LinePoints[i] = LinePoints[i + 1];
                LinePoints[i + 1] = t;
            }
        }
    }

    if (LinePoints[0].x > LinePoints[1].x){
        t = LinePoints[0];
        LinePoints[0] = LinePoints[1];
        LinePoints[1] = t;
    }
    if (LinePoints[2].x < LinePoints[3].x){
        t = LinePoints[2];
        LinePoints[2] = LinePoints[3];
        LinePoints[3] = t;
    }
    return LinePoints;
}

颜色识别
主要根据图像中心点的RGB进行颜色判断。

/**
 * 颜色识别
 */
public String findColor(Mat colorfulMat, Point[] checkedPoints){

        HashMap<String, double[]> CvColor = CvColor();
        Point centerPoint = getCenterPoint(checkedPoints);  // 取得轮廓中心点的坐标

        double[] RGB = colorfulMat.get((int)centerPoint.y, (int)centerPoint.x);
        double[] cvRGB;
        Set<String> set = CvColor.keySet();
        
        int r = 15, g = 40, b = 40; // 容忍度
        for (String string : set){
            cvRGB = CvColor.get(string);
            if (colorMiss(RGB, cvRGB, r, g, b)){
                return string;
            }
        }
        // 若颜色识别失败 向左上移动两个像素 重复
        Point Point = new Point((centerPoint.x - 2), (centerPoint.y -2));
        RGB = colorfulMat.get((int)Point.y, (int)Point.x);
        for (String string : set){
            cvRGB = CvColor.get(string);
            if (colorMiss(RGB, cvRGB, r, g, b)){
                return string;
            }
        }
        // 若颜色识别依然失败 增加容忍度
        while (true){
            for (String string : set){
                cvRGB = CvColor.get(string);
                if (colorMiss(RGB, cvRGB, r, g, b)){
                    return string;
                }
            }
            r += 5;
            g += 5;
            b += 5;
        }
    }
}

识别颜色用到的方法:
得到图形的中心点坐标。
根据RGB设置颜色。
这里我设置的颜色比较少,都是从实际图片上取的值,若有需要自行添加就好。

/**
 * 取得中心坐标
 */
public Point getCenterPoint(Point[] points) {
    double centerX = 0.0;
    double centerY = 0.0;
    for (Point point : points) {
        centerX += point.x;
        centerY += point.y;
    }
    Point result = new Point();
    result.x = (int) (centerX / points.length);
    result.y = (int) (centerY / points.length);
    return result;
}

/**
 * 判断颜色
 */
public boolean colorMiss(double[]a, double[]b, int x, int y, int z){
    if (Math.abs(a[0] - b[0]) <= x
            && Math.abs(a[1] - b[1]) <= y
            && Math.abs(a[2] - b[2]) <= z){
        return true;
    }else return false;
}

/**
 * 颜色 RGB
 */
public  HashMap<String, double[]> CvColor(){

    HashMap<String, double[]> map = new HashMap<String, double[]>();

    map.put("红色", new double[]{180.0, 30.0, 50.0});

    map.put("蓝色", new double[]{10.0, 140.0, 220.0});

    map.put("黄色", new double[]{215.0, 225.00, 120.0});

    map.put("品色", new double[]{190.0, 173.0, 220.0});

    map.put("黑色", new double[]{10.0, 15.0, 10.0});

    map.put("白色", new double[]{255.0, 255.0, 255.0});
    
    return map;
}

因为要用到所以临时学的opencv,学的时间短,比较菜,希望对大家有所帮助,若有什么问题及时提出来。

你可能感兴趣的:(Opencv 几何图形颜色形状识别 计算面积 角度大小 边长比 JAVA)