1

我有一个带有黑色圆圈的图像。

该图像是调查表的扫描副本,非常类似于 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 会处理这个问题,但由于某种原因,我无法让我的调查模板与该代码一起运行。

4

3 回答 3

4

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

我的方法是分两个步骤。

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

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

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

我希望我能有所帮助,祝你好运!

于 2013-04-03T02:02:12.530 回答
1

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

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());
    }
}
于 2013-04-03T12:24:10.737 回答
0

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

例如半径为 3 的圆将是:

  o
 ooo
  o

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

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

于 2013-03-24T11:33:27.600 回答