[2] OpenCV-JavaGUI

2024. 1. 16. 09:54프로그래밍/[Java] OpenCV

728x90

4. OpenCV JavaGUI 수정

좀 고쳐 본 화면은 다음과 같이 만들어 보았습니다.

5. JavaGUI 실행 전략

기본적인 흐름은 다음과 같이 진행을 해 보면 어떨까 합니다.

 

  1. 콤보박스에서 서비스 타입을 선택한다. 이 때, 선택 할 수 있는 것은 이미지, 동영상 그리고 카메라 이다.
  2. select 버튼을 이용해서 1. 에서 선택한 서비스 타입이 이미지 동영상일 경우 파일 경로를 사용자로 부터 입력 받는다.
  3. 이미지나 동영상 등이 선택이 되면, 'start' 버튼을 눌러서 실행 한다.
  4. 'Stop' 버튼은 해당 타입의 파일의 진행을 중지 한다.
  5. Exit 는 전체 프로그램을 종료 하는 데 사용한다.

 

파일을 선택 하는 것은 JFileChooser 를 사용 하도록 하겠습니다.

final String initialDir = "C:\\DEV\\DATA\\opencv";
btnSelect.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {

        boolean isDone = false;
        File file = null;
        int retValue = JFileChooser.FILES_ONLY;
        JFileChooser fchooser = new JFileChooser(initialDir);

        //"Image", "Movie", "Camera"
        if(cmbCapType.getSelectedItem().equals("Image"))
        {
            txtCapType.setEnabled(true);

            FileNameExtensionFilter filter = new FileNameExtensionFilter("Images", "jpeg", "jpg", "png");
            fchooser.setFileFilter(filter);
        }
        else if(cmbCapType.getSelectedItem().equals("Movie"))
        {
            txtCapType.setEnabled(true);
            FileNameExtensionFilter filter = new FileNameExtensionFilter("Movies", "mpeg", "mpg", "avi", "mp4");
            fchooser.setFileFilter(filter);
        }
        else if(cmbCapType.getSelectedItem().equals("Camera"))
        {
            txtCapType.setEnabled(false);
        }

        retValue = fchooser.showOpenDialog(null);
        switch(retValue)
        {
        case JFileChooser.APPROVE_OPTION :
            file = fchooser.getSelectedFile();
            txtCapType.setText(fchooser.getSelectedFile().getAbsolutePath());
    //					System.out.println("You chose to open this file: " + chooser.getSelectedFile().getName())
            break;
        case JFileChooser.CANCEL_OPTION:
            isDone = true;
            break;
        }

        if(isDone);
    }});
 

여기서 btnSelect 는 JButton 입니다.

cmbCapType은 사용자가 선택한 타입이면, 이미지, 동영상 혹은 카메라를 선택 할 수 있습니다.

 

그럼 여기서, 이미지의 경우에는 동영상이나 카메라에서 받아오는 프레임 개념이 아니라서 우선 이미지를 위한 Mat 을 얻어오는 코드를 작성 해야 합니다.

Mat img = Imgcodecs.imread("path/to/img");
 

코드가 약간 복잡해 지기 시작하는 군요...

 

6. 사용자 GUI

사용자에 대한 그래픽 유저 인터페이스 코드는 다음과 같은 형태 입니다.

public GUIOpenCV() {
	setTitle("OpenCV GUI");
	setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	setBounds(100, 100, 871, 578);
	contentPane = new JPanel();
	contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
	setContentPane(contentPane);
	contentPane.setLayout(null);
	
	ICvController cvController;
	
	JPanel cvPanel = new CvPanel();
	cvController = (ICvController)(cvPanel);
	
	cvPanel.setBorder(new BevelBorder(BevelBorder.LOWERED, null, null, null, null));
	cvPanel.setBounds(12, 10, 704, 430);
	contentPane.add(cvPanel);
	
	JScrollPane scrollPane = new JScrollPane();
	cvPanel.add(scrollPane);
	
	JPanel FuncPanel = new JPanel();
	FuncPanel.setBounds(12, 450, 833, 81);
	contentPane.add(FuncPanel);
	FuncPanel.setLayout(null);
	
	JComboBox<String> cmbCapType = new JComboBox<String>();
	cmbCapType.setModel(new DefaultComboBoxModel<String>(new String[] {"Image", "Movie", "Camera"}));
	cmbCapType.setBounds(12, 23, 103, 35);
	FuncPanel.add(cmbCapType);
	
	JButton btnGo = new JButton("Go");
	
	btnGo.setBounds(614, 23, 91, 35);
	FuncPanel.add(btnGo);
	
	txtCapType = new JTextField();
	txtCapType.setBounds(127, 23, 380, 35);
	FuncPanel.add(txtCapType);
	txtCapType.setColumns(10);
	
	JButton btnSelect = new JButton("Select");
	btnSelect.setBounds(519, 23, 91, 35);
	FuncPanel.add(btnSelect);
	
	JPanel CtrlPanel = new JPanel();
	CtrlPanel.setBounds(728, 10, 117, 430);
	contentPane.add(CtrlPanel);
	CtrlPanel.setLayout(null);
	
	JButton btnStop = new JButton("Stop");
	btnStop.setBounds(12, 55, 91, 35);
	CtrlPanel.add(btnStop);
	
	
	JButton btnStart = new JButton("Start");
	btnStart.setBounds(12, 10, 91, 35);
	CtrlPanel.add(btnStart);
	
	JButton btnExit = new JButton("Exit");
	btnExit.setBounds(12, 102, 91, 35);
	CtrlPanel.add(btnExit);
	
	... 액션 리스너...
}
 

유의해서 볼 부분은 첫 번째 패널을 생성하는 부분 입니다.

	ICvController cvController;
	
	JPanel cvPanel = new CvPanel();
	cvController = (ICvController)(cvPanel);
 

별로 좋아 보이지는 않지만, 일단 CvPanel 클래스는 인터페이스로 다음 행동을 가지도록 만들었습니다.

interface ICvController
{
	boolean startCvBufferRendering(CvRenderType rndType, String filePath);
	boolean stopCvBufferRendering();
}
 

말 그대로 버퍼에서 그리거나, 그리기를 중단 하는 것입니다.

 

두번째로 콤보박스에 타입을 넣은 부분 인데요. 긴 이야기는 필요 없을 것 같습니다.

JComboBox<String> cmbCapType = new JComboBox<String>();
cmbCapType.setModel(
   new DefaultComboBoxModel<String>(new String[] {"Image", "Movie", "Camera"}));
 

세 번째로 액션 리스너를 집어 넣어 완성해 줍니다.

final String initialDir = "C:\\DEV\\DATA\\opencv";

btnExit.addActionListener(new ActionListener() {
	@Override
	public void actionPerformed(ActionEvent e) {
		cvController.stopCvBufferRendering();
		dispose();
		System.exit(0);
		
	}});

btnStop.addActionListener(new ActionListener() {
	@Override
	public void actionPerformed(ActionEvent e) {
		cvController.stopCvBufferRendering();
	}});

btnStart.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent e) {
		
		//"Image", "Movie", "Camera"
		if(cmbCapType.getSelectedItem().equals("Image"))
		{
			txtCapType.setEnabled(true);
			String imagePath = txtCapType.getText().trim();
			
			if(imagePath == null || imagePath.isEmpty() || imagePath.isBlank())
			{
				JOptionPane.showMessageDialog(null, "No Image file found");
				return;
			}

			cvController.startCvBufferRendering(CvRenderType.RENDER_IMAGE, imagePath);
		}
		else if(cmbCapType.getSelectedItem().equals("Movie"))
		{
			txtCapType.setEnabled(true);
			String moviePath = txtCapType.getText().trim();
			
			if(moviePath == null || moviePath.isEmpty() || moviePath.isBlank())
			{
				JOptionPane.showMessageDialog(null, "No Movie file found");
				return;
			}
			
			if(moviePath.endsWith("mp4") || moviePath.endsWith("mpg") || moviePath.endsWith("avi"))
			{
				cvController.startCvBufferRendering(CvRenderType.RENDER_MOVIE, moviePath);
			}
			else
			{
				JOptionPane.showMessageDialog(null, "Not a movie file!!");
			}
		}
		else if(cmbCapType.getSelectedItem().equals("Camera"))
		{
			txtCapType.setEnabled(false);
			cvController.startCvBufferRendering(CvRenderType.RENDER_CAMERA, null);
		}
		
	}});

btnSelect.addActionListener(new ActionListener() {
	@Override
	public void actionPerformed(ActionEvent e) {
		
		boolean isDone = false;
		File file = null;
		int retValue = JFileChooser.FILES_ONLY;
		JFileChooser fchooser = new JFileChooser(initialDir);
		FileNameExtensionFilter filter = null;
		
		//"Image", "Movie", "Camera"
		if(cmbCapType.getSelectedItem().equals("Image"))
		{
			txtCapType.setEnabled(true);
			filter = new FileNameExtensionFilter("Image Files", "jpeg", "jpg", "png");
		}
		else if(cmbCapType.getSelectedItem().equals("Movie"))
		{
			txtCapType.setEnabled(true);
			filter = new FileNameExtensionFilter("Movie Files", "mpeg", "mpg", "avi", "mp4");
		}
		else if(cmbCapType.getSelectedItem().equals("Camera"))
		{
			txtCapType.setEnabled(false);
		}
		//fchooser.setFileFilter(filter);
		fchooser.addChoosableFileFilter(filter);
	
		retValue = fchooser.showOpenDialog(null);
		switch(retValue)
		{
		case JFileChooser.APPROVE_OPTION :
			file = fchooser.getSelectedFile();
			txtCapType.setText(fchooser.getSelectedFile().getAbsolutePath());
			break;
		case JFileChooser.CANCEL_OPTION:
			isDone = true;
			break;
		}

		if(isDone);
	}});
 

Start 버튼을 이용해서 프로그램을 실행 해 볼 수 있습니다.

 

7. 이미지 재 조정

그런데, 문제가 하나 발생 했는 데요(계속 발생 할 것이지만 ^^;;) 이미지 크기가 크거나 작을 경우에 패널에 이미지가 모두 들어가지 않는 문제가 발생 하는 것입니다.

패널을 만드는 CvPanel 클래스는 JPanel 에서 상속을 받았습니다. 물론 그러한 이유로 paintCompnent 를 사용 할 수가 있습니다.

이 클래스의 전체 코드는 다음과 같습니다.

final static class CvPanel extends JPanel implements ICvBufferRenderer, ICvController {
	private static final long serialVersionUID = -1248475680851582365L;

	private static OpenCVRender cvRenderer;
	private BufferedImage bufImagew;
	
	public CvPanel()
	{
		if(cvRenderer == null)
			cvRenderer = new OpenCVRender(this);
	}
	
	public boolean startCvBufferRendering(CvRenderType type, String filePath)
	{
		cvRenderer.renderFrame(type, filePath);
		return true;
	}
	
	public boolean stopCvBufferRendering()
	{
		cvRenderer.stopRenderFrame();
		return true;
	}
	
	
	public void setMovieToPanel(String filePath)
	{
		cvRenderer.stopRenderFrame();
		Manager.setHint( Manager.LIGHTWEIGHT_RENDERER, true );

        try{
            Player mediaPlayer = Manager.createRealizedPlayer( new File(filePath).toURI().toURL() );

            Component video = mediaPlayer.getVisualComponent();
            Component controls = mediaPlayer.getControlPanelComponent();

            if ( video != null )
                add( video, BorderLayout.CENTER ); //add video component
            if ( controls != null )
                add( controls, BorderLayout.SOUTH ); //add controls

                mediaPlayer.start(); //start playing the media clip
        } //end try
        catch ( NoPlayerException noPlayerException ){
            //JOptionPane.showMessageDialog(null, "No media player found");
        } //end catch
        catch ( CannotRealizeException cannotRealizeException ){
            //JOptionPane.showMessageDialog(null, "Could not realize media player.");
        } //end catch
        catch ( IOException iOException ){
            //JOptionPane.showMessageDialog(null, "Error reading from the source.");
        } //end catch
	}
	
	
	@Override
	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);
		try {  	
			if(bufImagew != null)
			{
			    g.drawImage(bufImagew,0,0,null);
            }
				
			else
				System.out.println("buffer is null!!!");

		}
		catch(Exception e){}
	}
	
	@Override
	public void renderCvBuffer(BufferedImage bufImage) {
		bufImagew = bufImage;
		repaint();
	}
}
 

결과 화면을 먼저 보도록 하겠습니다.

그냥 보면 잘 나온 것처럼 보이지만 전체 이미지가 안 나온 모양새 입니다.

여기에서 여러가지 판단을 할 수가 있을 것입니다.

 

  1. 원본 이미지를 살리기 - 스크롤을 달아서 원본이미지를 그대로 보여준다.
  2. BufferedImage 크기를 재조정 한다.(resize)
  3. BufferedImage 를 Mat 으로 변경해서 크기를 재조정한다.

 

1번의 경우는 건너뛰고, 2번 3번을 살펴보면

우선 2번 BufferedImage 크기를 재조정 한다는 다음처럼 참고사이트1 코드로 작성 할 수 있습니다.

Dimension imageD = getSize();
					
int paneWidth = imageD.width;
int paneHeight = imageD.height;

bufImagew = ImageUtil.resize(bufImagew, paneWidth, paneHeight);

g.drawImage(bufImagew,0,0,null);
 
public static BufferedImage resize(BufferedImage img, int newW, int newH) { 
    Image tmp = img.getScaledInstance(newW, newH, Image.SCALE_SMOOTH);
    BufferedImage dimg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB);

    Graphics2D g2d = dimg.createGraphics();
    g2d.drawImage(tmp, 0, 0, null);
    g2d.dispose();

    return dimg;
}
 

3 번의 경우 mat으로 변환하여 크기를 줄여 주는 것입니다.

Dimension imageD = getSize();
					
int paneWidth = imageD.width;
int paneHeight = imageD.height;

Mat resizeimage = new Mat();
Mat srcImg = ImageUtil.BufferedImage2Mat(bufImagew);

if(paneHeight == srcImg.rows() &&
	paneWidth == srcImg.cols())
{
	g.drawImage(bufImagew,0,0,null);
	bufImagew.flush();
}
else
{
	Size sz = new Size(paneWidth,paneHeight);
  Imgproc.resize( srcImg, resizeimage, sz );
  
  bufImagew.flush();
  bufImagew = null;
  
  bufImagew = ImageUtil.Mat2BufferedImage(resizeimage);
  
	g.drawImage(bufImagew,0,0,null);
	bufImagew.flush();
	srcImg.release();
	resizeimage.release();
	sz = null;
}

imageD = null;
 

위의 코드는 소스를 좀 잘 못 작성한 면도 있게으나, 그닥 추천하는 방법은 아닙니다.

Mat을 BufferedImage로 변환하는 방법은 많이 있습니다.

참고사이트2를 활용한 코드 입니다.

public static BufferedImage Mat2BufferedImage(Mat matrix)throws IOException {
    MatOfByte mob=new MatOfByte();
    Imgcodecs.imencode(".jpg", matrix, mob);
    return ImageIO.read(new ByteArrayInputStream(mob.toArray()));
}

public static Mat BufferedImage2Mat(BufferedImage image) throws IOException {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ImageIO.write(image, "jpg", byteArrayOutputStream);
    byteArrayOutputStream.flush();
    return Imgcodecs.imdecode(new MatOfByte(byteArrayOutputStream.toByteArray()), Imgcodecs.IMREAD_UNCHANGED);
}
 

결론적으로 큰 이미지를 줄여 보았습니다 작은 이미지를 해보지는 않았습니다.

 

 

참고사이트 1.

 

Bufferedimage resize

I am trying to resized a bufferedimage. I am able to store it and show up on a jframe no problems but I can't seem to resize it. Any tips on how I can change this to make it work and show the image...

stackoverflow.com

 
728x90