[Java] OpenCV, 프로그래머- 물체인식 Simulator 성능 비교

2024. 1. 31. 10:37프로그래밍/[Java] OpenCV

728x90

저번에 만들어 본 시뮬레이터 아닌 시뮬레이터를 약간 수정 해 보았습니다. 혹시라도 맞춰보면 Yolov3 모델과 성능이 비슷해 질까봐 해 봤는 데, 결론은 모델 자체가 다르기 때문에 생기는 성능 문제라는 것을 확인 하는 과정이 되어 버렸습니다.

 

우선 가장 중요한 부분은 JPanel 에 랜더링 하는 부분인데요

private Runnable frameGrabber = new Runnable() {

    @Override
    public void run() {
        // System.out.println("RENDER_TYPE " + RENDER_TYPE);
        if (RENDER_TYPE == CvDisplayType.DISPLAY_IMAGE) {
            // System.out.println("3 " + bufImagew.getWidth() + ":" + bufImagew.getWidth());
        } else {
            // effectively grab and process a single frame
            Mat frame = grabFrame();
            // convert and show the frame
            bufImagew = MatToBufferedImage(frame);
            // System.out.println("3 " + bufImagew.getWidth() + ":" + bufImagew.getWidth());
            // updateImageView(currentFrame, imageToShow);
            // invalidate();
        }

        mRenderer.renderCvBuffer(bufImagew);
    }
};
 

위와 같이 스레드를 사용해서 진행이 됩니다.

위 스레드는 다음과 같이 스레드 풀을 사용하게 됩니다.

protected void startRenderingSchedule() {
    if (timer == null) {
        timer = Executors.newSingleThreadScheduledExecutor();
        timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);
    } else {
        stopRenderingShedule();

        timer = Executors.newSingleThreadScheduledExecutor();
        timer.scheduleAtFixedRate(frameGrabber, 0, 33, TimeUnit.MILLISECONDS);
    }
}
 

결국, grabFrame 메서드에서 MaskRCNN 으로 할지 아니면 Yolo 모델을 사용 할지를 정해서 다음과 같이 랜더링 하게 됩니다.

Mat grabFrame() {
    // init everything
    if (RENDER_TYPE == CvDisplayType.DISPLAY_IMAGE) {
        return null;
    } else {
        Mat frame = new Mat();

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

                if(DETECT_METHOD.getDetectorType() == CvDetectorType.METHOD_MRCNN)
                {
                    MaskRCNNDetector.detectByMRCnn(frame, DETECT_METHOD);
                }
                else if(DETECT_METHOD.getDetectorType() == CvDetectorType.METHOD_YOLOV3)
                {
                    Yolov3Detector.detectByYolov3(frame, DETECT_METHOD);
                }
            } catch (Exception e) {
                // log the error
                System.err.println("Exception during the image elaboration: " + e);
            }
        }

        return frame;
    }
}
 

아래 코드 아래 위로 성능측정을 진행해 보면 얼마나 차이가 나는 지가 나오게 되겠죠?

if(DETECT_METHOD.getDetectorType() == CvDetectorType.METHOD_MRCNN)
{
	MaskRCNNDetector.detectByMRCnn(frame, DETECT_METHOD);
}
else if(DETECT_METHOD.getDetectorType() == CvDetectorType.METHOD_YOLOV3)
{
	Yolov3Detector.detectByYolov3(frame, DETECT_METHOD);
}
 

성능 측정에 대해서는 다음 코드만 넣어 주면 초단위 측정이 가능하겠습니다.

long finish = System.nanoTime();
long timeElapsed = finish - start;
...
System.out.println("timeElapsed : " + (double)timeElapsed / 1000000000);
 

그렇게 측정한 결과입니다.

timeElapsed : 1.682264
timeElapsed : 1.5612101
timeElapsed : 1.5634674
timeElapsed : 1.6040274
timeElapsed : 1.6531054
timeElapsed : 1.6019592
timeElapsed : 1.5851752
timeElapsed : 1.5969582
timeElapsed : 1.671585
==========================================
timeElapsed : 0.865967
timeElapsed : 0.2725424
timeElapsed : 0.3000441
timeElapsed : 0.2516531
timeElapsed : 0.2840524
timeElapsed : 0.2719521
timeElapsed : 0.2765884
timeElapsed : 0.2712055
timeElapsed : 0.2602712
timeElapsed : 0.2672618
 

뭐라고 말을 하지 않아도 될 정도로 Yolo 모델이 거의 일초 이상 차이가 잘 정도로 압도적이네요...

도대체 어디가 이런 성능에 차이를 만들어 내는가를 확인 해 보기 위해서 다음과 같이 메서드들을 정리 해 보았습니다.

 

우선 Yolov3 모델의 메서드는 다음과 같이 정리 해 보았습니다.

public class Yolov3Detector {
	private static double confThreshold = 0.5; // Confidence threshold
	
	private static List<Mat> outputBlobs = new ArrayList<Mat>();
	private static CvDetectorType prevDetectorType = CvDetectorType.METHOD_UNKNWON;
	private static Net dnnNet;
	private static List<Scalar> colors;
	private static List<String> cocoLabels;
	private static HashMap<String, List> result = new HashMap<String, List>();
	private static List<String> layerNames;
	private static List<String> outBlobNames = new ArrayList<String>();
	
	static {
		result.put("boxes", new ArrayList<Rect2d>());
		result.put("confidences", new ArrayList<Float>());
		result.put("class_ids", new ArrayList<Integer>());
	}
	
	private static void init()
	{
		if(outBlobNames != null && outBlobNames.size() > 0)
		{
			outBlobNames.clear();
		}
	}
	
	public static void detectByYolov3(Mat frame, CvDetectorOpt detectOptions) {
		outputBlobs.clear();
		result.get("boxes").clear();
		result.get("confidences").clear();
		result.get("class_ids").clear();
		
		if(prevDetectorType != detectOptions.getDetectorType())
		{
			init();
			dnnNet = detectOptions.getNet();
			prevDetectorType = detectOptions.getDetectorType();
			
			colors = detectOptions.getColors();
			cocoLabels = detectOptions.getCocoLabels();
			
			layerNames = dnnNet.getLayerNames();
			
			for (Integer i : dnnNet.getUnconnectedOutLayers().toList()) {
				outBlobNames.add(layerNames.get(i - 1));
			}
		}
		
		Mat blob = Dnn.blobFromImage(frame, 1 / 255.0, new Size(416, 416),
				new Scalar(new double[] { 0.0, 0.0, 0.0 }), true, false);
		dnnNet.setInput(blob);

		// -- the output from network's forward() method will contain a List of OpenCV
		// Mat object, so lets prepare one
		//List<Mat> outputBlobs = new ArrayList<Mat>();

		// -- Finally, let pass forward throught network. The main work is done here:
		dnnNet.forward(outputBlobs, outBlobNames);


		for (Mat output : outputBlobs) {
			// loop over each of the detections. Each row is a candidate detection,
			System.out.println("Output.rows(): " + output.rows() + ", Output.cols(): " + output.cols());
			
			for (int i = 0; i < output.rows(); i++) {
				Mat row = output.row(i);
				List<Float> detect = new MatOfFloat(row).toList();
				List<Float> score = detect.subList(5, output.cols());
				int class_id = argmax(score); // index maximalnog elementa liste
				float conf = score.get(class_id);
				
				if (conf >= confThreshold) {
					int center_x = (int) (detect.get(0) * frame.cols());
					int center_y = (int) (detect.get(1) * frame.rows());
					int width = (int) (detect.get(2) * frame.cols());
					int height = (int) (detect.get(3) * frame.rows());
					int x = (center_x - width / 2);
					int y = (center_y - height / 2);
					Rect2d box = new Rect2d(x, y, width, height);
					result.get("boxes").add(box);
					result.get("confidences").add(conf);
					result.get("class_ids").add(class_id);
				}
			}
		}
		
		ArrayList<Rect2d> boxes = (ArrayList<Rect2d>) result.get("boxes");
		ArrayList<Float> confidences = (ArrayList<Float>) result.get("confidences");
		ArrayList<Integer> class_ids = (ArrayList<Integer>) result.get("class_ids");

		// -- Now , do so-called Non-maxima suppression
		// Non-maximum suppression is performed on the boxes whose confidence is equal
		// to or greater than the threshold.
		// This will reduce the number of overlapping boxes:
		//MatOfInt indices = getBBoxIndicesFromNonMaximumSuppression(boxes, confidences);	
		MatOfRect2d mOfRect = new MatOfRect2d();
		mOfRect.fromList(boxes);
		MatOfFloat mfConfs = new MatOfFloat(Converters.vector_float_to_Mat(confidences));
		MatOfInt indices = new MatOfInt();
		Dnn.NMSBoxes(mOfRect, mfConfs, (float) (0.6), (float) (0.5), indices);
		
		
		// -- Finally, go over indices in order to draw bounding boxes on the image:
		//frame = drawBoxesOnTheImage(frame, indices, boxes, cocoLabels, class_ids, colors);
		
		List indices_list = indices.toList();
		for (int i = 0; i < boxes.size(); i++) {
			if (indices_list.contains(i)) {
				Rect2d box = boxes.get(i);
				Point x_y = new Point(box.x, box.y);
				Point w_h = new Point(box.x + box.width, box.y + box.height);
				Point text_point = new Point(box.x, box.y - 5);
				Imgproc.rectangle(frame, w_h, x_y, colors.get(class_ids.get(i)), 1);
				String label = cocoLabels.get(class_ids.get(i));
				Imgproc.putText(frame, label, text_point, Imgproc.FONT_HERSHEY_SIMPLEX, 1, colors.get(class_ids.get(i)),
						2);
			}
		}

	}
}
 

MaskRCNN 모델 메서드로 최대한 비슷하게 옮겨 보았습니다.

public class MaskRCNNDetector {
	private static double confThreshold = 0.5; // Confidence threshold
	private static List<String> outBlobNames = new ArrayList<String>(2);
	private static List<Mat> outputBlobs = new ArrayList<Mat>();
	private static CvDetectorType prevDetectorType = CvDetectorType.METHOD_UNKNWON;
	private static Net dnnNet;
	private static List<Scalar> colors;
	private static List<String> cocoLabels;
	
	static {
		outBlobNames.add(0, "detection_out_final");
        outBlobNames.add(1, "detection_masks");
	}
	
	public static void detectByMRCnn(Mat frame, CvDetectorOpt detectOptions) {
		outputBlobs.clear();
		
		if(prevDetectorType != detectOptions.getDetectorType())
		{
			dnnNet = detectOptions.getNet();
			prevDetectorType = detectOptions.getDetectorType();
			
			colors = detectOptions.getColors();
			cocoLabels = detectOptions.getCocoLabels();
		}
		
		// Create a 4D blob from a frame.
		Mat blob = Dnn.blobFromImage(frame, 1.0, new Size(frame.cols(), frame.rows()), Scalar.all(0), true, false);
		
        //Sets the input to the network
        dnnNet.setInput(blob);
        dnnNet.forward(outputBlobs, outBlobNames);
        
        // Extract the bounding box and mask for each of the detected objects     
        Mat outDetections = outputBlobs.get(0);
        
		// Output size of masks is NxCxHxW where
		// N - number of detected boxes
		// C - number of classes (excluding background)
		// HxW - segmentation shape
		final int numDetections = outDetections.size(2);
		//final int numClasses = outMasks.size(1);
		
		outDetections = outDetections.reshape(1, (int) outDetections.total() / 7);
		
		String label = "Na";
		float score = 0.0f;
		Rect box = null;
		
		for (int i = 0; i < numDetections; ++i) {
			score = (float)CvUtils.getMatVal(outDetections, i, 2);
			
			if (score > confThreshold) {
				int classId = ((Float)CvUtils.getMatVal(outDetections, i, 1)).intValue(); 
				int left = (int)(frame.cols() * (float)CvUtils.getMatVal(outDetections, i, 3));
				int top = (int)(frame.rows() * (float)CvUtils.getMatVal(outDetections, i, 4));
				int right = (int)(frame.cols() * (float)CvUtils.getMatVal(outDetections, i, 5));
				int bottom = (int)(frame.rows() * (float)CvUtils.getMatVal(outDetections, i, 6));

				left = Math.max(0, Math.min(left, frame.cols() - 1));
				top = Math.max(0, Math.min(top, frame.rows() - 1));
				right = Math.max(0, Math.min(right, frame.cols() - 1));
				bottom = Math.max(0, Math.min(bottom, frame.rows() - 1));
				
				box = new Rect(left, top, right - left + 1, bottom - top + 1);
				
				label = cocoLabels.get(classId);
				
				
				Point x_y = new Point(box.x, box.y);
				Point w_h = new Point(box.x + box.width, box.y + box.height);
				Point text_point = new Point(box.x, box.y - 5);
				Imgproc.rectangle(frame, w_h, x_y, colors.get(classId), 1);

				Imgproc.putText(frame, label, text_point, 
					Imgproc.FONT_HERSHEY_SIMPLEX, 1, colors.get(classId), 2);
				
			}
		}
	}

}
 

최종적으로 다음 코드에서 차이를 만들어 내고 있는 것을 확인 할 수 있었습니다.

위에서 살짝 재 본 대부분의 시간의 차이가 아래 코드에서 나오고 있었습니다.

dnnNet.forward(outputBlobs, outBlobNames);
 
 

 

이상.

728x90

'프로그래밍 > [Java] OpenCV' 카테고리의 다른 글

[2] OpenCV-JavaGUI  (0) 2024.01.16
[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