7

これが私の状況です。これには、スキャンされた画像の位置合わせが含まれます。これは、誤ったスキャンの原因となります。スキャンした画像を Java プログラムと位置合わせする必要があります。

詳細は次のとおりです。

  • 紙に印刷された表のようなフォームがあり、これをスキャンして画像ファイルに変換します。
  • Java で画像を開くと、テキスト ボックスのオーバーレイが表示されます。
  • テキスト ボックスは、スキャンした画像と正しく整列するはずです。
  • 正しく配置するために、私の Java プログラムはスキャンした画像を分析し、スキャンした画像上のテーブルの端の座標を検出し、画像とテキストボックスを配置して、テキストボックスと画像の両方が適切に配置されるようにする必要があります (誤ったスキャンの

おわかりのように、画像をスキャンしている人は必ずしも画像を完全に正しい位置に配置するとは限りません。そのため、スキャンした画像をロードするときに自動的に位置合わせするプログラムが必要です。このプログラムは、そのようなスキャンされた画像の多くで再利用できるため、このようにプログラムを柔軟にする必要があります。

私の質問は次のいずれかです。

  1. Java を使用してテーブルの上端の y 座標とテーブルの左端の x 座標を検出するにはどうすればよいですか。表は、多くのセルを含む通常の表で、黒い細い枠線が白い紙に印刷されています (横方向の印刷)。

  2. すべてのスキャンされた画像のグラフィカル テーブルが同じ x、y 座標に整列するように、スキャンされた画像を自動的に整列させる簡単な方法が存在する場合は、この方法を共有してください :)。

  3. 上記の質問に対する答えがわからない場合は、どこから始めればよいか教えてください。私はグラフィックスの Java プログラミングについてよく知りません。このプログラムを完成させるのに約 1 か月かかります。スケジュールがタイトで、グラフィック部分をできるだけシンプルにする必要があると仮定してください。

乾杯とありがとう。

4

4 に答える 4

5

単純なシナリオから始めて、アプローチを改善してください。

  1. 角を検出します。
  2. フォームの境界で角を見つけます。
  3. フォーム コーナーの座標を使用して、回転角度を計算します。
  4. 画像を回転/スケーリングします。
  5. フォームの原点座標を基準にして、フォーム内の各フィールドの位置をマッピングします。
  6. テキストボックスを一致させます。

この投稿の最後に提示されたプログラムは、ステップ 1 から 3 を実行します。これは、Marvin Frameworkを使用して実装されました。以下の画像は、コーナーが検出された出力画像を示しています。

ここに画像の説明を入力

プログラムは次の出力も出力します: Rotation angle:1.6365770416167182

ソースコード:

import java.awt.Color;
import java.awt.Point;
import marvin.image.MarvinImage;
import marvin.io.MarvinImageIO;
import marvin.plugin.MarvinImagePlugin;
import marvin.util.MarvinAttributes;
import marvin.util.MarvinPluginLoader;

public class FormCorners {

public FormCorners(){
    // Load plug-in
    MarvinImagePlugin moravec = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.corner.moravec");
    MarvinAttributes attr = new MarvinAttributes();

    // Load image
    MarvinImage image = MarvinImageIO.loadImage("./res/printedForm.jpg");

    // Process and save output image
    moravec.setAttribute("threshold", 2000);
    moravec.process(image, null, attr);
    Point[] boundaries = boundaries(attr);
    image = showCorners(image, boundaries, 12);
    MarvinImageIO.saveImage(image, "./res/printedForm_output.jpg");

    // Print rotation angle
    double angle =  (Math.atan2((boundaries[1].y*-1)-(boundaries[0].y*-1),boundaries[1].x-boundaries[0].x) * 180 / Math.PI);
    angle =  angle >= 0 ? angle : angle + 360;
    System.out.println("Rotation angle:"+angle);
}

private Point[] boundaries(MarvinAttributes attr){
    Point upLeft = new Point(-1,-1);
    Point upRight = new Point(-1,-1);
    Point bottomLeft = new Point(-1,-1);
    Point bottomRight = new Point(-1,-1);
    double ulDistance=9999,blDistance=9999,urDistance=9999,brDistance=9999;
    double tempDistance=-1;
    int[][] cornernessMap = (int[][]) attr.get("cornernessMap");

    for(int x=0; x<cornernessMap.length; x++){
        for(int y=0; y<cornernessMap[0].length; y++){
            if(cornernessMap[x][y] > 0){
                if((tempDistance = Point.distance(x, y, 0, 0)) < ulDistance){
                    upLeft.x = x; upLeft.y = y;
                    ulDistance = tempDistance;
                } 
                if((tempDistance = Point.distance(x, y, cornernessMap.length, 0)) < urDistance){
                    upRight.x = x; upRight.y = y;
                    urDistance = tempDistance;
                }
                if((tempDistance = Point.distance(x, y, 0, cornernessMap[0].length)) < blDistance){
                    bottomLeft.x = x; bottomLeft.y = y;
                    blDistance = tempDistance;
                }
                if((tempDistance = Point.distance(x, y, cornernessMap.length, cornernessMap[0].length)) < brDistance){
                    bottomRight.x = x; bottomRight.y = y;
                    brDistance = tempDistance;
                }
            }
        }
    }
    return new Point[]{upLeft, upRight, bottomRight, bottomLeft};
}

private MarvinImage showCorners(MarvinImage image, Point[] points, int rectSize){
    MarvinImage ret = image.clone();
    for(Point p:points){
        ret.fillRect(p.x-(rectSize/2), p.y-(rectSize/2), rectSize, rectSize, Color.red);
    }
    return ret;
}

public static void main(String[] args) {
    new FormCorners();
}
}
于 2013-09-29T14:45:20.263 に答える
1

ライブラリを調査しましたが、最終的には、独自のエッジ検出方法をコーディングする方が便利であることがわかりました。

以下のクラスは、スキャンされた用紙の黒/灰色のエッジを検出し、そのようなエッジを含む用紙のエッジの x 座標と y 座標を、右端 (reverse = true) または右端から開始して返します。下端から (reverse = true) または上端から (reverse = false) または左端から (reverse = false)。また...プログラムは、ピクセル単位で測定された垂直方向のエッジに沿った範囲 (rangex) と、ピクセル単位で測定された水平方向の範囲 (rangey) を取得します。範囲は、受け取ったポイントの外れ値を決定します。

プログラムは、指定された配列を使用して 4 つの垂直方向のカットと、4 つの水平方向のカットを行います。黒い点の値を取得します。範囲を使用して外れ値を排除します。場合によっては、用紙上の小さなスポットが外れ値になることがあります。範囲が狭いほど、外れ値が少なくなります。ただし、エッジがわずかに傾いている場合があるため、範囲を小さくしすぎないようにします。

楽しむ。それは私にとって完璧に機能します。

import java.awt.image.BufferedImage;
import java.awt.Color;
import java.util.ArrayList;
import java.lang.Math;
import java.awt.Point;
public class EdgeDetection {

    public App ap;
        public int[] horizontalCuts = {120, 220, 320, 420};
        public int[] verticalCuts = {300, 350, 375, 400};



    public void printEdgesTest(BufferedImage image, boolean reversex, boolean reversey, int rangex, int rangey){
        int[] mx = horizontalCuts;
        int[] my = verticalCuts;

            //you are getting edge points here
            //the "true" parameter indicates that it performs a cut starting at 0. (left edge)
        int[] xEdges = getEdges(image, mx, reversex, true);
        int edgex = getEdge(xEdges, rangex);
        for(int x = 0; x < xEdges.length; x++){
            System.out.println("EDGE = " + xEdges[x]);
        }
        System.out.println("THE EDGE = " + edgex);
            //the "false" parameter indicates you are doing your cut starting at the end (image.getHeight)
            //and ending at 0
            //if the parameter was true, it would mean it would start the cuts at y = 0
        int[] yEdges = getEdges(image, my, reversey, false);
        int edgey = getEdge(yEdges, rangey);
        for(int y = 0; y < yEdges.length; y++){
            System.out.println("EDGE = " + yEdges[y]);
        }
        System.out.println("THE EDGE = " + edgey);
    }

    //This function takes an array of coordinates...detects outliers, 
    //and computes the average of non-outlier points.

    public int getEdge(int[] edges, int range){
        ArrayList<Integer> result = new ArrayList<Integer>();
        boolean[] passes = new boolean[edges.length];
        int[][] differences = new int[edges.length][edges.length-1];
        //THIS CODE SEGMENT SAVES THE DIFFERENCES BETWEEN THE POINTS INTO AN ARRAY
        for(int n = 0; n<edges.length; n++){
            for(int m = 0; m<edges.length; m++){
                if(m < n){
                    differences[n][m] = edges[n] - edges[m];
                }else if(m > n){
                    differences[n][m-1] = edges[n] - edges[m];
                }
            }
        }
         //This array determines which points are outliers or nots (fall within range of other points)
        for(int n = 0; n<edges.length; n++){
            passes[n] = false;
            for(int m = 0; m<edges.length-1; m++){
                if(Math.abs(differences[n][m]) < range){
                    passes[n] = true;
                    System.out.println("EDGECHECK = TRUE" + n);
                    break;
                }
            }
        }
         //Create a new array only using valid points
        for(int i = 0; i<edges.length; i++){
            if(passes[i]){
                result.add(edges[i]);
            }
        }

        //Calculate the rounded mean... This will be the x/y coordinate of the edge
        //Whether they are x or y values depends on the "reverse" variable used to calculate the edges array
        int divisor = result.size();
        int addend = 0;
        double mean = 0;
        for(Integer i : result){
            addend += i;
        }
        mean = (double)addend/(double)divisor;

        //returns the mean of the valid points: this is the x or y coordinate of your calculated edge.
        if(mean - (int)mean >= .5){
            System.out.println("MEAN " + mean);
            return (int)mean+1;
        }else{
            System.out.println("MEAN " + mean);
            return (int)mean;
        }       
    }


     //this function computes "dark" points, which include light gray, to detect edges.
     //reverse - when true, starts counting from x = 0 or y = 0, and ends at image.getWidth or image.getHeight()
     //verticalEdge - determines whether you want to detect a vertical edge, or a horizontal edge
     //arr[] - determines the coordinates of the vertical or horizontal cuts you will do
     //set the arr[] array according to the graphical layout of your scanned image
     //image - this is the image you want to detect black/white edges of
    public int[] getEdges(BufferedImage image, int[] arr, boolean reverse, boolean verticalEdge){
        int red = 255;
        int green = 255;
        int blue = 255;
        int[] result = new int[arr.length];
        for(int n = 0; n<arr.length; n++){
            for(int m = reverse ? (verticalEdge ? image.getWidth():image.getHeight())-1:0; reverse ? m>=0:m<(verticalEdge ? image.getWidth():image.getHeight());){
                Color c = new Color(image.getRGB(verticalEdge ? m:arr[n], verticalEdge ? arr[n]:m));
                red = c.getRed();
                green = c.getGreen();
                blue = c.getBlue();
                        //determine if the point is considered "dark" or not.
                        //modify the range if you want to only include really dark spots.
                        //occasionally, though, the edge might be blurred out, and light gray helps
                if(red<239 && green<239 && blue<239){
                    result[n] = m;
                    break;
                }
                        //count forwards or backwards depending on reverse variable
                if(reverse){
                    m--;
                }else{
                    m++;
                }
            }
        }
    return result;
    }

}
于 2013-11-16T02:10:47.720 に答える