OpenCV와 자바 - 2. 헬로 Camera

2022. 9. 9. 16:47프로그래밍/[Java] OpenCV

728x90

이제 노트북 카메라에서 입력을 받아서 화면에 뿌릴 수 있는 클래스를 만들어 보기도 하겠습니다.

이 클래스의 껍데기랑, 이력에 대해서는 언급을 했던 것으로 알고 있습니다.

그럼 다음 행동을 통해서 화면에 뿌리는 클래스에 접근 할 수 있을 것입니다.

private static JButton goCam = new JButton("Go camera~");
private static CamPanel camPane = new CamPanel();
private static CamCanvas camCavs = new CamCanvas();

public HelloCamera(final boolean isCanvas) {
	setSize(new Dimension(600, 450));
	setPreferredSize(new Dimension(600, 450));
	
	initCamPanel(isCanvas);
	
	setVisible(true);
}

private void initCamPanel(final boolean isCanvas)
{
	if(!isCanvas)
	{
		camPane.setSize(new Dimension(600, 450));
		camPane.setPreferredSize(new Dimension(600, 450));
		setLayout(new BorderLayout());
		add(camPane, BorderLayout.CENTER);
		add(goCam, BorderLayout.SOUTH);
		goCam.setActionCommand("goCamPanel");
		goCam.addActionListener(new CamButtonClickListener());
	}
	else
	{
		camCavs.setSize(new Dimension(600, 450));
		camCavs.setPreferredSize(new Dimension(600, 450));
		setLayout(new BorderLayout());
		add(camCavs, BorderLayout.CENTER);
		add(goCam, BorderLayout.SOUTH);
		goCam.setActionCommand("goCamCanvas");
		goCam.addActionListener(new CamButtonClickListener());
	}
}
 

화면을 그리는 클래스를 만드는 방법은 크게는 두가지가 있을 것 같습니다.

하나는 JPanel, 그 나머지 하나는 Canvas 입니다.

그냥 두 개를 다 만들어 보기로 했습니다.

두 개를 만들기 위해서 참고 한 사이트들은 부록에 있습니다.

 

우선 CamPanel과 CamCanvas 에 대한 소스 입니다.

 

CamPanel.java

final static class CamPanel extends JPanel {
	private static final long serialVersionUID = -1248475680851582365L;

	// a flag to change the button behavior
	private boolean cameraActive = false;
	private BufferedImage bufImagew;

	@Override
	public void paintComponent(Graphics g)
	{
		try {  	
			super.paintComponent(g);
			if(bufImagew != null)
				g.drawImage(bufImagew,0,0,null);
			else
				System.out.println("buffer is null!!!");

		}
		catch(Exception e){}
	}

	protected void startCamera() {
		
		if (!this.cameraActive) {
			// start the video capture
			InnerUtil.openCam();
			
			// is the video stream available?
			if (InnerUtil.isCamOpened()) {
				cameraActive = true;

				// grab a frame every 33 ms (30 frames/sec)
				Runnable frameGrabber = new Runnable() {

					@Override
					public void run() {
						// effectively grab and process a single frame
						Mat frame = InnerUtil.grabFrame();
						// convert and show the frame
						bufImagew = Utils.matToBufferedImage(frame);
						//System.out.println("3 " + bufImagew.getWidth() + ":" + bufImagew.getWidth());
						// updateImageView(currentFrame, imageToShow);

						repaint();
						// invalidate();
					}
				};
				InnerUtil.scheduleStart(frameGrabber);
				
				// update the button content
				goCam.setText("Stop Camera");
			} else {
				// log the error
				System.err.println("Impossible to open the camera connection...");
			}
		} else {
			// the camera is not active at this point
			this.cameraActive = false;
			// update again the button content
			goCam.setText("Start Camera");

			// stop the timer
			InnerUtil.stopAcquisition();
		}
	}

}
 

CamCanvas.java

final static class CamCanvas extends Canvas {
	private static final long serialVersionUID = 1235525630469980081L;
	
	// a flag to change the button behavior
	private boolean cameraActive = false;
	private BufferedImage bufImagew;
	
	@Override
	public void paint(Graphics g) {
		Graphics2D g2;
		g2 = (Graphics2D) g;

		if (bufImagew != null) 
		{
			g2.drawImage(bufImagew, 0, 0, getWidth(), getHeight(), null);
		}
		
		//g2.drawString ("This is is a string", 0 + 10, getHeight() - 10);
	}

	protected void startCamera() {
		... CamPanel 과 같음
	}
}
 

그리고 공통 메서드를 넣어 두기 위해서 유틸 클래스인 InnerUtil 을 하나 아래 와 같이 만들었습니다.

private final static class InnerUtil
{
	static {
		System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
	}
	
	// a timer for acquiring the video stream
	private static ScheduledExecutorService timer;
	// the OpenCV object that realizes the video capture
	private static VideoCapture capture = new VideoCapture();
	// the id of the camera to be used
	private static int cameraId = 0;
			
	static void openCam()
	{
		capture.open(cameraId);
	}
	
	static boolean isCamOpened()
	{
		return capture.isOpened();
	}
	
	static void scheduleStart(Runnable frameGrabber)
	{
		timer = Executors.newSingleThreadScheduledExecutor();
		timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);
	}
	
	static Mat grabFrame() {
		// init everything
		Mat frame = new Mat();

		// check if the capture is open
		if (capture.isOpened()) {
			try {
				// read the current frame
				capture.read(frame);

				// if the frame is not empty, process it
				if (!frame.empty()) {
					Imgproc.cvtColor(frame, frame, Imgproc.COLOR_BGR2GRAY);
				}

			} catch (Exception e) {
				// log the error
				System.err.println("Exception during the image elaboration: " + e);
			}
		}

		return frame;
	}

	/**
	 * Stop the acquisition from the camera and release all the resources
	 */
	static void stopAcquisition() {
		if (timer != null && !timer.isShutdown()) {
			try {
				// stop the timer
				timer.shutdown();
				timer.awaitTermination(33, TimeUnit.MILLISECONDS);
			} catch (InterruptedException e) {
				// log any exception
				System.err.println(
						"Exception in stopping the frame capture, trying to release the camera now... " + e);
			}
		}

		if (capture.isOpened()) {
			// release the camera
			capture.release();
		}
	}
}
 

이제 모든 준비는 끝난 것 같네요.

그럼 패널위에다 화면을 표출 한 것을 먼저 보도록 하겠습니다.

JPanel 을 이용한 OpenCV 캠

 

그 다음으로 Canvas 를 이용한 캠 기능 활성화 입니다.

 

 

Canvas 를 이용한 OpenCV 캠 기능 활성화

 

두 개의 메서드가 구현 된 모양은 거의 비슷해 보이기는 하지만

paint와 paintComponent 메서드는 좀 달라 보이기는 합니다.

 

Canvas로 그려 놓은 것이 심하게 화면이 깜박거리네요~

거의 모든 사람들이 알겠지만 이 때 필요한 것이 바로 더블 버퍼 링이라는 거겠죠..

가만히 생각해 보면 카메라에서 쉼없이 변화되어 나오는 그림 파일을 계속 실시간으로 그려 준다는 것이 아마 이런 깜박임이 나타나는 현상이기 때문이겠죠.

그럼, 일단 그려 놓고 다 그려 지면 보여 주면 되겠네? 라는 것이 이 더블 퍼버링의 주된 의미가 되겠습니다.

CamCanvas.java 파일을 다음 처럼 좀 바꿔 주면 되겠습니다.

final static class CamCanvas extends Canvas {
	private static final long serialVersionUID = 1235525630469980081L;
	
	// a flag to change the button behavior
	private boolean cameraActive = false;
	
	private BufferedImage bufImagew;
	
	//for duble buffering...
	@Override
	public void update(Graphics g) {
		Graphics2D g2;
		g2 = (Graphics2D) g;
		Graphics offgc;
		Image offscreen;
		
		if (bufImagew != null) {
			offscreen = createImage(bufImagew.getWidth(), bufImagew.getHeight());
			offgc = offscreen.getGraphics();
			// clear the exposed area
			offgc.setColor(getBackground());
			offgc.drawImage(bufImagew, 0, 0, getWidth(), getHeight(), null);
			offgc.setColor(getBackground());
			
			g2.drawImage(offscreen, 0, 0, getWidth(), getHeight(), null);
			//invalidate();
		}
		//g2.drawString ("This is is a string", 0 + 10, getHeight() - 10);
	}
	
	protected void startCamera() {
		
		... 생략
	}
}
 

그러고 나서 다시 한번 볼까요?

심하게 떨리던 화면을 더블 버퍼링으로 잡아 보았습니다.

 

 

간단하게(🥲) OpenCV 를 이용해서 할 수 있는 예제를 살펴 보았습니다.

많은 이야기가 나올 수도 있겠지만...

다음으로 미루기로 하고 이 정도면 뭔가 이 라이브러리를 사용하고 있다는 느낌이 들어가는 군요~

 

 

 

 

728x90