앞서 만든 화면을 좀 고쳐 볼까 합니다.
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.
'프로그래밍 > [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 (0) | 2022.09.15 |