앞서 만든 화면을 좀 고쳐 볼까 합니다.
[1] OpenCV-JavaGUI
저번에 OpenCV 자바 라이브러리를 활용한 GUI 를 만들어 보고 있었습니다. 근데, 만들다 보니 수작업...
blog.naver.com
4. OpenCV JavaGUI 수정
좀 고쳐 본 화면은 다음과 같이 만들어 보았습니다.

5. JavaGUI 실행 전략
기본적인 흐름은 다음과 같이 진행을 해 보면 어떨까 합니다.
- 콤보박스에서 서비스 타입을 선택한다. 이 때, 선택 할 수 있는 것은 이미지, 동영상 그리고 카메라 이다.
- select 버튼을 이용해서 1. 에서 선택한 서비스 타입이 이미지 동영상일 경우 파일 경로를 사용자로 부터 입력 받는다.
- 이미지나 동영상 등이 선택이 되면, 'start' 버튼을 눌러서 실행 한다.
- 'Stop' 버튼은 해당 타입의 파일의 진행을 중지 한다.
- 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();
	}
}결과 화면을 먼저 보도록 하겠습니다.

그냥 보면 잘 나온 것처럼 보이지만 전체 이미지가 안 나온 모양새 입니다.
여기에서 여러가지 판단을 할 수가 있을 것입니다.
- 원본 이미지를 살리기 - 스크롤을 달아서 원본이미지를 그대로 보여준다.
- BufferedImage 크기를 재조정 한다.(resize)
- 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
'프로그래밍 > [Java] OpenCV' 카테고리의 다른 글
| [Java] OpenCV, 프로그래머- 물체인식 Simulator 성능 비교 (0) | 2024.01.31 | 
|---|---|
| [1] OpenCV-JavaGUI (2) | 2024.01.15 | 
| OpenCV와 자바 - 4.3. 카메라 조작 (0) | 2022.09.17 | 
| OpenCV와 자바 - 4.2. 동영상 조작 (0) | 2022.09.16 | 
| OpenCV와 자바 - 4.1. 이미지 조작 - 2 (1) | 2022.09.15 | 
