Perbaikan Deskewing Image

By | May 22, 2022
1,122 Views

Perbaikan Deskewing Image –  Grafika komputer yang sebelumnya pernah kita bahas yaitu Croping Image dengan Mouse Event yang masih ada pekerjaan utama untuk menganhandle objek/gambar yang terkena skewing. Skewing pernah juga kita bahas Corner Detection (python) serta sedikit menyinggung penerapannya dengan OpenCV Membuat Scanner Document Corner Detection OpenCV Java. Pada 2 judul postingan tersebut yaitu mencari 4 corner secara otomatis yaitu A, B, C, dan D sebagai titik referensi operasi deskewing.

Namun pada postingan yang kita bahas nanti, kita akan mengandalkan user untuk menentukan sendiri point A, B, C, dan D menggunakan mouse setelah sebelumnya dibahas pada grafika komputer untuk Croping Image dengan Mouse Event.

Tulisan ini dibagi menjadi beberapa serial yaitu

  1. Belajar OpenCV bagian 1 dan 2 Setting OpenCV di Java
  2. Grafika Komputer untuk Resize Object Secara Dinamis
  3. Croping Image dengan Mouse Event

Pada postingan tersebut akan kita perbaiki dengan drawPolyline() dan bagaimana teknik yang kita akan kerjakan agar drawPolyline() bersifat closed path secara otomatis. Fokus kita yaitu pada class Button yang telah kita buat pada Croping Image dengan Mouse Event dengan operasi drawPolyline()

Yuk kita mulai bahas mengenai drawPolyline() yang merupakan operasi grafik vektor pada Java  untuk membuat multi-line. Secara garis besar argument input pada function tersebut berupa array [] integer x, y. Misalkan saja kita akan membuat sebuah 3 garis yang saling terhubung dengan aturan koordinat layar x dan y sebagai berikut

 

int [] x = new int []{100,200,200};
int [] y = new int []{100,100,50};        
g.drawPolyline(x, y,x.length);

Pada contoh diatas, polyline masih bersifat open path, agar membentuk closed path, kita harus menambahkan point ke 3 sama dengan posisi ke 0 (ingat bahwa array dimulai dari index 0) menjadi bentuk segitiga!

int [] x = new int []{100,200,200,100};
int [] y = new int []{100,100,50,100};        
g.drawPolyline(x, y,x.length);

Sehingga dapat disimpulkan bahwa untuk membuat closed path maka posisi point x,y pertama dan yang paling akhir harus sama! Misalkan saja kita akan buat rectangle, maka butuh 5 point

g.setColor(Color.white);
int [] x = new int []{100,200,200,100};
int [] y = new int []{100,100,50,100};        
g.drawPolyline(x, y,x.length);

x = new int[]{100,200,200,50,50,100};
y = new int[]{300,200,200,90,200,300};
g.drawPolyline(x, y,x.length);

 

Ok, sekarang sudah paham ya, yuk sekarang kita buat event mouse ketika moved maka akan diikuti oleh sebuah oval vektor

class Button2 extends JButton{
    Image IMAGE;
    File FILE;
    Dimension DIMENSION;
    Point CURRENT;
    public Button2(){
        super();
        setSize(100,100);
        setText("Button");
        FILE = new File("D:/corner_gambar3.jpg");
        DIMENSION = new Dimension(100,100);//sebagai awalan saja
        
        this.addMouseListener(new MouseAdapter(){
            @Override
            public void mousePressed(MouseEvent e){

            }
        });
        
        this.addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseMoved(MouseEvent e){
                CURRENT = new Point(e.getX(),e.getY());
                repaint();        
            }
        });
    }
    @Override
    public void paint(Graphics g){
        IMAGE = new ImageIcon(FILE.getAbsolutePath()).getImage();
        g.drawImage(IMAGE,
                    0,
                    0,                        
                    DIMENSION.width,DIMENSION.height,
                    null);
        
        int diameter = 10;
        if(CURRENT!=null){
            g.setColor(Color.white);
            g.fillOval(CURRENT.x-diameter/2,CURRENT.y-diameter/2,diameter,diameter);
        }        
    }
};

Jadi setiap mouse digerakan, maka akan ada oval putih yang mengikuti, berikut wadah untuk menampung Button tersebut pada JFrame pada class Demo2

public class Demo2 {    
    public static void main(String[] args) {
        // TODO code application logic here
        JFrame frame = new JFrame();
        Button2 button = new Button2();    
        
        frame.setSize(new Dimension(500,500));
         
        frame.add(button);       
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.addComponentListener(new ComponentAdapter(){  
            @Override
            public void componentResized(ComponentEvent evt) {
                Dimension actualSize = frame.getContentPane().getSize();
                button.DIMENSION = actualSize;
            }
        });
        frame.setVisible(true);
    }
}

Sekarang kita tingkatkan lagi yaitu kita akan menerapkan polyline, untuk hal tersebut kita butuh arraylist untuk menampung setiap mouse clicked. Jadi akan dihitung! jika jumlah klik sudah mencapai 4 kali klik! maka tambahkan 1 lokasi terakhir sama dengan lokasi yang pertama. Perhatikan kode berikut jadi ketika ada klik mouse akan ditambahkan lokasi point nya tapi bila  sudah 4 kali klik hentikan dulu tidak boleh nambah

@Override
public void mousePressed(MouseEvent e){
    if(e.getButton()==MouseEvent.BUTTON3){
        //klik kanan
        point = new ArrayList();
        repaint();
        return;
    }
    //jangan nambah lagi kalau point sudah cukup
    if(point.size()>3){
        System.out.println("sudah 4 titik");
        return;
    }
    //setiap kali klik  akan tambah point
    point.add(new Point(e.getX(),e.getY()));
    repaint();
}

kemudian untuk menggambar terlebih dahulu akan check array list point bila tidak null, maka buat array x,y. Ketika sudah 4 kali klik! maka buat 1 point untuk ditambahkan diakhir dengan lokasi yang sama dengan pertama

if(point!=null){
    int [] x  = new int [point.size()];
    int [] y = new int [point.size()];
    for(int i=0;i<point.size();i++){
        x[i] = (int) point.get(i).getX();
        y[i] = (int) point.get(i).getY();
        g.setColor(Color.RED);
        g.fillOval(x[i]-diameter/2,y[i]-diameter/2,diameter,diameter);
    }
    g.setColor(Color.RED);
    g.drawPolyline(x, y,x.length);            
}
//jika sudah 4 kali, maka
//point ke 5 harus sama dengan point 1 biar closed
if(point.size()==4){
    //maka otomatis
    point.add(point.get(0));
}

Yuk kita lihat kode lengkapnya class Button2  berikut

class Button2 extends JButton{
    Image IMAGE;
    File FILE;
    Dimension DIMENSION;
    Point CURRENT;
    ArrayList <Point> point;
    public Button2(){
        super();
        setSize(100,100);
        setText("Button");
        FILE = new File("D:/corner_gambar3.jpg");
        DIMENSION = new Dimension(100,100);//sebagai awalan saja
        
        point = new ArrayList();
        this.addMouseListener(new MouseAdapter(){
            @Override
            public void mousePressed(MouseEvent e){
                if(e.getButton()==MouseEvent.BUTTON3){
                    //klik kanan
                    point = new ArrayList();
                    repaint();
                    return;
                }
                //jangan nambah lagi kalau point sudah cukup
                if(point.size()>3){
                    System.out.println("sudah 4 titik");
                    return;
                }
                //setiap kali klik  akan tambah point
                point.add(new Point(e.getX(),e.getY()));
                repaint();
            }
        });
        
        this.addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseMoved(MouseEvent e){
                CURRENT = new Point(e.getX(),e.getY());
                repaint();        
            }
        });
    }
    @Override
    public void paint(Graphics g){
        IMAGE = new ImageIcon(FILE.getAbsolutePath()).getImage();
        g.drawImage(IMAGE,
                    0,
                    0,                        
                    DIMENSION.width,DIMENSION.height,
                    null);
        
        int diameter = 10;
        
        if(CURRENT!=null){
            g.setColor(Color.white);
            g.fillOval(CURRENT.x-diameter/2,CURRENT.y-diameter/2,diameter,diameter);
        }
        if(point!=null){
            int [] x  = new int [point.size()];
            int [] y = new int [point.size()];
            for(int i=0;i<point.size();i++){
                x[i] = (int) point.get(i).getX();
                y[i] = (int) point.get(i).getY();
                g.setColor(Color.RED);
                g.fillOval(x[i]-diameter/2,y[i]-diameter/2,diameter,diameter);
            }
            g.setColor(Color.RED);
            g.drawPolyline(x, y,x.length);            
        }
        //jika sudah 4 kali, maka
        //point ke 5 harus sama dengan point 1 biar closed
        if(point.size()==4){
            //maka otomatis
            point.add(point.get(0));
        }
        
        
    }
};

ketika di run pada ketika setiap kali diklik akan membuat polyline secara otomatis

See also  Croping Image per Block

Yup pada tahap ini kalian sudah membuat corner detection secara manual sebagai dasar operasi deskewing, yuk kita tambahkan kode berikut agar tampil lokasi nya ketika klik kanan mouse

public void mousePressed(MouseEvent e){
    if(e.getButton()==MouseEvent.BUTTON3){
        for(int i = 0;i<point.size();i++){
            System.out.println("lokasi : "+point.get(i));
        }
        
        //klik kanan
        point = new ArrayList();
        repaint();
        return;
    }
    //jangan nambah lagi kalau point sudah cukup
    if(point.size()>3){
        System.out.println("sudah 4 titik");
        return;
    }
    //setiap kali klik  akan tambah point
    point.add(new Point(e.getX(),e.getY()));
    repaint();
}

hasilnya kalian bisa melihat ada 5 point (dimana point 1 dan 5 mempunyai letak yang sama)

lokasi : java.awt.Point[x=105,y=144]
lokasi : java.awt.Point[x=319,y=135]
lokasi : java.awt.Point[x=371,y=361]
lokasi : java.awt.Point[x=134,y=394]
lokasi : java.awt.Point[x=105,y=144]

Tapi saya punya aturan tersendiri untuk mempermudah membuat polygon akan closed path yaitu dimulai dari searah jarum jam sebagai gambaran berikut yaitu searah jarum jam

 

Karena kalau tidak seperti itu nanti malah bikin tanda silang

Deskewing menggunakan OpenCV

Sekarang kita lupakan diatas dulu, mari kita bahas mengenai operasi deskewing yang telah dibahas https://softscients.com/2022/02/16/membuat-scanner-document-corner-detection-opencv-java/ namun kita permudah dengan cara meminta user untuk meng input sendiri lokasi corner nya – Memperbaiki Deskewing perlu kita pahami mengenai lokasi A, B, C, dan D sebagai titik perspektif

  1. A: atas kiri
  2. B: atas kanan
  3. C: bawah kiri
  4. D: bawah kiri

yang diproyeksikan ulang terhadap A’, B’, C’, dan D’

Pada kasus diatas sebelumnya didapatkan informasi berikut (searah jarum jam)

lokasi : java.awt.Point[x=105,y=144] 
lokasi : java.awt.Point[x=319,y=135] 
lokasi : java.awt.Point[x=371,y=361] 
lokasi : java.awt.Point[x=134,y=394] 
lokasi : java.awt.Point[x=105,y=144]

atau bila saya tulis lagi sebagai berikut

  • A: lokasi : java.awt.Point[x=105,y=144]
  • B: lokasi : java.awt.Point[x=319,y=135]
  • C: lokasi : java.awt.Point[x=371,y=361]
  • D: lokasi : java.awt.Point[x=134,y=394]
  • E: lokasi : java.awt.Point[x=105,y=144]

kemudian untuk mengikuti aturan opencv kita akan ubah urutannya agar mengikuti kaidah yaitu mengubah lokasi C ditukar ke D

  • A: lokasi : java.awt.Point[x=105,y=144]
  • B: lokasi : java.awt.Point[x=319,y=135]
  • C: lokasi : java.awt.Point[x=134,y=394] (tadinya lokasi D)
  • D: lokasi : java.awt.Point[x=371,y=361] (tadinya lokasi C)
See also  7 Alasan Mengapa Bahasa Java masih bakal terus digunakan

sedangkan lokasi E tidak perlu digunakan! perhatikan kode berikut untuk melakukan operasi deskewing gambar menggunakan OpenCV

//jika point nya sudah ada A,B,C, dan D maka bisa buat simpan gambar!
if(point.size()==4){
    Imgcodecs imageCodecs = new Imgcodecs();
    //perspektif openCV
    org.opencv.core.Point[] pointSorted = new org.opencv.core.Point[4];
    pointSorted[0] = new org.opencv.core.Point(point.get(0).getX(),point.get(0).getY()); //A
    pointSorted[1] = new org.opencv.core.Point(point.get(1).getX(),point.get(1).getY()); //B
    pointSorted[2] = new org.opencv.core.Point(point.get(3).getX(),point.get(3).getY()); //C penukaran lokasi
    pointSorted[3] = new org.opencv.core.Point(point.get(2).getX(),point.get(2).getY()); //D penukaran lokasi


    MatOfPoint2f pointSrc = new MatOfPoint2f(
    pointSorted[0], //atas kiri
    pointSorted[1], //atas kanan
    pointSorted[2], //bawah kiri
    pointSorted[3]); //bawah kanan


    double lebarObjekBagianAtas = pointSorted[1].x - pointSorted[0].x;
    double lebarObjekBagianBawah = pointSorted[3].x - pointSorted[2].x;
    //nanti dipilih yang lebar paling besar
    int lebar  = 0;
    if(lebarObjekBagianAtas>lebarObjekBagianBawah){
        lebar = (int)lebarObjekBagianAtas;
    }else{
        lebar = (int)lebarObjekBagianBawah;
    }

    double tinggiObjekBagianKiri = pointSorted[2].y - pointSorted[0].y;
    double tinggiObjekBagianKanan = pointSorted[3].y - pointSorted[1].y;

    //pilih tinggi yang paling besar
    int tinggi = 0;
    if(tinggiObjekBagianKiri>tinggiObjekBagianKanan){
        tinggi = (int) tinggiObjekBagianKiri;
    }else{
        tinggi = (int) tinggiObjekBagianKanan;
    }

    int margin = 5;

    MatOfPoint2f pointDst = new MatOfPoint2f(
            new org.opencv.core.Point(margin, margin), //atas kiri
            new org.opencv.core.Point(lebar,margin), //atas kanan
            new org.opencv.core.Point(margin,tinggi-margin), // bawah kiri
            new org.opencv.core.Point(lebar-margin,tinggi-margin)// bawah kanan
            );
    Mat matWrap = Imgproc.getPerspectiveTransform(pointSrc,pointDst);

    Mat matDeskewing = new Mat();

    //baca file gambar
    Mat matSrc = Imgcodecs.imread(FILE.getAbsolutePath(),1);
    int tinggi_semula = matSrc.height();
    int lebar_semula = matSrc.width();
    //lakukan resize agar sama dengan ukuran window saat ini
    //berguna untuk menyesuaikan posisi corner
    Imgproc.resize(matSrc, matSrc, new Size(DIMENSION.width,DIMENSION.height),Imgproc.INTER_LINEAR);

    //lakukan deskewing
    Imgproc.warpPerspective(matSrc, matDeskewing, matWrap, new Size(lebar+margin,tinggi+margin));
    //ubah lagi ke ukuran semula
    Imgproc.resize(matDeskewing, matDeskewing, new Size(lebar_semula,tinggi_semula));
    //gambar akan disimpan
    imageCodecs.imwrite("D:/out.jpg",matDeskewing);
                         
}

Kode lengkap Perbaikan Deskewing Image bisa kalian pelajari dibawah ini

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

/**
 *
 * @author User
 */
public class Demo2 {    
    public static void main(String[] args) {
        // TODO code application logic here
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        JFrame frame = new JFrame();
        Button2 button = new Button2();    
        
        frame.setSize(new Dimension(500,500));
         
        frame.add(button);       
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.addComponentListener(new ComponentAdapter(){  
            @Override
            public void componentResized(ComponentEvent evt) {
                Dimension actualSize = frame.getContentPane().getSize();
                button.DIMENSION = actualSize;
            }
        });
        frame.setVisible(true);
    }
}


class Button2 extends JButton{
    Image IMAGE;
    File FILE;
    Dimension DIMENSION;
    Point CURRENT;
    ArrayList <Point> point;
    public Button2(){
        super();
        setSize(100,100);
        setText("Button");
        FILE = new File("D:\\Data Lama\\Project Java\\Deskewing dan Mouse Event\\corner_gambar3.jpg");
        DIMENSION = new Dimension(100,100);//sebagai awalan saja
        
        point = new ArrayList();
        this.addMouseListener(new MouseAdapter(){
            @Override
            public void mousePressed(MouseEvent e){
                if(e.getButton()==MouseEvent.BUTTON3){ //artinya klik kanan!
                    for(int i = 0;i<point.size();i++){
                        System.out.println("lokasi : "+point.get(i));
                    }
                    
                    //jika point nya sudah ada A,B,C, dan D maka bisa buat simpan gambar!
                    if(point.size()==4){
                        Imgcodecs imageCodecs = new Imgcodecs();
                        //perspektif openCV
                        org.opencv.core.Point[] pointSorted = new org.opencv.core.Point[4];
                        pointSorted[0] = new org.opencv.core.Point(point.get(0).getX(),point.get(0).getY()); //A
                        pointSorted[1] = new org.opencv.core.Point(point.get(1).getX(),point.get(1).getY()); //B
                        pointSorted[2] = new org.opencv.core.Point(point.get(3).getX(),point.get(3).getY()); //C penukaran lokasi
                        pointSorted[3] = new org.opencv.core.Point(point.get(2).getX(),point.get(2).getY()); //D penukaran lokasi


                        MatOfPoint2f pointSrc = new MatOfPoint2f(
                        pointSorted[0], //atas kiri
                        pointSorted[1], //atas kanan
                        pointSorted[2], //bawah kiri
                        pointSorted[3]); //bawah kanan


                        double lebarObjekBagianAtas = pointSorted[1].x - pointSorted[0].x;
                        double lebarObjekBagianBawah = pointSorted[3].x - pointSorted[2].x;
                        //nanti dipilih yang lebar paling besar
                        int lebar  = 0;
                        if(lebarObjekBagianAtas>lebarObjekBagianBawah){
                            lebar = (int)lebarObjekBagianAtas;
                        }else{
                            lebar = (int)lebarObjekBagianBawah;
                        }

                        double tinggiObjekBagianKiri = pointSorted[2].y - pointSorted[0].y;
                        double tinggiObjekBagianKanan = pointSorted[3].y - pointSorted[1].y;

                        //pilih tinggi yang paling besar
                        int tinggi = 0;
                        if(tinggiObjekBagianKiri>tinggiObjekBagianKanan){
                            tinggi = (int) tinggiObjekBagianKiri;
                        }else{
                            tinggi = (int) tinggiObjekBagianKanan;
                        }

                        int margin = 5;

                        MatOfPoint2f pointDst = new MatOfPoint2f(
                                new org.opencv.core.Point(margin, margin), //atas kiri
                                new org.opencv.core.Point(lebar,margin), //atas kanan
                                new org.opencv.core.Point(margin,tinggi-margin), // bawah kiri
                                new org.opencv.core.Point(lebar-margin,tinggi-margin)// bawah kanan
                                );
                        Mat matWrap = Imgproc.getPerspectiveTransform(pointSrc,pointDst);

                        Mat matDeskewing = new Mat();

                        //baca file gambar
                        Mat matSrc = Imgcodecs.imread(FILE.getAbsolutePath(),1);
                        int tinggi_semula = matSrc.height();
                        int lebar_semula = matSrc.width();
                        //lakukan resize agar sama dengan ukuran window saat ini
                        //berguna untuk menyesuaikan posisi corner
                        Imgproc.resize(matSrc, matSrc, new Size(DIMENSION.width,DIMENSION.height),Imgproc.INTER_LINEAR);

                        //lakukan deskewing
                        Imgproc.warpPerspective(matSrc, matDeskewing, matWrap, new Size(lebar+margin,tinggi+margin));
                        //ubah lagi ke ukuran semula
                        Imgproc.resize(matDeskewing, matDeskewing, new Size(lebar_semula,tinggi_semula));
                        //gambar akan disimpan
                        imageCodecs.imwrite("D:/out.jpg",matDeskewing);
                                             
                    }

                    
                    //untuk menghapus titik yang ada!
                    //mulai baru
                    point = new ArrayList();
                    repaint();
                    return;
                }
                //jangan nambah lagi kalau point sudah cukup
                if(point.size()>3){
                    System.out.println("sudah 4 titik");
                    return;
                }
                //setiap kali klik  akan tambah point
                point.add(new Point(e.getX(),e.getY()));
                repaint();
            }
        });
        
        this.addMouseMotionListener(new MouseAdapter() {
            @Override
            public void mouseMoved(MouseEvent e){
                CURRENT = new Point(e.getX(),e.getY());
                repaint();        
            }
        });
    }
    @Override
    public void paint(Graphics g){
        IMAGE = new ImageIcon(FILE.getAbsolutePath()).getImage();
        g.drawImage(IMAGE,
                    0,
                    0,                        
                    DIMENSION.width,DIMENSION.height,
                    null);
        
        int diameter = 10;
        
        if(CURRENT!=null){
            g.setColor(Color.white);
            g.fillOval(CURRENT.x-diameter/2,CURRENT.y-diameter/2,diameter,diameter);
        }
        if(point!=null){
            int [] x  = new int [point.size()];
            int [] y = new int [point.size()];
            for(int i=0;i<point.size();i++){
                x[i] = (int) point.get(i).getX();
                y[i] = (int) point.get(i).getY();
                g.setColor(Color.RED);
                g.fillOval(x[i]-diameter/2,y[i]-diameter/2,diameter,diameter);
            }
            g.setColor(Color.RED);
            g.drawPolyline(x, y,x.length);            
        }
        //jika sudah 4 kali, maka
        //point ke 5 harus sama dengan point 1 biar closed
        if(point.size()==4){
            //maka otomatis
            point.add(point.get(0));
        }
        
        
    }
};

 

See also  Transformasi Hough Untuk Ekstraksi Iris Mata