使用JUI检测图像中的黑色圆圈(不仅仅是像素)

时间:2013-03-24 11:13:10

标签: java imaging

我的图像上有黑色圆圈。

图像是调查表的扫描副本,非常类似于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负责这一点,但由于某种原因,我无法得到我的调查使用该代码运行的模板。

3 个答案:

答案 0 :(得分:4)

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

我接近这个的方法分两步。

1)聚类像素。创建一个名为Cluster的类,它包含一个点列表,并使用您的聚类算法将所有点放在正确的聚类中。

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

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

我希望我能得到一些帮助,祝你好运!

答案 1 :(得分:1)

为了回答你的第一个问题,我创建了一个用于检查天气的类,一个图像包含一个非黑色填充的黑色轮廓圆。 这个类是实验性的,它不会始终提供准确的结果,随时编辑它并纠正您可能遇到的错误。 setter不检查空值或超出范围值。

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());
    }
}

答案 2 :(得分:0)

您需要在模板中对圆圈的外观进行编程,然后对其进行扩展以适应不同的圆圈尺寸。

例如,半径为3的圆将是:

  o
 ooo
  o

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

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