macOS 설치 후 개발환경을 위한 설정

1. Xcode 와 Xcode Command Line Tools 설치

Xcode Command Line Tools에는 컴파일러등의 도구가 들어 있어서 다른 설정들과 관계가 있어 가장 먼저 설치 해준다. Xcode는 App Store에서 설치하면 되고, Xcode Command Line Tools는 아래의 명령어를 터미널에서 실행한다.

xcode-select --install

2. 패키지 관리자 설치

개발등에 필요한 여러가지 도구들을 설치하려면 Macports 혹은 Homebrew와 같은 패키지 관리자를 설치해 주어야 한다. 2026년 현재 Homebrew는 Sonoma(14)이상에서만 동작 하므로 그보다 이전 버전을 사용하고 있다면 Macports가 선택가능한 대안이다.

3. zshell용 extension 설치

macOS terminal을 실행 할 때 기본적으로 zsh가 실행되는데, Oh my zsh 혹은 zim:fw 같은 extension을 사용하면 명령어 오타감지 등의 다양한 기능을 추가 하거나 보기 좋은 테마를 적용할 수 있다.

나는 기능은 적지만 단순한 zim:fw를 선호하는데, 만약 설치 후 사용할 때 “zsh: command not found: prompt-pwd” 라는 보기 싫은 경고 문구가 매번 출력된다면 ~/.zimrc 파일에 다음과 같이 prompt-pwd module을 추가해 서 해결할 수 있다.

4. 메타키 변경 (Emacs)

기본적인 터미널 설정의 메타(Meta) 키는 ESC이다. 이 키를 자주사용하는 Emacs 사용자라면 멀리 떨어져 있는 ESC 키를 누르는게 무척 귀찮을 것이다. Terminal을 열어서 “Use Option as Meta key”를 활성화 해 주면 ESC 대신 Option key로 대체할 수 있다.

한가지 주의할 점은 이 설정은 각 프로필 마다 따로 해주어야 한다는 점이다. 따라서 다른 종류의 프로필 여러개를 사용한다면 각각에 위의 설정을 적용해 주어야 한다.

5. Caps lock key를 Ctrl로 변경

Mac용 한글 키보드에서는 한영전환으로 사용되는 Caps lock key의 위치는 손목이 편안한 명당 이지만 자주 사용되지 않는 키를 배치하는 용도로 사용하면 효율이 나쁘다. HHKB 처럼 Caps lock을 Ctrl로 변경하면 손이 편하게 작업할 수 있다. 그리고 나서 Caps lock은 변방인 Ctrl 위치로 밀어 낸다.

Settings -> Keyboard -> Modifier Keys

6. 한글입력과 spotlight 단축키 변경

기본 한글 입력기 변환은 Ctrl + space인데 HHKB나 레오폴드 키보드 처럼 Ctrl 키의 위치가 Caps Lock을 대체하는 경우에는 한영 변환 입력이 조금 어렵다. 그래서 Settings -> Keyboard -> Shortcuts에서 Command + space로 변경해주고 이 때문에 충돌이 생기는 spotlight을 옛날 버전의 MacOS에서 쓰던 Command + p로 변경해 주었다.

Shift + Command + p는 VS code의 명령어 팔레트 단축키와 충돌 하므로 spotlight설정 할 때 함께 보이는 “Show Finder search window”는 단축키를 설정하지 않았다.

7. 기타 설정

  • SSH key를 복사하고 config를 설정
  • GitHub에 올려두는 Emacs 환경 파일을 clone
  • 터치바에서 siri을 없애고 Screenshot 기능으로 대체

Raspberry Pi Debugprobe Firmware Update

Warn : ***
Warn : *** Old Raspberry Pi Debugprobe firmware detected (1.0.1)
Warn : *** Using low-performance workaround
Warn : *** Please update to the latest release at:
Warn : *** https://github.com/raspberrypi/debugprobe/releases/latest
Warn : ***

Debugprobe의 firmware가 오래되어서 low-performance workaround로 동작한다는 경고메세지가 발생할 때는 firmware의 버전을 업데이트 해주는 것으로 해결할 수 있다.

  • Debugprobe realease page에서 최신 버전의 debugprobe.uf2 file을 다운로드 받는다.
  • Debugprobe hardware에도 BOOTSEL 버튼이 있는데 이것을 누른채로 USB를 연결하면 디스크가 마운트된다.
  • 다운로드 받은 debugprobe.uf2 file을 복사하면 완료된 후 최신 firmware로 재실행된다.

OpenVINO Object Detection Model의 전처리와 후처리를 간단하게

AI model들은 입출력 layer의 구조가 다르므로 서로 다른 pre/post processing을 필요로 한다. Vision model을 예로 들면 어떤 모델은 입력을 416×416으로 받게 되어 있어서 입력 전에 원본 크기로 부터 resizing을 해주어야 하고, 어떤 모델은 resizing layer을 포함하고 있어서 그냥 입력해도 잘 동작하기도 한다. 또한 어떤 모델은 입력값을 정규화 해서 입력해 주어야 하고, 어떤 모델은 정규화가 필요 없기도 하다.

입력층 뿐 아니라 출력층도 제각각이다. Object detection을 수행하는 모델들을 보면 YOLO같은 모델은 score값을 bounding box와 함께 전달하고, D-Fine같은 모델은 bounding box, score, label을 각각 따로 출력하기도 한다.

어떤 모델의 입출력 형태의 차이를 보고 싶으면 읽어들인 모델의 inputs와 outputs 변수의 내용을 살펴보면 된다.

def print_model_io_layer(ovcore: Core, model_path: str, model_name: str):
    read_model = ovcore.read_model(model_path)
    print(f"[{model_name}]")

    # Display input layer
    for idx, input_layer in enumerate(read_model.inputs):
        print(f"Input({idx+1}):")
        print("  any_name     :", input_layer.get_any_name())
        print("  names        :", input_layer.get_names())
        print("  shape        :", input_layer.get_partial_shape())
        print("  element type :", input_layer.get_element_type())

    # Display output layer
    for idx, output_layer in enumerate(read_model.outputs):
        print(f"Output({idx+1}):")
        print("  any_name     :", output_layer.get_any_name())
        print("  names        :", output_layer.get_names())
        print("  shape        :", output_layer.get_partial_shape())
        print("  element type :", output_layer.get_element_type())

이렇게 다양한 모델들의 입출력을 직접 처리하는 것은 여간 번거로운 일이 아니다. OpenVINO에서는 모델에 전/후처리를 사용자가 지정할 수 있는 PrePostProcessor를 지원하기는 하지만 이 마저도 모델별로 상이한 부분을 직접 수정해 주어야 하기 때문에 번거로운 것은 마찬가지다.

OpenVINO Model API 사용법

예를 들어 ATSS, YOLOX-S, YOLOX-Tiny, D-Fine 네개의 OpenVINO model과 각각에 대한 ONNX model들까지 8개의 서로 다른 세개의 서로 다른 모델들이 있다고 할 때, 8개의 서로 다른 입출력 결과를 처리하는 것은 아주 많은 노력을 필요로 할 것이다.

openvino-model-api를 사용하면 이러한 부분을 비교적 간단하게 처리 할 수 있다. 사용하려면 다음과 같이 필요한 패키지들을 설치해 준다.

pip install openvino openvino-model-api onnx

설치가 끝나면 먼저 서로 다른 입출력 계층을 맞춰 주는 모델을 생성한다. 추론에 사용하고자 하는 모델 파일로 OpenvinoAdapter를 만들어서 create_model()에 넘겨 준다. 이렇게 생성된 모델은 입력을 위한 전처리 과정을 자동으로 수행한다.

from openvino import Core
from model_api.adapters import OpenvinoAdapter
from model_api.models import Model


ovcore = Core()

# Adapter를 생성하고 create_model()에 넘겨준다.
ovadapter = OpenvinoAdapter(ovcore, model_path)
model = Model.create_model(ovadapter, preload=True)

그리고 나서 추론을 실행하면 그 결과는 DetectionResult 타입으로 정리되어 반환된다. 따라서 모델별로 서로다른 후처리(post processing) 과정 필요 없이 DetectionResult으로 반환되는 결과를 처리해주면 된다.

# 추론 실행
detection_result = model(test_image)

결과물 처리를 위해서 detection_result를 원본 이미지에 overlay하는 함수로 만들었다(overlay_detection_result). 이 함수는 Threshold값을 넘는 결과물을 원본 이미지 위에 bounding box와 label로 표시하고 detect된 object의 갯수와 함께 반환해 준다.

DetectionResult에서 참조되는 주요한 멤버변수는 Socre값을 나타내는 .score, bounding box를 나타내는 .xmin, .ymin, .xmax, .ymax 그리고 label을 가지고 있는 .str_label이다. Bounding box 좌표도 입력 이미지의 크기에 맞게 이미 scaling되어 있으므로 좌표를 그대로 가져다 쓰면 된다.

from model_api.models.utils import DetectionResult


def overlay_detection_result(
    original_image: np.ndarray, detection_result: DetectionResult, threshold: float
) -> Tuple[np.ndarray, int]:
    num_detected = 0
    processed_image = original_image.copy()

    for det in detection_result[0]:
        score = det.score
        if score >= threshold:
            num_detected += 1

            # Bounding box
            x1 = int(det.xmin)
            y1 = int(det.ymin)
            x2 = int(det.xmax)
            y2 = int(det.ymax)

            # Clamping
            x1 = max(0, min(x1, original_image.shape[1]))
            y1 = max(0, min(y1, original_image.shape[0]))
            x2 = max(0, min(x2, original_image.shape[1]))
            y2 = max(0, min(y2, original_image.shape[0]))

            # Draw BBox
            cv2.rectangle(processed_image, (x1, y1), (x2, y2), BBOX_COLOR, 2)

            # Draw label.
            display_text = (
                f"{det.str_label} {score:.2f}" if {det.str_label} else f"{score:.2f}"
            )
            cv2.putText(
                processed_image,
                display_text,
                (x1, y1 - 10),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.5,
                LABEL_COLOR,
                2,
            )
    return processed_image, num_detected

8개의 모델에 시험해 보자

사용하려는 8개의 모델들은 다음과 같다.

atss_model_file = os.path.join(MODEL_PATH, "atss_fp32/model.xml")
atss_onnx_model_file = os.path.join(MODEL_PATH, "atss_onnx_fp32/model.onnx")
yolos_model_file = os.path.join(MODEL_PATH, "yolos_fp32/model.xml")
yolos_onnx_model_file = os.path.join(MODEL_PATH, "yolos_onnx_fp32/model.onnx")
yolotiny_model_file = os.path.join(MODEL_PATH, "yolotiny_fp32/model.xml")
yolotiny_onnx_model_file = os.path.join(MODEL_PATH, "yolotiny_onnx_fp32/model.onnx")
dfinex_model_file = os.path.join(MODEL_PATH, "dfinex_fp32/model.xml")
dfinex_onnx_model_file = os.path.join(MODEL_PATH, "dfinex_onnx_fp32/model.onnx")

이것들을 all_models라는 list에 넣고 한번에 돌린다. 즉, 각 모델들의 서로 다른 입력과 출력 계층에 대한 처리를 단일한 코드로 수행하는 것이다.

ovcore = Core()


# Test image
test_image = cv2.imread("./data/test_image.jpg")

threshold = 0.8
for m in all_models:
    model_path = m[0]
    model_name = m[1]
    ovadapter = OpenvinoAdapter(ovcore, model_path)
    model = Model.create_model(ovadapter, preload=True)
    detection_result = model(test_image)
    proc_image, num_det = overlay_detection_result(
        test_image, detection_result, threshold
    )

    print(f"{model_name} :: {num_det} objects detected (threshold: {threshold}).")
    cv2.imwrite(f"output_{model_name}.jpg", proc_image)

전체 소스코드