【问题标题】:Detect black circles(not just pixels) in a image using JUI使用 JUI 检测图像中的黑色圆圈(不仅仅是像素)
【发布时间】:2013-03-24 11:13:09
【问题描述】:

我有一张带有黑色圆圈的图片。

该图像是调查表的扫描副本,非常类似于 OMR 问卷表。

我想使用JUI检测被黑化的圆圈(如果需要任何其他api)

我在搜索时有几个例子,但它们没有给我准确的结果。

我试过..UDAI、Moodle...等等...

然后我决定自己做。我能够检测到黑色像素,但如下所示。

BufferedImage mapa = BMPDecoder.read(new File("testjui.bmp"));

             final int xmin = mapa.getMinX();
             final int ymin = mapa.getMinY();

             final int ymax = ymin + mapa.getHeight();
             final int xmax = xmin + mapa.getWidth();


             for (int i = xmin;i<xmax;i++)
             {
                for (int j = ymin;j<ymax;j++)
                {

                 int pixel = mapa.getRGB(i, j);

                 if ((pixel & 0x00FFFFFF) == 0)
                 {
                     System.out.println("("+i+","+j+")");
                 }
                }
             }

这给了我所有黑色像素的坐标,但我无法确定它是否是一个圆圈。

我如何识别它是否是一个圆圈。

2] 另外我想知道扫描的图像是否倾斜......我知道 Udai api 会处理这个问题,但由于某种原因我无法获得我的调查使用该代码运行的模板。

【问题讨论】:

    标签: java imaging


    【解决方案1】:

    所以,如果我理解正确的话,你有挑选黑色像素的代码,所以现在你有了所有黑色像素的坐标,并且你想确定所有落在一个圆圈上的像素。

    我的方法是分两步。

    1) 对像素进行聚类。创建一个名为 Cluster 的类,其中包含一个点列表,并使用您的聚类算法将所有点放入正确的集群中。

    2) 确定哪些簇是圆形。为此,请找到每个集群中所有点的中点(只需取所有点的平均值)。然后找到距中心的最小和最大距离,它们之间的差异应该小于文件中圆的最大厚度。这些将为您提供圆内包含的最内圈和最外圈的半径。现在使用圆方程 x^2 + y^2 = 半径,将半径设置为先前找到的最大值和最小值之间的值,以找到集群应包含的点。如果您的集群包含这些,则它是一个圆圈。

    当然要考虑的其他因素是您的形状是否近似椭圆而不是圆形,在这种情况下您应该使用椭圆方程。此外,如果您的文件包含类似圆形的形状,您将需要编写额外的代码来排除这些形状。另一方面,如果您所有的圆圈大小完全相同,您可以通过让算法仅搜索该大小的圆圈来减少需要完成的工作。

    希望能帮到你,祝你好运!

    【讨论】:

    • 我认为这种方法应该对我有用...但是您能否建议我使用 java 中的聚类算法来帮助...
    • 据我所知,在 Java 中没有任何算法可以满足您的需求。我要做的是自己编写一个算法,这将是 k-means 算法的一种变体。本质上,您需要为您的集群类编写一个名为 distanceFromCluster(Point p) 的方法,该方法遍历集群中的每个点 q 并计算从 p 到 q 的距离。此方法将允许您确定点 q 是否与集群足够近,如果是,则应将其添加到其中...
    • 然后这个过程应该包含在一个while循环中,并在所有点都在正确的簇中时终止 - 即在每次遍历中不再将点移动到新簇时。
    【解决方案2】:

    为了回答您的第一个问题,我创建了一个类来检查图像是否包含一个非黑色填充的黑色轮廓圆圈。 这个类是实验性的,它并不总是提供准确的结果,请随意编辑它并纠正你可能遇到的错误。 设置器不检查空值或超出范围的值。

    import java.awt.Point;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.imageio.ImageIO;
    
    /**
     * Checks weather an image contains a single non black filled black outlined circle<br />
     * This class is experimental, it does not provide exact results all the time, feel free to edit it and to correct
     * the bugs you might encounter.
     * @author      Ahmed KRAIEM
     * @version     0.9 alpha
     * @since       2013-04-03
     */
    public class CircleChecker {
    
        private BufferedImage image;
    
        /**
         * Points that are equal to the calculated radius±<code>radiusesErrorMargin%</code> are not considered rogue points.<br />
         * <code>radiusesErrorMargin</code> must be <code>>0 && <1</code>
         */
        private double radiusesErrorMargin = 0.2;
    
        /**
         * A shape that has fewer than roguePointSensitivity% of rogue points is considered a circle.<br />
         * <code>roguePointSensitivity</code> must be <code>>0 && <1</code>
         */
        private double roguePointSensitivity = 0.05;
        /**
         * The presumed circle is divided into <code>angleCompartimentPrecision</code> parts,<br />
         * each part must have <code>minPointsPerCompartiment</code> points
         * <code>angleCompartimentPrecision</code> must be <code>> 0</code>
         */
        private int angleCompartimentPrecision = 50;
        /**
         * The minimum number of points requiered to declare a part valid.<br />
         * <code>minPointsPerCompartiment</code> must be <code>> 0</code>
         */
        private int minPointsPerCompartiment = 20;
    
    
        public CircleChecker(BufferedImage image) {
            super();
            this.image = image;
        }
    
        public CircleChecker(BufferedImage image, double radiusesErrorMargin,
                int minPointsPerCompartiment, double roguePointSensitivity,
                int angleCompartimentPrecision) {
            this(image);
            this.radiusesErrorMargin = radiusesErrorMargin;
            this.minPointsPerCompartiment = minPointsPerCompartiment;
            this.roguePointSensitivity = roguePointSensitivity;
            this.angleCompartimentPrecision = angleCompartimentPrecision;
        }
    
        public BufferedImage getImage() {
            return image;
        }
    
        public void setImage(BufferedImage image) {
            this.image = image;
        }
    
        public double getRadiusesErrorMargin() {
            return radiusesErrorMargin;
        }
    
        public void setRadiusesErrorMargin(double radiusesErrorMargin) {
            this.radiusesErrorMargin = radiusesErrorMargin;
        }
    
        public double getMinPointsPerCompartiment() {
            return minPointsPerCompartiment;
        }
    
        public void setMinPointsPerCompartiment(int minPointsPerCompartiment) {
            this.minPointsPerCompartiment = minPointsPerCompartiment;
        }
    
        public double getRoguePointSensitivity() {
            return roguePointSensitivity;
        }
    
        public void setRoguePointSensitivity(double roguePointSensitivity) {
            this.roguePointSensitivity = roguePointSensitivity;
        }
    
        public int getAngleCompartimentPrecision() {
            return angleCompartimentPrecision;
        }
    
        public void setAngleCompartimentPrecision(int angleCompartimentPrecision) {
            this.angleCompartimentPrecision = angleCompartimentPrecision;
        }
    
        /**
         * 
         * @return true if the image contains no more than <code>roguePointSensitivity%</code> rogue points
         * and all the parts contain at least <code>minPointsPerCompartiment</code> points.
         */
        public boolean isCircle() {
            List<Point> list = new ArrayList<>();
            final int xmin = image.getMinX();
            final int ymin = image.getMinY();
    
            final int ymax = ymin + image.getHeight();
            final int xmax = xmin + image.getWidth();
    
            for (int i = xmin; i < xmax; i++) {
                for (int j = ymin; j < ymax; j++) {
    
                    int pixel = image.getRGB(i, j);
    
                    if ((pixel & 0x00FFFFFF) == 0) {
                        list.add(new Point(i, j));
                    }
                }
            }
            if (list.size() == 0)
                return false;
            double diameter = -1;
            Point p1 = list.get(0);
            Point across = null;
            for (Point p2 : list) {
                double d = distance(p1, p2);
                if (d > diameter) {
                    diameter = d;
                    across = p2;
                }
            }
            double radius = diameter / 2;
            Point center = center(p1, across);
            int diffs = 0;
    
            int diffsUntilError = (int) (list.size() * roguePointSensitivity);
            double minRadius = radius - radius * radiusesErrorMargin;
            double maxRadius = radius + radius * radiusesErrorMargin;
    
            int[] compartiments = new int[angleCompartimentPrecision];
    
    
            for (int i=0; i<list.size(); i++) {
                Point p = list.get(i);
                 double calRadius = distance(p, center);
                 if (calRadius>maxRadius || calRadius < minRadius)
                     diffs++;
                 else{
                     //Angle
                     double angle = Math.atan2(p.y -center.y,p.x-center.x);
                     //angle is between -pi and pi
                     int index = (int) ((angle + Math.PI)/(Math.PI * 2 / angleCompartimentPrecision));
                     compartiments[index]++;
                 }
                 if (diffs >= diffsUntilError){
                     return false;
                 }
            }
            int sumCompartiments = list.size() - diffs;
            for(int comp : compartiments){
                if (comp < minPointsPerCompartiment){
                    return false;
                }
            }
    
            return true;
        }
    
        private double distance(Point p1, Point p2) {
            return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
        }
    
        private Point center(Point p1, Point p2) {
            return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
        }
    
        public static void main(String[] args) throws IOException {
            BufferedImage image = ImageIO.read(new File("image.bmp"));
    
            CircleChecker cc = new CircleChecker(image);
    
            System.out.println(cc.isCircle());
        }
    }
    

    【讨论】:

      【解决方案3】:

      您需要在一个圆的外观模板中进行编程,然后使其可扩展以适应不同的圆大小。

      例如半径为 3 的圆是:

        o
       ooo
        o
      

      这假设您需要找到一组有限的圆,可能最大为 5x5 或 6x6,这是可行的。

      或者你可以使用:Midpoint circle algorithm
      这将涉及找到所有黑色像素组,然后为每个组选择中间像素。
      应用此算法,使用外部像素作为圆的大小的指导。
      找出黑色/预期黑色像素之间的差异。
      如果黑色与预期黑色的比例足够高,则为黑色圆圈,您可以将其删除/变白。

      【讨论】:

        猜你喜欢
        • 2015-07-25
        • 2012-02-15
        • 1970-01-01
        • 1970-01-01
        • 2011-12-25
        • 2011-12-11
        • 1970-01-01
        • 1970-01-01
        • 2020-04-09
        相关资源
        最近更新 更多