我的图像上有黑色圆圈。
图像是调查表的扫描副本,非常类似于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负责这一点,但由于某种原因,我无法得到我的调查使用该代码运行的模板。
答案 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
这将涉及找到所有黑色像素组,然后为每个像素组选择中间像素
应用此算法使用外部像素作为指导圆圈的大小。
查找黑色/预期黑色像素之间的差异
如果黑色与预期的黑色比例足够高,则为黑色圆圈,您可以删除/白化它。