728x90

이전 블로그에서 카메라 렌즈 종류에 대해서 소개 했었는데, 그 렌즈 중에는 라인 스캔 카메라가 있었습니다. TDI (Time Delay Integration) 라인 스캔 카메라는 고속으로 이동한는 물체를 고해상도로 촬영하기 위해 설계된 특수한 라인 스캔 카메라로 볼 수 있습니다. 보통 한 라인을 이용하여 이미지를 만드는 경우 작은 영역에 한 번만 노출되기 때문에 감도가 낮고 노이즈에 취약할 수 있습니다.

 

TDI 라는 명칭에서 처럼 시간 지연을 통한 적분 기술을 이용하여 고감도 이미지를 만듭니다. TDI에서는 1 X N 라인을 이용하며, 이미지 센서에 여러 라인이 배열되어 있어 대상이 이동함에 따라 각 라인이 같은 위치를 순차적으로 촬영 및 누적합니다. 예를 들어 N = 64 라면 물체의 동일한 위치를 64번 촬영한다는 의미로 볼 수 있고, 신호는 64배 증가하고 노이즈는 8배 증가하므로 SNR(Signal to Noise Ratio)이 향상됩니다. 

 

TDI 라인 스캔 카메라의 특징은 라인 수를 증가 시킬 수록 고감도 이미지를 얻을 수 있으며 노이즈 감소와 빠르게 이동하는 물체에 대해 고속 촬용이 가능합니다. 따라서 컨베이어 벨트에 제품을 이동시키면서 검사하는 산업 분야인 PCB 검사, 인쇄물 검사 등에 많이 활용됩니다. 그림에서 같이 라인 수에 따라 Single Line, Dual Line, TDI 형태로 구분 할 수도 있습니다.

 

 

 

TDI 활용 분야 중에는 반도체 검사가 있습니다. 웨이퍼에 이물질이나 결함 검사시에 이미지를 활용하는 경우가 많으며 수십 나노미터 또는 수 나노미터 크기의 노이즈 탐색 시 주요하게 활용됩니다. 일반적으로 반도체 분야에 사용되는 TDI는 256 라인 스캔이 활용되며, 컨베이어 벨트 예와 반대로 웨이퍼가 고정된 상태에서 라인 스캔이 이동함으로써 이미지를 구현합니다.

 

TDI의 이미지 합성은 1) N개 라인에 촬영된 이미지 배열을 같은 위치의 정보가 일치하도록 정렬하고, 2) 각 위치의 픽셀값을 전체 더하거나 평균값을 구합니다. 3) 마지막으로 출력할 이미지 Depth에 맞게 스케일링 될 수 있도록 정규화 합니다. 아래는 현 과정을 시뮬레이션 해 볼 수 있는 파이썬 예제 코드 입니다.

 

import cv2
import numpy as np
import matplotlib.pyplot as plt

# 가상으로 대상 이미지 생성
height, width = 100, 200
moving_obj = np.zeros((height, width), dtype=np.uint8)

# 대상 밝은 줄이 이동하는 효과
for i in range(height):
    moving_obj[i, 50 + (i % 50)] = 255  # 대각선 방향 밝기

# 일반 라인 스캔 (한 라인)
line_scan_img = np.zeros((height, width), dtype=np.uint8)
for i in range(height):
    line = moving_obj[i:i+1, :]  # 한 줄
    line_scan_img[i:i+1, :] = line

# TDI 라인 스캔 (8 라인 누적)
TDI_stage = 8
tdi_img = np.zeros((height - TDI_stage, width), dtype=np.uint16)
for i in range(height - TDI_stage):
    acc_line = np.zeros_like(line, dtype=np.uint16)
    for j in range(TDI_stage):
        acc_line += moving_obj[i + j:i + j + 1, :]
    tdi_img[i:i+1, :] = acc_line

# 정규화(스케일링)
tdi_img_norm = cv2.convertScaleAbs(tdi_img, alpha=255.0 / (TDI_stage * 255))

# 결과 보기
plt.figure(figsize=(12, 6))
plt.subplot(1, 3, 1)
plt.title("Original Moving Image")
plt.imshow(moving_obj, cmap='gray')

plt.subplot(1, 3, 2)
plt.title("Line Scan Image")
plt.imshow(line_scan_img, cmap='gray')

plt.subplot(1, 3, 3)
plt.title("TDI Result (8 stages)")
plt.imshow(tdi_img_norm, cmap='gray')

plt.tight_layout()
plt.show()
728x90
728x90

이미지 분석을 위한 특성은 이미지 센서 및 렌즈 특성에 따라 달라질 수 있습니다. 칼라 이미지는 Red, Blue, Green이라는 파장을 렌즈에서 필터링 해서 만들어 질 수 있으며, 렌즈 수차에 따라 이미지 왜곡이 발 생할 수 도 있습니다. 특히 산업 분야에서 활용되는 광학 현미경 Microscope 에서 렌즈 특성은 용도에 따라 중요하며, 현 블로그에서는 주요 기본 용어에 의미와 특징을 알아보겠습니다.

 

색상과 파장 (Color and Wavelength)

광학 분야에서 빛을 설명할 때 파장으로 정의합니다. 빛을 받아드릴때 어떤 파장을 필터링하느냐에 따라서 렌즈의 특성이 달라질 수 있습니다. 파장은 크게 자외선(380nm 미만), 가시광선(380~750nm), 적외선(770nm 이상) 으로 구분되며, 770nm 이상에서 이미지를 취득하는 카메라를 적외선 카메라라고 합니다.

 

배율 (Magnification)

카메라에서 배율은 피사체의 실제 크기와 이미지 센서 상에 맺힌 상의 크기 비율을 의미합니다. 배율을 정의해 보면 “센서 상의 이미지 크기 / 실제 물체의 크기”이며, 예를 들어 물체의 실제 크기가 50mm이고 센서 상에 5mm로 맺혔다면 배율은 0.1가 됩니다.

 

광학 현미경 렌즈에 “20x”, “50x”, “100x”로 표시된 것을 볼 수 있으며 배율을 의미합니다. 20배, 50배, 100배로 확대할 수 있다는 의미이며 배율 계산시 정의는 아래와 같습니다. 중간 배율이 없으면 제외하고 계산합니다.

 

배율(M) = 대물렌즈(Objective) 배율 X 접안렌즈(Eyepiece) 배율 X 중간(Intermediate tube) 배율

 

개구수 (Numerical Aperture)

개구수 NA는 대물렌즈를 통과한 빛과 광축이 이루는 최대각과 매질의 굴절률을 이용하며, 분해능과 밝기, 초점심도 등을 결정하는 중요한 수치입니다. 개구수 값이 클수록 광학 현미경의 분별 가능한 최소 길이가 작아집니다. 개구수 NA를 구하는 공식은 아래 같습니다.

 

N.A = n X sinθ

 

n: 시료와 대물렌즈 사이 매질의 굴절률 ( 공기: 1.00, 물: 1.33)

θ: 렌즈의 중심축(광축)과 제일 외측을 지나는 광선 간 이루는 각도

 

 

추가로 분해능 Resolving Power을 구하는 공식은 아래와 같습니다.

 

분해능 R = λ / (2 X N.A)

 

수차 (Aberration)

이미지 센서에 상을 맺을 때 한점에서 나온 빛이 광학계를 지난 다음 한 점에서 모이지 않아 영상에 색깔이 있어 보이거나 일그러지는 현상을 말합니다. 수차에는 크게 구면 수차와 색 수차가 있습니다.

 

렌즈도 하나의 매질로 볼 수 있으며 빛이 통과하게 되면 파장이나 진폭등이 변하게 되고 명도 및 채도도 변하게 됩니다. 렌즈를 지나면서 생기는 수차를 구면 수차라고 하며, 파장 길이에 따라 발생하는 것이 색 수차 입니다.

 

밝기와 작동거리(WD, Working Distance)

센서 맺히는 상의 밝기는 광원의 밝기 외에도 개구수나 배율의 영향을 받습니다. 개구수가 높을 수록, 배율이 낮을 수록 상의 밝기는 밝아집니다. 작동거리는 렌즈 끝에서 피사체가 있는 곳까지의 거리를 말합니다. 렌즈가 작동할 수 있는 거리이며 이 거리는 렌즈와 피사체가 맞닿지 않는 범위를 의미합니다. 작동거리가 길다고 해서 무조건 좋다고 할 수 없는데 WD와 NA는 반비례하기 때문입니다.

728x90
728x90

기존 블로그 에서 산업 자동화에 활용되는 머신 비전용 렌즈인 Telecentric Lens, Macro Lens, Line Scan Lens를 소개했습니다. 아래 링크를 참고해 보시면 좋을 듯 합니다.

2025.06.29 - [영상처리 도구] - 머신 비전 Machine Vision을 위한 카메라 렌즈 종류와 선택(1)

 

아래는 이 외에 머신 비전에서 활용되는 렌즈들 입니다. 활용 분야에 따라 렌즈의 적절한 선택은 효율성이나 비용 측면에서 더 유리할 수 있습니다.

 

고정 초점형 렌즈 (Fixed Focal Lens)

초점 거리가 고정되어 있어 화각이 일정하고 줌이 불가능한 렌즈를 의미합니다. 하지만 광학 성능이 우수하고 구조가 단순하여 PCB 패턴, 바코드, 약품 포장 라벨 검사 등등 산업용 검사나 머신 비전 분야에 널리 사용됩니다. 

 

명칭 그대로 고정 초점이기 때문에 해상도와 왜곡 보정에 유리하고 구조가 간단하여 유지보수가 용이합니다. 가격 측면에서도 효율성이 있어 산업용으로 많이 활용됩니다. 당연히 초점 거리 변경이 필요하면 렌즈 자체를 교체해야 하며 촬영 물체들의 높낮이가 변동이 있으면 사용이 불가 합니다.

 

무왜곡 렌즈 (Zero-Distortion Lens 또는 Ultra Low Distortion Lens)

광학 이미지에서 발생할 수 있는 기하학적 왜곡 Geometric Distortion을 최대한 제거하나 최소화한 렌즈를 말합니다. 다시 말해 왜곡률을 극한까지 줄여 실제 대상의 형태를 왜곡 없이 촬영하는 렌즈이며, 산업 검사와 비전 시스템 등 정확한 기하학적 재현이 요구되는 분야에 필수로 사용됩니다.

 

왜곡률의 경우 일반 렌즈 1~3%, 광각 렌즈 3~10% 이상, 무왜곡 렌즈 0.1% 이하로 볼 수 있으며, 광학 측정 장비나 생물학 영상, 문자 스캔, 실물 재현을 위한 건축물 및 문화재 촬영에 사용될 수 있습니다. 렌즈 설계 복잡성으로 가격이 비싸며 화각 FOV가 제한적일 수 있습니다. 

 

Pericentric 렌즈

360도 시야 확보용 렌즈로 렌즈 하나로 물체의 상단과 측면, 일부 바닥까지 한번에 볼 수 있도록 설계된 렌즈 입니다. 특히 원형 물체나 튜브용 물체의 외곽을 한번에 검사해야 할 때 활용됩니다.

 

일반 렌즈와 다르게 광선이 센서 중심을 향해 굴절 되도록 설계되어 센서가 보는 시야는 렌즈를 중심으로 물체를 감싸는 형태가 됩니다. 결과론적으로 물체의 위와 측면을 동시에 이미지화 할 수 있으며, 병뚜껑 밀봉 상태나 병과 캔의 레이블 위치, 화장품 튜브 외곽 인쇄 품질 검사 등에 사용됩니다.

 

그림에서와 같이 플라스틱 통의 뚜껑을 기준으로 촬영을 하면 플라스틱 측면에 라벨까지 이미지로 확인 할 수 있습니다. 그림에 WD는 Working Distance를 의미합니다.

 

 

전동식 렌즈 (Motorized Lens)

명칭에서와 같이 전동식으로 초점, 줌, 조리개 등을 자동으로 조절할 수 있는 렌즈 입니다. 시스템 명령이나 소프트웨어를 통해 원격으로 제어할 수도 있습니다. 따라서 넓은 지역을 감시하면서 특정 지점을 확대해 볼 수 있는 감시 시스템이나 다양한 거리의 물체 인식을 필요로 하는 로봇 비전에서 활용될 수 있습니다. 요즘 골목마다 설치 되어 있는 CCTV가 전동식 렌즈의 한 예 입니다.

 

원격 제어 가능함과 자동화 시스템의 연동으로 정밀한 제어에 장점을 가지며 전원 의존성이나 비용, 설정 복잡성 등은 단점으로 생각할 수 있습니다.

728x90
728x90

이미지 처리 및 분석을 통한 기술 적용 분야가 많아지고 있습니다. 그 중 컴퓨터 비전 Computer Vision의 한 부분인 머신 비전 분야가 있습니다. 머신 비전은 산업 자동화와 제조 라인의 검사, 품질 관리, 제어 등에 사용되며, 그 이미지를 취득하는 다양한 비전 카메라들이 있습니다. 현 블로그에서는 비전 카메라에 장착되는 렌즈에 특성을 알아보겠습니다. 

 

텔레센트릭 렌즈 (Telecentric Lens)

광학 중심이 물체나 제품쪽으로 이동하면서도 배율이 일정하게 유지되도록 설계된 특수 렌즈이며, 정밀한 측정 및 검사에 사용됩니다. 특성을 보면 배율이 일정하고 일반 렌즈에 비해 왜곡을 최소화합니다. 일반 렌즈는 조리개가 렌즈 중간에 위치한 반면 텔레세트릭 렌즈는 조리개가 주점(Primary principal point) 위에 배치되어 광선이 평행하게 입사 및 출사할 수 있는 구조적 특징을 가지고 있습니다.

 

이러한 구조적 특성에 의해 물체의 왜곡을 최소화하고 깊이 변화에 무관한 정확한 크기 측정이 가능하며 동일한 조건에서 반복 측정이 가능한 장점을 가지고 있습니다. 반면 고가에 렌즈가 길고 무거우며 조리개가 작아 광량 손실이 있는 단점과 검사 대상 물체 크기가 렌즈 직경보다 작아야 한다는 단점을 가집니다. 세부적으로 반도체 검사나 PCB 및 전자부품 측정, 3D 스캐닝 시스템 등에 활용됩니다.

 

종류로는 아래 세가지로 구분할 수 있습니다. 아래 그림은 Bi-Telecentric Lens 구성도와 일반 렌즈 및 텔레센트릭 간 촬영한 물체를 비교한 예입니다. 일반 렌즈에서는 원근에 따른 물체의 크기가 다르지만 텔레센트릭 렌즈에서는 같은 크기의 물체는 동일하게 촬영됩니다.

 

1) Object-space Telecentric Lens: 물체가 렌즈에 가깝거나 멀어도 배율이 일정

2) Image-space Telecentric Lens: 이미지 센서에 광선이 평행하게 입사, 이미지 센서 크기에 무관하게 균일한 이미지 품질 유지

3) Bi-Telecentric Lens: 물체 및 이미지 센서 모두 Telecentric. 

 

 

 

매크로 렌즈 (Macro Lens)

작은 사물 또는 제품 등을 크게 확대하여 촬영할 수 있도록 설계된 특수 렌즈이며, 주로 접사(Close-up photography)에 사용됩니다. 일반적으로 곤충이나 꽃, 작은 전자부품 및 인쇄패턴과 같은 미세한 부분 촬영에 적합합니다.

매크로 렌즈의 특성은 배율이 크며 촬영 대상의 왜곡을 최소화하고 얕은 심도(Depth of Field)를 가집니다. 초점 거리별 유형을 보통 50~60mm는 문서 등의 근거리 접사, 90~105mm는 곤충이나 꽃 접사 촬용에 사용되며 150~200mm는 원거리 접사로 산업용 검사에 활용됩니다.

 

라인 스캔 렌즈 (Line Scan Lens)

라인 스캔 카메라와 함께 사용하는 특수 렌즈로 한 줄(Line)의 이미지를 정밀하게 촬용하기 위해 설계된 렌즈입니다. 우리가 일반적으로 사용하는 영역 스캔 (Area Scan) 방식과는 다른 연속적으로 고속 이미지 취득에 최적화 되어 있습니다.

 

라인 스캔은 한번에 물체의 가로 또는 세로의 한 줄만 촬영이 되며, 대상이 이동하면서 연속 라인 촬영을 통해 2차원 이미지를 구성합니다. 문서를 스캔할 때 방식과 유사하다고 보면 됩니다. 라인 스캔 렌즈의 특징을 보면 미세 결함도 감지할 만큼 해상도가 높고 전체 라인에 걸쳐 왜곡 없이 균일합니다. 그리고 균일하게 밝기를 유할 수 있는 광량 균일성과 열이나 진동에 강건하게 장시간 연속 촬영이 가능합니다.

 

아래 그림에서와 같이 라인 스캔 렌즈로 촬영된 이미지는 일반적으로 X축이 시간으로 볼수 있이며, Y축이 라인 스캔의 라인 크기입니다.

 

728x90
728x90

이미지를 다루다 보면 다양한 포맷들을 볼 수 있습니다. 실생활에서도 비트맵 Bitmap 이미지를 많이 사용하기도 하며 MFC와 C# 윈폼에서 화면에 그려주는 이미지가 비트맵이기도 합니다.

MFC (Microsoft Foundation Class) 라이브러리를 이용하여 비트맵을 만들어 화면에 출력하거나 파일로 저장하는 과정을 살펴 보겠습니다. 단계별로 보면, 메모리 DC 생성을 하고 비트맵 객체 생성, 그리기 작업을 수행합니다. 이후 비트맵 이미지를 저장하거나 화면에 출력하고 리소스 해제를 하면 끝입니다.

 

GDI (Graphics Device Interface) 그리기

memDC.FillSolidRect(0, 0, width, height, RGB(255, 255, 255)); // 배경 흰색

memDC.TextOutW(10, 10, _T("Hello Bitmap!!!")); // 텍스트 출력 

화면에 출력 또는 저장

pDC->BitBlt(0, 0, width, height, &memDC, 0, 0, SRCCOPY);  // 화면 출력

 

CImage image; // 저장

image.Attach((HBITMAP)bitmap.Detach()); // CBitmap → HBITMAP → CImage

image.Save(_T("output.bmp")); 

리소스 해제

memDC.SelectObject(pOldBitmap); // 원래 비트맵으로 복원

bitmap.DeleteObject(); // 리소스 해제

memDC.DeleteDC(); // 리소스 해제

 

OnDraw 함수를 이용한 전체 코드는 아래와 같습니다. 이미지를 화면에 출력시 Device Context(pDC)의 BitBlt 함수를 이용하는 것을 볼 수 있습니다. 함수 별 활용법을 알아두면 실무 개발에 많은 도움이 될 수 있습니다.

 

void CMyView::OnDraw(CDC* pDC)
{
    int width = 100;
    int height = 100;

    CDC memDC;
    memDC.CreateCompatibleDC(pDC);

    CBitmap bitmap;
    bitmap.CreateCompatibleBitmap(pDC, width, height);
    CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);

    // 배경 및 텍스트 출력
    memDC.FillSolidRect(0, 0, width, height, RGB(200, 220, 255));
    memDC.TextOutW(10, 10, _T("Hello Bitmap!!!"));

    // 화면에 출력
    pDC->BitBlt(0, 0, width, height, &memDC, 0, 0, SRCCOPY);

   
    // 리소스 정리
    memDC.SelectObject(pOldBitmap);
    memDC.DeleteDC();
}

 

 

추가로 비트맵 헤더 정보는 아래와 같고 실제 프로그래밍 시 각 요소에 접근이 필요할 때가 있습니다. 압축 설정에는 여러 종류가 있으며 일반적으로 무압축 BI_RGB를 설정 합니다.

 

typedef struct tagBITMAPINFOHEADER {
  DWORD biSize;
  LONG  biWidth;
  LONG  biHeight;
  WORD  biPlanes;
  WORD  biBitCount;
  DWORD biCompression;
  DWORD biSizeImage;
  LONG  biXPelsPerMeter;
  LONG  biYPelsPerMeter;
  DWORD biClrUsed;
  DWORD biClrImportant;
} BITMAPINFOHEADER, *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;

 

 

728x90
728x90

MFC를 활용하여 GUI(User Interface) 구현 시, DC, CDC, HDC, HWND 등을 정확히 이해 못하고 활용할 때가 많습니다. Window GUI Programming에 핵심적인 개념으로 명확히 이해하고 있으면 MFC 기반 어플리케이션 개발이 쉬워질 수 있습니다.

 

  • DC (Device Context) - 그리기 작업을 수행하기 위한 핸들 또는 구조체
  • CDC - MFC에서 제공하는 HDC를 C++ Class 형태로 래핑한 Class
  • HWND - 윈도우 핸들 Handle, MFC에서는 CWnd Class가 HWND를 래핑

 1. DC: Device Context

윈도우에서 화면, 프린터 등 출력 장치에 텍스트나 그래픽을 출력할 때 사용되며, HDC라는 핸들로 윈도우 API 함수 GetDC와 ReleaseDC를 통해 가져오고 해제합니다. 메모리를 위한 DC는 CreateCompatibleDC 함수가 있습니다.

 

HDC hdc = ::GetDC(hWnd); // HDC: Handle to Device Context

TextOut(hdc, 30, 30, _T(”Device Context: DC !!!”), 10);

::ReleaseDC(hWnd, hdc);

 

2. CDC

HDC를 객체지향적으로 래핑한 MFC 클래스 입니다. 메서드를 통해 구조화된 방식으로 그리기 작업을 쉽게 할 수 있으며, HDC보다 더 다양한 멤버 함수를 제공합니다. MFC에서는 화면, 프린터, 메모리 등 다양한 DC를 파생 클래스로 나눠서 지원 합니다. 예를 들어 CClientDC는 클라이언트 영역 DC, CPaintDC는 WM_PAINT 메시지 핸들링, CWindowDC는 윈도우 전체 영역 그림, 그리고 CMemDC 등이 있습니다.

 

CClientDC dc(this); // this는 CWnd 포인터

dc.TextOut(30, 30, _T(”Device Context: DC !!!”));

 

3. HWND: Window Handle

윈도우에서 각 창을 고유하게 식별하기 위한 ID로 보면 됩니다. MFC에서는 CWnd 클래스가 HWND를 래핑합니다. GetSafeHwnd()로 HWND를 가져올 수 있습니다.

 

// HWND > HDC > CDC

HWND hWnd = this->GetSafeHwnd();

HDC hdc = ::GetDC(hWnd); // HDC: Handle to Device Context

TextOut(hdc, 30, 30, _T(”Device Context: DC !!!”), 10);

::ReleaseDC(hWnd, hdc);

 

정리하면 HWND는 윈도우 창 식별자로 특정 창에 접근할 때 사용하며, HDC는 디바이스 컨텍스트 핸들로 GDI로 그림 그릴때 사용합니다. CDC는 MFC의 DC 클래스로 그리기 함수 및 리소스를 관리합니다. 이 세가지가 맞물려서 MFC 기반 앱에서 시각적 요소들을 처리합니다. 비유하면, HWND는 화면위에 창문, HDC는 그 창문에 그림을 그릴 수 있게 해주는 붓, CD는 그 붓을 잘 다뤄주는 도구함이라고 생각해 볼 수 있습니다. 

 

추가로 메모리 DC를 활용하면 화면 깜빡임 없는 출력이 가능합니다. 더블 버퍼링 Double-Buffering 기법이라고도 합니다.

 

CDC memDC;
memDC.CreateCompatibleDC(&dc);

CBitmap bitmap;
bitmap.CreateCompatibleBitmap(&dc, 100, 100);

CBitmap* pOldBitmap = memDC.SelectObject(&bitmap);
memDC.FillSolidRect(0, 0, 100, 100, RGB(255, 255, 255));
memDC.TextOutW(10, 10, _T("Off-screen drawing"));

dc.BitBlt(0, 0, 100, 100, &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOldBitmap);
728x90
728x90

C++/MFC를 활용하여 이미지 처리 알고리즘 등 User Interface 및 Display 목적으로 코드 구현 시 파일 오픈 또는 외부 프로그램을 실행시켜야 할 때가 있습니다. 이때 사용할 수 있는 함수가 Window API로 ShellExecute 가 있습니다. 

 

MSDN에는 아래와 같이 정의 되어 있고, 3가지 이용할 수 있는 방법이 나와 있습니다.

 

HINSTANCE ShellExecute(

HWND hwnd,

LPCTSTR lpOperation,

LPCTSTR lpFile,

LPCTSTR lpParameters,

LPCTSTR lpDirectory,

INT nShowCmd

);

 

1) ShellExecute(handle, NULL, path_to_folder, NULL, NULL, SW_SHOWNORMAL);

2) ShellExecute(handle, “open”, path_to_folder, NULL, NULL, SW_SHOWNORMAL);

3) ShellExecute(handle, “explore”, path_to_folder, NULL, NULL, SW_SHOWNORMAL);

 

웹 주소 연결하려 웹을 열고 임의의 프로그램을 활성화하는 예제 입니다. 인터넷 웹을 활성화하는 방법은 두가지로 구분되며 HTTP 프로토콜을 사용 유무에 있습니다.

[Web(Internet)]

방법 #1.

HWND hWnd = ::FindWindow(NULL, “IEFrame”);

::ShellExecute(hWnd, “Open”, “www.kkk.co.kr”, NULL, NULL, SW_SHOW)

 

방법 #2.

ShellExecute(NULL, “Open”, “http://www.kkk.co.kr/aaa”, NULL, NULL, SW_SHOW) 

[프로그램 (실행파일 .exe)]

ShellExecute(NULL, NULL, “program.exe”, NULL, NULL, SW_SHOWNORMAL)

 

ShellExecute 함수를 찾다보면 ShellExecuteEx 함수를 볼 수 있습니다. ShellExecuteEx도 웹 및 문서 열기, 애플리케이션 실행, 프린터 명령 등 다양한 작업을 윈도우 쉘을 통해 수행할 수 있게 해주며, ShellExecute 함수 보다 더 많은 기능을 제공합니다. 간략히 ShellExecuteEx 함수 사용법은 아래와 같습니다.

 

> SHELLEXECUTEINFO 를 설정 및 초기화하고 Size 설정

> ShellExecuteEx의 멤버 변수들 설정: __TEXT(SzFileName)에서 SzFileName은 초기 설정되어 있는 Path가 바뀔수 있기 때문에 그 실행 프로그램 위치를 고정시켜 줄 필요가 있고, Path가 변할일이 없으면 “program.exe”으로 직접 넣어 주면 됩니다.

// 예제 #1.
SHELLEXECUTEINFO Eexe;
ZeroMemory(&Eexe, sizeof(SHELLEXECUTEINFO);
Eexe.cbSize = sizeof(SHELLEXECUTEINFO);
Eexe.lpFile = __TEXT(SzFileName);
Eexe.nShow = SW_SHOWMAXIMIZED;
Eexe.lpVerb = __TEXT(”open”);
ShellExecuteEx(&Eexe);

 

// 예제 #2.
SHELLEXECUTEINFO Eexe= { 0 };
Eexe.cbSize = sizeof(SHELLEXECUTEINFO);
Eexe.fMask = SEE_MASK_NOCLOSEPROCESS;
Eexe.hwnd = NULL;
Eexe.lpVerb = L"open"; // L"runas" (관리자 권한 실행)
Eexe.lpFile = L"notepad.exe"; // 실행할 파일
Eexe.lpParameters = L""; 
Eexe.lpDirectory = NULL; // 디렉터리
Eexe.nShow = SW_SHOW;
Eexe.hInstApp = NULL;

if (ShellExecuteEx(&Eexe)) { // 성공적으로 실행됨    
    if (Eexe.hProcess != NULL) {
        // 실행된 프로세스가 종료될 때까지 대기
        WaitForSingleObject(Eexe.hProcess, INFINITE);
        CloseHandle(Eexe.hProcess);
    }
} else {
    MessageBox(NULL, L"실행 실패", L"오류", MB_OK | MB_ICONERROR);
}

// 오류처리
if (!ShellExecuteEx(&Eexe)) {
    DWORD err = GetLastError();
    wprintf(L"실패, 오류 코드: %lu\n", err);
}

 

728x90
728x90

이미지 처리에서 알고리즘 구현 시 픽셀 단위 처리나 필터 처리 등이 빈번하게 사용됩니다. 이는 이미지의 데이터 형식이 2차원 행렬 구조이기 때문에 그렇습니다. 영상처리 라이브러리인 OpenCV에서는 이미지 처리를 위해 데이터 타입 Mat을 제공합니다. CV의 여러 함수들 내부에서도 Mat 형식을 통한 처리가 기본이며, CV를 세부처리에 활용하기 위해서는 Mat 활용 방법을 정확히 알고 있어야 합니다.

 

행렬 Matrix와 배열 Array의 차이는 행렬은 X좌표와 Y좌표로 이루어진 2차원 배열을 의미하며, 배열은 1차원, 2차원, 3차원 모두를 포함합니다. 행렬 연산 관련 항목들을 찾다 보면 조밀 행렬 Dense Matrix와 희소 행렬 Sparse Matrix 형식을 볼 수 있습니다. 조밀 행렬은 배열의 모든 값이 의미 있는 값으로 채워져 있는 배열이며, 희소 행렬에서는 “0”이 아닌 값들을 의미 있는 값으로 보게 됩니다. 다시 말해 메모리 공간 확보 시 조밀 행렬은 전체 배열을 할당 해야하지만 희소 행렬은 “0”이 아닌 값만 메모리 할당을 하게 되어 메모리 절약을 할 수 있습니다.

 

Mat 형식은 Create 메서드로 객체 값을 할당 할 수도 있지만 생성자 여러 개로 오버로드 되어 있습니다. 메서드 및 예제는 C/C# 구현 예입니다. 

[Create 메서드]

Create( MatType type, params int[] sizes )

Create( Size size, MatType type )

Create( int rows, int cols, MatType type )

[ 생성자 ]

Mat()

Mat( int rows, int cols, MatType type )

Mat( int rows, int cols, MatType type, Scalar s )

Mat( Size size, MatType type )

Mat( Size size, MatType type, Scalar s )

 

기본적인 사용법은 아래와 같습니다. “CV_8UC3”은 8 bits Unsigned Char의 3채널을 의미합니다.

 

Mat M = new Mat();

예제 1) M.Create( MatType.CV_8UC3, new int[] { 240, 320 } );

예제 2) M.Create( new Size(320, 240), MatType.CV_8UC3 );

예제 3) M.Create( 240, 320, MatType.CV_8UC3 );

 

예제 4) Mat M( 240, 320, MatType.CV_8UC3 );

예제 5) Mat M( new Size(320, 240), MatType.CV_8UC3 );

 

Mat 클래스에서는 아래와 같은 기본 행렬 표현식을 사용할 수 있게 정적 메서드를 제공합니다. 기본 행렬 표현식은 단일 채널에 값을 할당하므로 주의해서 사용하면 됩니다.

[ 정적 메서드 ]

Mat.Eye( int rows, int cols, MatType type ) : 단위 행렬

Mat.Ones( int rows, int cols, MatType type ) : “1”로 채워진 행렬

Mat.Zeros( int rows, int cols, MatType type ) : “0”으로 채워진 행렬

Mat.Diag( Mat d ) : 대각행렬

 

예제) Mat M = Mat.Eye( new Size(3,3), MatType.CV_64FC1 ); // 64 bits double 1채널

 

Mat 데이터를 생성하고 처리했으면 행렬 요소에 접근 방법을 알아야합니다. 요소에 접근해서 값을 가져올때는 At() 또는 Get() 메서드를 사용하고, 값을 설정 할때는 Set() 메서드를 사용할 수 있습니다. 아래는 2차원 행렬 접근시 사용 예입니다. 행렬의 블럭 단위로 요소에 접근할 수 있는 메서드들 중에는 M.Row.Get, M.Col.Get, M.Row.Set, M.Col.Set 도 있습니다. 

[ 픽셀 접근 ]

M.At( i, j ) // 행렬 M의 i, j 위치 요소, (row, col>

M.Get( i, j ) // 행렬 M의 i, j 위치 요소

M.Set( i, j, value ) // 행렬 M의 i, j 위치 요소를 value로 설정 

 

예제) M.At(1, 1) // MatTyped이 CV_64FC1이면 type은 double

728x90
728x90

앞에 블로그에서 설명한 것 처럼 푸리에 변환은 시간 영역의 신호를 주파수 영역으로 변환할 수 있는 이론입니다. 푸리에 변환 기본을 아래 블로그로 들어 가시면 참고 할 수 있습니다.

 

2025.06.01 - [영상처리 기술] - 영상처리 푸리에 변환 Fourier Transform 이해와 기본 (Python)

 

영상처리 영역에서는 이미지 평면을 주파수 평면으로 변환하여 분석할 수 있는 도구로 사용할 수 있습니다. 실무에서는 속도와 구현 이점 때문에 고속 푸리에 변환 FFT를 사용합니다. Python, C/C++, Matlab 등 모두에서 이미지 처리시 사용하는 함수 명칭이 “fft” 로 되어 있는 이유가 그 이점 때문에 그렇습니다. 푸리에 변환과 같은 이론들을 테스트 해 볼 수 있는 라이브러리들이 많아지고 있습니다만 단계별 처리 과정이 어떻게 되는지 이해를 하고 활용 또는 응용을 한다면 더 효과 적으로 사용할 수 있습니다.

 

2D Discrete Fourier Transform(DFT) 수식은 아래와 같습니다. F[k,l]은 주파수 영역, f[m,n]은 이미지 평면을 나타냅니다. 

 

푸리에 변환을 통해 얻을 수 있는 값들은 스펙트럼 Spectrum과 페이즈 Phase 정보 입니다. 변환된 수식에서 F[k,l]의 크기를 Spectrum 또는 Magnitude를 의미하고, 각도를 Phase로 부릅니다. 일반적으로 이미지 처리에서 활용하는 정보는 Spectrun(Magnitude)입니다. 

 

이미지를 활용하여 주파수 영역의 스펙트럼을 구하는 순서를 살펴 보면, 

1) 이미지 Width와 Height가 각 2^N에 맞게 조정 후 Zero Padding  

2) 이미지 X 방향 푸리에 변환하고 그 결과에 Y방향으로 푸리에 변환

3) 각 모서리에 집중되어 있는 저주파 영역 DC 정보를 중심으로 이동 시키기 위한 DC Shift

4) 주파수 영역은 그림에서 상단 두 번째 이미지 결과 형태

 

 

 

FFT에서는 2에 거듭제곱에 비례한 신호 또는 픽셀 수를 이용하여 짝수와 홀수로 나누어 변환을 수행합니다. 이를 Butterfly Operation이라고 하며, 처리 속도를 향상 시킬수 있고 하드웨어적인 구현에도 이점을 가질 수 있습니다. 따라서 이미지에 대해서도 2에 거듭제곱에 비례하게 크기를 조절 후 변환하게 됩니다. 예를 들어 500X500 크기를 갖는 이미지라면 2의 N 승배인 512X512로 크기 조절 및 Zero Padding 후 처리하게 됩니다.

 

그림에서 하단 첫번째 처럼 주파수 영역 중심 부분을 필터링하고 역변환하면 하단 두 번째 이미지 결과를 얻는데, 저주파 영역을 마스크 처리 했으니 고주파 특성의 에지영역들만 남길 수 있습니다. 이러한 처리를 Lowpass Filtering 하며, 여러 필터링 방법들을 이용하여 이미지 처리에 응용할 수 있습니다.

728x90
728x90

실무에서 영상처리 알고리즘을 구현할 때 관심 영역이나 물체의 외곽선을 탐색하고 추출할 상황이 많습니다. 물체의 대략적인 위치 확인을 위해 탐색할 때도 있지만 세부적인 외곽선이 필요한 상황도 알고리즘 중간 중간 필요할 때가 있습니다. 이때 사용할 수 있는 함수가 OpenCV의 “findConturs” 함수 이며 파이썬 또는 C/C++에서 아래 형식으로 적용해 볼 수 있습니다. 출력에 “contours”는 검출된 윤곽선을 저장한 포인트 리스트 이며, “hierachy”는 윤곽선의 계층 구조를 저장할 배열 입니다.

 

C/C++ 활용

void cv::findContours( 

           InputArray image,                            // 8-bit 그레이 또는 이진 이미지 

           OutputArrayOfArrays contours,          // (출력) contours

           OutputArray hierarchy,                     // (출력) hierarchy

           int mode,                                       // (아래 설명)

           int method,                                    // (아래 설명)

           Point           offset = Point() )           // (옵션) 설정된 offset 값 만큼 이동

 

Python 활용

contours, hierarchy = cv.findContours( image, mode, method[, contours[, hierarchy[, offset]]]) 

 

실무 활용시에는 “mode”와 “method”를 알고리즘에 맞게 적절히 설정할 줄 알아야 원하는 결과를 얻을 수 있으며, 일반적으로 입력 이미지는 이진 binary 이미지를 사용합니다. “mode”에서 윤곽선을 정의할 때 물체가 한 덩어리로 될 수 있지만, 구멍이 있는 영역들도 존재하기 때문에 계층 구조로 윤곽선을 추출 할 수 있게 설정할 수 있습니다. “method”는 좌표 리스트들을 어떻게 근사화해서 추출 할 지에 대한 설정 이며, 3)과 4)의 CHAIN_APPROX는 1989는 PAMI에 발표된 “On the detection of dominant points on digital curves” 논문에 기반한 방법이니 참조해 보시면 좋을 듯 합니다.

 

모드 mode

1) RETR_EXTERNAL (Python: cv.RETR_EXTERNAL): 최외곽선 검출

2) RETR_LIST (Python: cv.RETR_LIST): 모든 윤곽선 검출

3) RETR_CCOMP (Python: cv.RETR_CCOMP): 2-Level 계측 구조로 윤곽선 검출

4) RETR_TREE (Python: cv.RETR_TREE): 모든 윤곽선을 계층 구조로 검출

 

메소드 method

1) CHAIN_APPROX_NONE (Python: cv.CHAIN_APPROX_NONE): 모든 포인트에 대한 좌표를 추출

2) CHAIN_APPROX_SIMPLE (Python: cv.CHAIN_APPROX_SIMPLE): 수평, 수직, 대각 방향의 끝 좌표만 추출, 예를 들어 물체가 사각형이라면 4개의 끝점만 추출.

3) CHAIN_APPROX_TC89_L1 (Python: cv.CHAIN_APPROX_TC89_L1)

4) CHAIN_APPROX_TC89_KCOS (Python: cv.CHAIN_APPROX_TC89_KCOS)

 

아래는 파이썬 코드 예이며, 응용에서는 외곽선을 추출하고 “contourArea”와 “arcLength” 함수를 이용하여 물체나 ROI의 면적과 둘레를 구할 수 있습니다.

 

import cv2
import numpy as np

# 1. 흑백 배경에 흰색 사각형 
img = np.zeros((500, 500, 3), dtype=np.uint8)
cv2.rectangle(img, (50, 50), (350, 350), (255, 255, 255), -1) 

# 2. 이진화
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

# 3. 윤곽선 찾기
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
#contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 4. 면적, 둘레 구하기
for i, contour in enumerate(contours):
    area = cv2.contourArea(contour) #면적
    perimeter = cv2.arcLength(contour, True) #둘레
    
    print(f"{area}")
    print(f"{perimeter}")
    
    cv2.drawContours(img, [contour], -1, (0, 0, 255), 3)

# 5. 결과 출력
cv2.imshow('Contours with Area & Perimeter', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
728x90
728x90

이미지 처리에서 영역을 분할하는 방법들은 다양합니다. OpenCV에서 제공하는 기능 중 floodFill도 영역을 분할 하는 방법들 중에 하나 입니다. 응용에서는 물체 추적에 전처리나 배경 제거, 물체의 영역 채우기 등에 활용됩니다.

 

아래는 파이썬에서 활용하는 함수 형태와 C/C++에서 사용하는 함수 예 입니다. C/C++에서 활용하는 함수는 두 가지 형태인데 mask 유무가 차이 입니다. 

 

(Python) retval, image, mask, rect = cv2.floodFill(image, mask, seedPoint, newVal,

                                                                                 loDiff=None, upDiff=None, flags=None)

 

(C/C++) int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal,

                                  Rect* rect = 0, Scalar loDiff = Scalar(), Scalar upDiff = Scalar(), int flags = 4);

 

(C/C++) int floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint, 

                    Scalar newVal, Rect* rect = 0, Scalar loDiff = Scalar(), Scalar upDiff = Scalar(), int flags = 4);

 

파라미터 설명은 아래와 같으며 실무 적용에서는 mask 활용할 때와 아닐 때만 유의하면 됩니다. 

 

1) image: 입력 이미지이자 처리 대상 이미지이며 그레이 이미지와 칼라 이미지 적용 가능합니다.

2) mask: 어떤 부분을 채울 때 채우기 작업을 제한하는 역할을 합니다. 예를 들어 영역의 에지를 추출한 Binary 이미지라면 “0”인 부분만 특정 값으로 채울 수 있습니다. 8-bit 그레이 이미지로 정의해야 하며, 원본 이미지보다 가로와 세로가 2 픽셀씩 커야 합니다. 추가로 마스크의 테두리는 “1”로 채우며, 동일한 마스크를 여러번 사용해도 채워진 영역이 겹치지 않도록 할 수 있습니다.

3) seedPoint: 값을 변경하는 시작 좌표(x, y) 입니다.

4) newVal: 변경할 픽셀값 또는 색상 입니다. 칼라라면 (255, 10, 10) 형태가 됩니다.

5) loDiff: 변경할 픽셀값의 하한값 입니다.

6) upDiff: 변경할 픽셀값의 상한값 입니다.

7) flag: 변경 처리 조건을 지정합니다.

 

flag에는 아래 두 가지가 있으며, 실제 적용 시에는 픽셀 연결 방식인 4방향 또는 8방향과 조합해서 사용할 수 있습니다.

 

(flag)

1) FLOODFILL_FIXED_RANGE: 정의된 상한 및 하한 픽셀값 비교

2) FLOODFILL_MASK_ONLY: 입력 이미지는 변화 없으며 mask를 업데이트

(connectivity) 상하좌우 4방향과 대각 포함한 8방향으로 비교

 

아래 예는 mask를 사용하지 않는 파이썬 및 C/C++ 코드 입니다. “flag” 부분을 살펴보면 “flags=4 | cv2.FLOODFILL_FIXED_RANGE”과 “4 | FLOODFILL_FIXED_RANGE”로 정의 되었으며, 4방향 + 정의된 상한 및 하한 픽셀값 비교 로 동일한 의미로 적용되었음을 알 수 있습니다. 

 

import cv2
import numpy as np

# 이미지 로드 및 복사
img = cv2.imread('image.jpg')
im_floodfill = img.copy()

# 마스크 생성
h, w = img.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)

# 위치 및 변환값
seed_point = (125, 125)
new_val = (255, 0, 0)

cv2.floodFill(im_floodfill, mask, seed_point, new_val,
              loDiff=(5, 5, 5), upDiff=(5, 5, 5),
              flags=4 | cv2.FLOODFILL_FIXED_RANGE)

cv2.imshow("Filled Image", im_floodfill)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
    
    // 이미지 로드
    Mat img = imread("image.jpg");
   
    // 마스크 생성
    Mat mask = Mat::zeros(img.rows + 2, img.cols + 2, CV_8UC1);

    // 위치 및 변환값
    Point seedPoint(125, 125);
    Scalar fillColor(255, 0, 0);  

   floodFill(img, mask, seedPoint, fillColor,
              0,              // rect 포인터 (필요 없으면 0)
              Scalar(5,5,5),  // loDiff
              Scalar(5,5,5),  // upDiff
              4 | FLOODFILL_FIXED_RANGE);  // flags

    // 결과 출력
    imshow("Flood Filled", img);
    waitKey(0);
    return 0;
}
728x90
728x90

시간 영역의 신호를 주파수 영역으로 변환하여 처리나 해석하는 이론이 푸리에 변환 Fourier Transform 입니다. 신호처리 분야에서 주파수 영역 분석을 위한 중요한 이론이기도 합니다. 영상처리에서는 이미지를 2차원 신호로 보고 푸리에 변환을 이용할 수 있으며, 이미지 공간 정보를 주파수 성분으로 바꾸어 분석할 수 있습니다. 분석을 통한 응용은 이미지 필터링, 압축, 복원등에 활용됩니다.

 

푸리에 변환은 이론적으로 4가지 형식으로 구분됩니다. 영상처리에 활용되는 기법은 이산 푸리에 변환 DFT (Discrete Fourier Transform) 입니다. 아래는 2차원 이산 푸리에 변환 수식이며 오일러 공식과 결합된 형태 입니다.

 

 

여기서 f(x,y)는 원본 이미지의 픽셀 값, F(u,v)는 주파수 평면에서의 값, M과 N은 이미지 크기인 Width와 Height 입니다. 추가로 푸리에 변환 요소에는 각 주파수 성분의 세기를 나타내는 크기 Magnitude와 성분의 위치 정보를 나타내는 위상 Phase 정보가 있으며, 위상 정보는 이미지의 구조를 유지하는데 중요한 요소입니다.

 

이미지를 푸리에 변환하면 최종 주파수 영역에 중심에는 저주파, 바깥쪽에는 고주파로 구성됩니다. 고주파는 이미지에서 물체의 윤곽선과 같은 에지 Edge 성분들을 의미하고 저주파는 물체의 전반적인 형태를 나타냅니다. 따라서 응용에서는 이러한 특징을 이용하여 저역통과 Low-pass 필터링 및 고역통과 High-pass 필터링을 통해 이미지의 경계 강조나 블러 효과를 낼 수 있습니다. 이미지 압축에서는 고주파 성분이 적으면 적을 수록 작은 정보만으로 이미지 재구성이 가능해지며, 대표적으로 JPEG 손실 압축 알고리즘에서 푸리에 이론의 일종인 이산 코사인 변환 DCT(Discrete Cosine Transform)을 사용합니다.

 

코드를 통해 푸리에 활용 방법을 확인해 보겠습니다. 실무에서는 C/C++, Python, Matlab 등 알고리즘 구현시 FFT (Fast Fourier Transform)을 이용합니다. DFT의 계산 복잡도 때문에 FFT를 활용하게 되며, 예를 들어 샘플 수가 N=1024일 때 DFT는 약 100만번의 연산이 필요한 반면 FFT는 10,000번의 연산이 필요합니다. 아래와 같이 파이썬 Numpy에서는 “np.fft.fft2” 함수를 사용하여 이미지 평면을 주파수 평면으로 변환하며, “np.fft.fftshift” 함수를 통해 저주파 성분을 중심으로 이동 시킵니다. 반대로 주파수 평면에서 이미지 평면으로 변환 시, “np.fft.ifftshift” > “np.fft.ifft2” 함수 순으로 적용할 수 있습니다. 

 

FFT의 이론 및 응용에 대해서는 추후 예와 함께 좀더 상세히 다루도록 하겠습니다.

 

import cv2
import numpy as np

# 이미지 불러오기 (그레이스케일)
img = cv2.imread('lena.jpg', cv2.IMREAD_GRAYSCALE)

f = np.fft.fft2(img) # 푸리에 FFT 변환
fshift = np.fft.fftshift(f)  # 중심 이동

magnitude_spectrum = 20 * np.log(np.abs(fshift)) # 주파수 스펙트럼 확인

f_ishift = np.fft.ifftshift(fshift_filtered) # 중심 복원
img_ifft = np.fft.ifft2(f_ishift) # 역 FFT 적용

# 실수 이미지 변환
img_re = np.abs(img_ifft)
728x90
728x90

이미지에서 우리가 원하는 영역을 찾거나 추출할 때 필요한 방법이 이진화 Binarization 입니다. 물체 탐색, 물체 추적, 이미지 분할 등 알고리즘을 구현할 때 필수적으로 사용해야하는 기법이 이진화 기술들입니다. 이진화 방법에는 다양한 기술들이 있으며, OpenCV 에서 제공하는 기술들 중 하나인 적응적 이진화 기술 Adaptive Thresholding Method에 대해 알아보겠습니다.

 

이진화 방법은 크게 전역적 처리 Global Processing과 지역적 처리 방법 Local Processing 방법으로 구분할 수 있습니다. OpenCV에서 제공하는 “adaptiveThreshold” 방법은 지역적 처리 방법과 적응적 방법이 합쳐진 이진화 방법입니다. Python에서는 아래와 같이 “cv2.adaptiveThreshold” 명으로 적용해 볼 수 있으며, 입력 변수는 아래와 같습니다.

 

[Python] dst = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)

(입력 변수 설명)

- src: 입력 이미지 (Gray Scale Image)

- maxValue: 임계값 Threshold을 초과한 픽셀에 적용할 값 (일반적으로 255)

- adaptiveMethod: 1) cv2.ADAPTIVE_THRESH_MEAN_C, 2) cv2.ADAPTIVE_THRESH_GAUSSIAN_C

- thresholdType: 1) cv2.THRESH_BINARY, 2) cv2.THRESH_BINARY_INV

- blockSize: 픽셀 임계값을 계산할 영역 크기 (홀수: 3, 5, 7 ...)

- C: 계산된 임계값에서 빼는 상수 (일반적으로 0 또는 2~10)

 

여기서 “cv2.ADAPTIVE_THRESH_MEAN_C”는 “blockSize”가 3이라면 3x3 크기 영역 안에 평균 값에서 “C”를 뺀 값을 임계값으로 사용하게 됩니다. “cv2.ADAPTIVE_THRESH_GAUSSIAN_C”는 3x3 크기 영역 안에 가우시안 가중 평균값에서 “C”를 뺀 값을 임계값으로 사용합니다. 앞에서 언급했듯이 “blockSize”는 3은 3x3, 5는 5x5의 블럭을 의미하며 블럭 크기가 작아질 수록 잡음 Noise에 민감해지고 너무 크면 지역적 처리의 이점이 줄어들 수 있으니 설정 시 고민이 필요할 수 있습니다.

 

C/C++ 코드를 사용한다면 아래와 같이 사용할 수 있습니다. 파이썬 함수와 다르게 결과 이미지는 입력 변수와 함께 설정해야 합니다. (dst = 결과 이미지)

[C/C++] adaptiveThreshold(src, dst, maxValue, adaptiveMethod, thresholdType, blockSize, C)

 

지역적이면서 적응적 이진화 방법은 이미지에 조명이 균일하지 않거나 배경이 복잡하여 명암이 불균일한 경우 유용할 수 있으며, 문서 이미지에서 문자 추출 시에도 잘 활용될 수 있습니다. 아래는 파이썬과 C/C++에서 “adaptiveThreshold”를 활용한 예이니 참고해서 보면 좋을 듯 합니다.

 

import cv2

# 그레이 이미지 읽기
img = cv2.imread('grayimage.jpg', cv2.IMREAD_GRAYSCALE)

# 가우시안 적응형 이진화
thresh = cv2.adaptiveThreshold(
    img,
    255,
    cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
    cv2.THRESH_BINARY,
    11,
    2
)

cv2.imshow("Adaptive Threshold", thresh)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

#include <opencv2/opencv.hpp>
using namespace cv;

int main() {
    // 이미지 불러오기 (그레이스케일)
    Mat src = imread("grayimage.jpg", IMREAD_GRAYSCALE);
    Mat dst;
    // 적응형 이진화 적용
    adaptiveThreshold(
        src,              // 입력 이미지
        dst,              // 출력 이미지
        255,              // 최대값
        ADAPTIVE_THRESH_GAUSSIAN_C,  // 적응형 방법
        THRESH_BINARY,    // 임계값 타입
        7,               // 블록 크기 (홀수)
        2                 // C 상수
    );

    // 결과 출력
    imshow("Original", src);
    imshow("Adaptive Threshold", dst);
    waitKey(0);
    return 0;
}
728x90
728x90

보간법 Interpolation 은 영상처리에서 자주 활용됩니다. 이미지 확대나 변환 시 원본 이미지의 특성을 보전하고자 하는 해상도 개선 기법의 기본 입니다. 양선형 보간법 Bilinear Interpolation은 단어에서 같이 선형 보간법을 두번 반복하는 방법이며, 양선형 필터링 또는 양선형 맵핑 등으로도 언급됩니다. 현 블로그에서는 보간법의 이론적인 부분을 간략 다뤄보겠습니다.

 

양선형 보간 과정을 살펴보면, 그림에서 우리가 알고 싶은 점은 실수 좌표 (x, y)에 위치한 픽셀 값입니다. 이 실수 좌표는 정수 좌표 A, B, C, D의 픽셀 사이에 있으며, 이 정수 좌표의 픽셀 값을 이용하여 보간 할 수 있습니다.

 

 

 

4개의 좌표의 픽셀 값을 식으로 표현하면,

 

A = f(x1, y1), B = f(x2, y1), C = f(x1, y2), D = f(x2, y2)

보간 위치: (x, y), 여기서 x1 ≤ x ≤ x2, y1 ≤ y ≤ y2

 

계산 과정을 단계로 보면,

1) X 방향 보간

(A와 B 사이) R1=(x2−x)/(x2−x1)∗A+(x−x1)/(x2−x1)∗B

(C와 D 사이) R2=(x2−x)/(x2−x1)∗C+(x−x1)/(x2−x1)∗D

 

2) Y 방향 보간

(R1과 R2를 이용) P(x,y)=(y2−y)/(y2−y1)∗R1+(y−y1)/(y2−y1)∗R2

 

3) 하나의 공식으로 표현하면,

P(x,y)=A∗(x2−x)∗(y2−y)+B∗(x−x1)∗(y2−y)+C∗(x2−x)∗(y−y1)+D∗(x−x1)∗(y−y1)

여기서, 픽셀 간격이 “1”이라고 가정합니다. 참고로 “1”이 아닌 경우 (x2-x1)*(y2-y1)으로 정규화해주는 것이 일반적입니다.

 

단계별 공식 변환에서처럼 X축 방향과 Y축 방향으로 각각 적용한 방법이 양선형 보간법 입니다. 더 확장해보면 위 식은 다선형 다항식 Multilinear Polynomial 형태로 변환하여 문제를 풀 수 있습니다. 양선형 보간은 이차 다항식이 아니라 두 변수 일차 다항식으로 생각할 수 있으며, 어떤점 (x, y)에서의 값을 2차원이 아닌 일차 다항식 형태로 근사합니다.

 

하나의 공식으로 합쳐진 P(x, y)를 아래와 같이 좌표 (x, y) 기준 다항식으로 변환 할 수 있습니다.

f(x,y)=a0​+a1​x+a2​y+a3​xy

a0​=Ax2​y2​−Bx1​y2​−Cx2​y1​+Dx1​y1

a1​=−Ay2​+By2​−Cy1​+Dy1​

a2​=−Ax2​+Bx1​+Cx2​−Dx1​

a3=A+D−B−C

 

따라서 위 f(x,y) 식을 선형시스템으로 가정하여 우리가 알고 있는 4개의 위치 A, B, C, D를 행렬식으로 정리하여 선형대수로 계수 a0, a1, a2, a3를 구할 수 있습니다. 간략히 Ax=b 형태의 Matrix 계산으로 변환할 수 있으며, 주변 4개의 좌표를 이용한 양선형 보간법의 예를 파이썬 코드로 확인 할 수 있습니다. 계수 계산 시 np.lianlg,solve 함수를 사용합니다.

 

| 1 x1 y1 x1y1 |  |a0|      |f1|

| 1 x2 y1 x2y1 |  |a1|  =  |f2|

| 1 x1 y2 x1y2 |  |a2|      |f3|

| 1 x2 y2 x2y2 |  |a3|      |f4|

 

# Linear System Solution Ax=b
import numpy as np

# 4개 점의 좌표와 픽셀값
x1, x2 = 0, 1
y1, y2 = 0, 1
f1, f2, f3, f4 = A, B, C, D

# 계수 행렬 A
M = np.array([
    [1, x1, y1, x1*y1],
    [1, x2, y1, x2*y1],
    [1, x1, y2, x1*y2],
    [1, x2, y2, x2*y2],
])

# 함수값 벡터
f = np.array([f1, f2, f3, f4])

# 계수 a0, a1, a2, a3 계산
a = np.linalg.solve(M, f)

# 양선형 보간 함수
def bilinear_interp(x, y):
    return a[0] + a[1]*x + a[2]*y + a[3]*x*y
728x90
728x90

블로그에 글 항목들을 보니 컴퓨터 비전, 이미지 처리의 가장 기본적인 항목이 빠져 있어 이 참에 작성해 봅니다. 이미지라는 것은 현실 세계의 한 장면을 센서로 취득한 뒤 2차원 형태의 매트릭스 Matrix로 디지털화 한 데이터로 볼 수 있습니다. 이 매트릭스 하나의 셀 Cell을 픽셀 Pixel 이라고 합니다. 각 픽셀은 색상과 밝기를 수치로 저장 할 수 있습니다.

 

  • 픽셀 Pixel (Picture Element)

이미지를 구성하는 가장 작은 단위 입니다. 디스플레이를 위한 각 픽셀은 정수 Unsigned Integer로 저장되며 이미지 처리 시 부동소수점 Floating Point 형태로 변환하여 계산할 수 있습니다. 예를 들어 보통 우리가 보는 흑백 이미지 Grayscale의 경우 픽셀 값의 범위는 0~255의 값을 갖습니다.

 

  • 해상도 Resolution

해상도는 이미지의 크기를 의미합니다. 이미지의 "가로 Width X 세로 Height = 픽셀 수" 로 표현되고, 우리가 TV를 구매할 때 HD, Full HD, Ultra HD 용어가 해상도를 나타냅니다. 예를 들어 HD는 1280x720, Full HD는 1920x1080, Ultra HD는 3840x2160 크기로 정의되고, 계산해보면 Full HD는 207만 개 이상의 픽셀로 구성됨을 알 수 있습니다.

 

  • 채널 Channel

이미지 처리에서 채널은 색상 성분을 나누어 표한 각각의 구성 요소를 의미합니다. 흑백 이미지 Grayscale의 경우 밝기 정보만 존재하므로 1-채널로 볼 수 있습니다. 칼라 이미지 RGB의 경우 색상 표현을 위한 빨강, 초록, 파랑의 3개의 채널로 구성 됩니다. 이미지 처리를 하다 보면 칼라 이미지인데 RGBA 형태도 볼 수 있으며, 마지막 A는 투명도 조절 역할을 하는 채널입니다. 예를 들어 A의 값이 낮아질 수록 빨강색은 연한 빨강색으로 표현됩니다.

 

  • 깊이 Bit Depth

한 픽셀의 색 또는 밝기를 표현할 수 있는 단계의 범위 입니다. 우리가 생활에서 접할 수 있는 흑백 이미지 Grayscale의 경우 256단계의 범위를 가지며 8-bit 이미지라고도 합니다. 칼라 이미지는 RGB 각각 8-bit으로 구성되며 합쳐서 24-bit 이미지로 불립니다. (8-bit: 2의 8승 = 256) 세부적인 처리가 필요한 의료 분야 등에서는 10-bit (1,024 단계) 이상의 범위로 이미지를 저장하여 활용합니다.

 

  • 포맷 Format (이미지 저장 형식)

일상에서 이미지는 메모리에 파일 형태로 저장되며, 그 파일 형태를 포맷이라고 합니다. 가장 익숙한 포맷은 BMP와 JPG가 있습니다. BMP는 마이크로소프트에서 개발된 디지털 이미지 포맷이며 무손실 이미지로 저장되므로 파일 크기가 큽니다. JPG는 ISO와 ITU-T에서 제정된 손실 압축 방법의 표준 이미지 포맷입니다. 손실 압축이므로 압축 비율에 따라 데이터 크기는 작아 질 수 있습니다. 이 외 TIFF가 있으며 앨더스사와 마이크로소프트가 공동 개발한 이미지 포맷이며 무손실 및 손실 압축이 가능하고 사용자가 고쳐서 사용할 수 있는 유연함이 특징입니다. 추가로 GIF, PNG, RAW 등의 포맷이 있습니다.

 

  • 이미지 저장 공간 

흑백 이미지 Grayscale의 경우 1-채널이므로 메모리에는 2차원 배열로 저장됩니다. 칼라 이미지 RGB는 3-채널이므로 3차원 배열로 저장됩니다. 아래는 Grayscale과 RGB의 각 픽셀값을 추출하는 파이썬 예제 코드 입니다. RGB 추출 시 순서는 B, G, R 이니 혼동하지 않도록 주의해야 합니다.

 

# Grayscale

gray = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE) # 이미지 로드

pixel_value = gray[50, 100] # 픽셀 정보 출력, 좌표 (50, 100)

 

# RGB

img = cv2.imread('image.jpg') # 이미지 로드

(b, g, r) = img[50, 100] # 픽셀 정보 출력 (좌표 (50, 100)) → [B, G, R]

 

이상에서 같이 디지털 이미지는 2차원 매트릭스 구조로 볼 수 있으며, 2차원 이산 신호처리 2D Discrete Signal Processing을 적용 할 수 있습니다. 영상처리에 적용되는 이론들 대부분이 푸리에 변환 및 필터링 등과 같은 신호처리 개념입니다.

728x90
728x90

영상처리 기술은 이미지를 활용하는 다양한 산업분야에서 핵심적인 역할을 하고 있습니다. 현재 많은 분야에서 확장되고 있는 AI분야 머신러닝과 딥러닝에서도 부분적으로 영상기술 이론이 주요하게 사용되고 있습니다. 영상처리 기술을 활용하는 산업 분야를 간략히 요약하면 아래와 같습니다.

 

산업 자동화

  • 제조 공정에서 불량품 검출, 로봇 비전 등에 활용되며, 고속 카메라와 영상분석을 통해 사람의 개입 없이 품질을 관리할 수 있습니다.

의료 영상

  • MRI, CT, X-ray 등에서 병변을 자동으로 식별하거나, 조직을 정량적으로 분석하며, 진단 정확도를 높여 줍니다.

보안 및 감시

  • CCTV 영상에서 이상행동 탐지, 얼굴 인식, 번호판 인식 등에 사용되며, 범죄 예방 및 수사에 핵심적인 역할을 합니다.

자율주행

  • 카메라로 주변 물체를 인식하고, 차선, 신호, 보행자 등을 분석하며, 딥러닝을 활용한 실시간 처리에 핵심적인 역할을 합니다.

엔터테이먼트 및 미디어

  • 영상 편집, 필터 적용, 실시간 합성, 가상현실(VR), 증강현실(AR) 등에서 쓰이며, 사용자 경험 UI/UX에도 활용됩니다.

 

몇 년 사이에 AI기술이 확장 및 확대 되면서 컴퓨터 비전과 영상처리 라이브러리들이 다양화 되었습니다. 특히 파이썬 Python 프로그래밍 활용이 확대 되면서 파이썬 관련 영상처리 라이브러리가 많아진 것을 볼 수 있습니다. 아래는 대표적인 라이브러 설명이며, OpenCV의 경우 참 오래된 라이브러이기도 하고 꾸준히 사용되 되었습니다. AI 성장과 함께 현재는 영상처리 활용에 핵심 라이브러리의 하나이기도 합니다.

 

1) OpenCV (Open Source Computer Vision)

  • 가장 널리 사용되는 오픈소스 라이브러리. 영상 입출력, 필터, 물체 인식 등 광범위한 기능을 제공하며 C/C++과 파이썬에서 활용할 수 있습니다.

2) MATLAB

  • 영상처리 전용 툴박스 제공, 연구 및 교육용으로 활용도 높으며 최근 실무에서도 활용도가 높아지고 있습니다. MATLAB 자체가 유료이므로 라이브러리도 유료로 사용할 수 있습니다.

3) Pillow(PIL)

  • 파이썬 기반 영상 처리 라이브러리이며, 단순한 이미지 처리에 적합합니다.

4) Scikit Image

  • 파이썬 과학 연산 라이브러리 기반 영상처리 라이브러리이며, 연구 및 분석 목적에 적합합니다.

5) PyTorch

  • 파이썬 머신러닝, 딥러닝 기반 영상처리에 적합하며 분류, 객체탐지, 세크멘테이션에 활용 됩니다.

6) MediaPipe

  • 구글에서 개발한 실시간 영상처리 프레임워크이며 얼굴, 손, 포즌 인식에 이용됩니다. C/C++ 및 파이썬을 통해 활용할 수 있습니다.

 

이미지 처리는 단순 필터 적용을 넘어서 AI와 융합되어 인공지능 기술의 시각지능의 핵심이 되고 있습니다. 특히 영상에서의 의미 있는 정보 추출과 자동화가 가능해져 다양한 기술 분야에 확대 적용되고 있습니다. 

 

영상처리 라이브러리 중 MediaPipe에 대해 조금 더 알아 보겠습니다. MediaPipe는 머신러닝 기반의 멀티모달 파이프라인을 쉽게 만들고 실행할 수 있게 해주는 프레임워크입니다. 실시간 영상에서 정교한 분석을 저지연으로 처리할 수 있도록 설계되어, 모바일, 데스크탑, 웹 등 다양한 환경에서 실행 가능합니다.

 

MediaPipe의 주요 특징으로는 CPU에서도 실시간 처리가 가능할 정도로 경량화 된 모델 구조이며, 각 기능이 독립된 컴포넌트로 구성되어 있습니다. 크로스 플랫폼으로 사용 가능하며, 사전 학습된 모델도 제공 됩니다. 기능으로는 얼굴 감지 및 인식, 손 제스처 인식, 포즈 예측, 신체 모션 분석, 3D 객체 인식 등이 있습니다. 아래는 설치 방법 입니다.

 

설치 : pip install mediapipe opencv-python

파이썬 활용 시: import mediapipe as mp

 

추가로 OpenCV는 .NET 프레임워크 환경 C# 등에서도 활용 가능합니다. OpenCvSharp은 C++ OpenCV의 .NET 바인딩 라이브러리입니다. 쉽게 이야기 하면 C++ 코드의 함수를 C#에서 호출하는 방식으로 볼 수 있습니다. 아래 첫번째 예는 OpenCvSharp을 이용한 이미지 읽기이며, 두번째는 웹캠 영상 로드 예 입니다.

 

// 이미지 읽기
using OpenCvSharp;

Mat image = Cv2.ImRead("lena.jpg");
Cv2.ImShow("lena", image);
Cv2.WaitKey(0);

 

// 웹캠 이미지 로드
using OpenCvSharp;

using var capture = new VideoCapture(0);
using var window = new Window("WebCam");

var frame = new Mat();
while (true)
{
    capture.Read(frame);
    if (frame.Empty()) break;

    window.ShowImage(frame);
    if (Cv2.WaitKey(1) == 27) break; // esc 종료
}
728x90
728x90

영상처리 기술에서 보간법은 이미지 확대나 회전, 좌표 변환 기술 Affine Transform 등 알고리즘 구현 시 빈번히 사용됩니다. 머신 러닝 Machine Learning 및 딥러닝 Deep Learning 등 특히 CNN 계열에 Coarse Map을 Dense Map으로 Up-Sampling시 활용되는 기술이기도 합니다. 이미지를 두 배로 확대할 때 보간법은 새로운 픽셀 Pixel 값을 그럴듯하게 예측하는 기술로 볼 수 있습니다. 이론적인 부분은 영상처리 기술에서 다루도록 하겠습니다.

 

기본적인 보간법 세가지는 아래와 같습니다.

 

1. 최근접 이웃 보간법 Nearest Neighbor Interpolation

- 소수점 좌표에서 가장 가까운 정수 좌표 픽셀 사용, 수행속도가 가장 빠르지만 계단 현상 및 깨짐 현상 발생

 

2. 양선형 보간법 Bilinear Interpolation

- 주변 4개 픽셀을 사용해 선형 평균을 이용, 최근접 보다 자연스러운 결과를 보이고 일반적으로 많이 활용되는 기술

 

3. 양큐빅 보간법 Bicubic Interpolation

- 주변 16개 픽셀을 활용하며 앞에 두 기술보다 자연스러우며, Super Resolution 기술들에서 비교 결과로 많이 사용, 고화질 이미지 편집에 사용되는 기술이며 처리속도는 느림

 

이미지 회전 시 보간법이 사용되는 이유는 회전은 수학적으로 기존 픽셀 좌표를 회전 행렬로 이동시키는 과정입니다. 회전 시 생성되는 좌표는 보통 소수점 좌표가 되며, 새로운 평면에 매칭 시 좌표는 소수점이 아닌 정수이기때문에 빈공간이 생기게 됩니다. 그 빈공간을 채워주기 위해 보간법이 활용됩니다. 예를 들어 좌표 (5, 5)를 30도 회전하면 (5.3, 3.9) 되므로 실제 좌표 매칭을 위해서는 주변 픽셀 (5, 3), (6, 3), (5, 4), (6, 4)를 이용하여 추정해야 합니다.

 

OpenCV에서 이미지 회전 및 확대 시 보간법을 지정할 수 있습니다. 아래는 파이썬 활용시 CV 상수 입니다. 첫번째 예제는 회전시 파이썬 코드이며, 이전 블로그에서 설명한 “cv2.warpAffine” 함수를 사용합니다. 두번째 예제는 “cv2.resize” 함수를 활용한 보간법 사용 예입니다. 

 

1. 최근접 이웃 보간법 - cv2.INTER_NEAREST

2. 양선형 보간법 - cv2.INTER_LINEAR

3. 양큐빅 보간법 - cv2.INTER_CUBIC

4. Lanczos 보간법 - cv2.INTER_LANCZOS4

 

# 이미지 회전 예제
import cv2
import numpy as np

# 이미지 로드
img = cv2.imread('lena.jpg')

# 회전 중심, 각도, 스케일
center = (img.shape[1] // 2, img.shape[0] // 2)
angle = 50
scale = 1.0

# 회전 행렬 생성
M = cv2.getRotationMatrix2D(center, angle, scale)

# 양큐빅 보간법 사용예
rotated = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]), flags=cv2.INTER_CUBIC)

# 결과 보기
cv2.imshow('Rotated Image', rotat_result)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

# 이미지 확대 예제
img = cv2.imread('lena.jpg')

# 2배 확대, 선형 보간법
resized = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR)

# 결과 보기
cv2.imshow('Resized Image', resiz_result)
cv2.waitKey(0)
cv2.destroyAllWindows()
728x90
728x90

현 블로그에서는

오래된 PC 하드 HDD에서 SSD로 교체, 윈도우 OEM 제품키 확인하기 (1)

에 이어 오래된 데스크탑 보드 체크 및 청소와 윈도우 설치 과정을 설명합니다.

 

( 글 순서 )

1. 윈도우 드라이브 마이그레이션

2. 윈도우 제품 키 확인 + PC 청소

3. 윈도우 설치 USB 만들기 + 윈도우 설치 

 

윈도우 설치하기 전에 PC 청소 겸 구매한 SSD 연결 포트 확인을 위해 메인보드 확인을 해 보았습니다. 오래된 PC 이긴 하지만 그 당시 고사양이라 그런지 메인보드에 SATA 포트가 총 6개가 보였고, 4개가 SATA2(@3Gbps)고 2개 포트가 SATA3(@6Gbps) 인것을 확인할 수 있었습니다. 내장형 SSD 연결에서는 SATA3 한포트를 이용했고요, GPU도 해체해서 보니 세월이 흔적이 많이 보였습니다. 

 

 

 

분리된 GPU 그림에서처럼 냉각팬간 써멀구리스가 마르고 닳아있어서, 이참에 CPU와 GPU 각각 분리해서 청소하고 써멀구리스 새로 도포해줬습니다. 써멀구리스 구매와 사용방법은 검색해보시면 많은 블로그에 잘 설명되어 있으니 참고하시면 됩니다.  

 

3. 윈도우 설치 USB 만들기 + 설치

 

기존에 윈도우10이 설치 되어 있었기 때문에 설치 디스크도 윈도우10으로 만들었고요, 설치 USB를 만들기 위해 기본적으로 8G 이상 USB를 준비하면 됩니다. 설치 USB 만들어보니 과정이 쉽게 되어있어서 금방 만들수 있었습니다.

 

윈도우10 설치 usb 만들기 https://www.microsoft.com/ko-kr/software-download/windows10

 

위 주소 화면에서 아래와 같이 “Windows 10 설치 미디어 만들기” 선택하면 됩니다. 중간 과정인 원하는 작업 선택에서는 “다른 PC용 설치 미디어 만들기” 선택, 사용할 미디어 선택에서는 “USB 플래시 드라이브” 선택하고, USB 플래시 드라이브 선택에서 PC에 삽입한 8G USB 선택하면 완료가 됩니다.

 

설치 USB가 만들어지면 USB 레이블이 “ESD-USB”로 바뀌어 있는것을 확인할 수 있습니다.

 

 

 

USB 설치 디스크가 만들어졌으니 간략히 설치 진행 과정을 설명하겠습니다. 설치 진행 전 구매한 내장형 SSD를 연결하고, 설치 USB를 PC에 삽입합니다.

 

1) 부팅을 USB로 해야합니다. 따라서 BIOS를 통해 부팅 순서를 USB로 변경 합니다. 제 PC의 경우 부팅 후 ESC키를 반복적으로 눌러 진입 할 수 있었습니다. 변경 후 재부팅을 하면 USB를 통해 윈도우 설치가 진행 됩니다.

2) 윈도우 설치가 진행되면 다른 변경 없이 다음으로 계속 진행

3) 정품 인증이 나오면 “제품 키 없음”으로 진행

4) 윈도우 버전 선택에서는 제 경우 기존과 동일한 버전 선택

5) 설치 유형은 “사용자 지정” 선택

6) 윈도우 설치 위치 지정에서는 장착해놓은 SSD 선택, 필요에 따라 여기서 포맷도 할 수 있음

7) 여기까지 설치 완료. 재부팅 시 USB 제거

8) 재부팅 후 윈도우 기본 설정 진행하고 정품인증, 업데이트 진행 이후 부팅 순서 변경

 

정품인증의 경우 기존 윈도우 제품키를 찾아놨었는데 브랜드 PC에 OEM이다 보니 윈도우가 설치되면서 자동 인증이 되어있어서 별도 정품인증은 하지 않았습니다. 그리고 윈도우 설정시 이메일 계정단계가 있는데 윈도우10에 경우 이메일 없이 로컬계정으로 사용이 가능합니다. 

728x90
728x90

10년이 훨씬 넘은 구형 데스크탑 PC가 한대 있어 어떻게 처리할까 살펴보다가 SSD 교체로 재사용 해보기로 했습니다. 그 당시 고성능 PC로 i5 3.3GHz에 1TB 7200RPM SATA 3G, GeForce 550Ti가 장착되어 있었고요, 어느때부터 아이들 교육용 PC로 사용하다가 HDD가 오래되다보니 버벅거리는 상태가 되었네요. 하드디스크 HDD 교체와 램 RAM 만 추가해주면 기본적인 영상처리 알고리즘 구현 및 프로그래밍 정도는 가능할 수 있어 보였습니다. 그래서 PC 리뉴얼을 위해 제가 확인하고 진행한 과정은 아래와 같습니다. 

 

( 글 순서 )

1. 윈도우 드라이브 마이그레이션

2. 윈도우 제품 키 확인 + PC 청소

3. 윈도우 설치 USB 만들기 + 윈도우 설치

 

위 세 과정을 진행하면서 중간중간 시간을 허비한 부분이 있어, 도움 될 만한 부분이 있으면 참고하셨으면 좋겠습니다.

 

1. 윈도우 C: 드라이브 마이그레이션

 

SSD 교체 시 여러 방법들을 찾아보던 중에 드라이브 마이그레이션 방법을 알게 되어서 시도를 했습니다. 윈도우 설치 파일을 만들 필요도 없고 윈도우 제품키도 필요 없으니 이거다 싶었죠. 내장형 SSD 하나를 구매하고 마이그레이션 툴들을 검색해보니 여러 종류가 있는데 유료와 무료로 나뉩니다. 무료라고 하는 툴들도 정책이 바뀌었는지 진행하다보면 비용 지불 팝업이 뜨네요. 제가 사용해 본 툴은 매크리움 리플렉트 Macrium Reflect 툴이고요, 현 시점 30일 무료로 사용할 수 있습니다.

 

https://www.macrium.com/reflectfree?mo

 

위 사이트에 들어가면 Home User > Reflect X Home Free Trial로 설치하면 됩니다. 툴 사용법은 여러 블로그들에 설명이 잘 되있고요, HDD 전체 드라이브를 SDD 전체 드라이브로 마이그레이션 하는 방법과 실제 OS로 사용하는 C: 드라이브만 선택적으로 마이그레이션 할 수 있습니다. 주의할 점은 전체를 하든 일부 파티션만 하든 각 용량과 같거나 커야 합니다. 예를 들어 HDD 전체가 500G라면 SDD도 500G 이상으로 준비하면 됩니다.

 

제 경우 OS로 사용하는 C: 드라이브만 마이그레이션을 시도 했었고요, 결론적으로는 실패했습니다. 이유는 오랜기간 HDD를 사용하다보니 배드섹터 Bad Sector가 많아 마이그레이션 오류가 문제 였습니다. 디스크 정리를 하면 된다고 하는데 OS 드라이브다보니 문제가 될 수 있어 포기하고 깨끗하게 새로 설치하기로 했습니다.

 

2. 윈도우 Window 제품키 확인

 

윈도우를 새로 설치하게 되면 기존 제품키를 알고 있거나 구매를 해야하는데 우선 현재 윈도우 버전과 제품키를 확인했습니다. 구형 PC가 모 브랜드 PC여서 본체에 제품키를 확인할 수 있었습니다. 그런데 최초 윈도우7에서 중간에 윈도우 10으로 업그레이드를 해서 제품키를 한번더 확인해 봤고요, 윈도우 키 확인 방법들도 여러가지 있는데 아래 두가지로 확인해 봤습니다.

 

( 윈도우키 확인 방법 )

1) wmic path softwarelicensingservice get Oa3xOriginalProductKey

2) reg query “HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SoftwareProtectionPlatform” /v BackupProductKeyDefault

 

명령 프롬프트 cmd 창을 열어서 위 둘 중에 하나를 넣어 주시면 됩니다. 제 PC의 경우 2)번으로 시리얼키 25자리 확인할 수 있었습니다. 최초 윈도우7일때 키와 윈도우10으로 업그레이드 되면서 윈도우 키도 바뀐걸 알 수 있었고, 윈도우 구매 없이 설치 진행해 갈 수 있었습니다.

 

PC 청소 및 윈도우 설치 과정은 다음 블로그에서 확인할 수 있습니다. 

728x90
728x90

윈폼 환경에서 개발을 하다 보면 여러개의 SubForm 생성이 필요한 경우 가 빈번합니다. 첫 번째 예에서는 한장의 이미지를 Main에서 Sub로 전달하는 방법입니다. 예에서는 이미지를 읽어 와서 전달하는 예이지만 메모리에 저장되어 있는 Bitmap 등의 이미지도 동일한 방법으로 전달이 가능합니다. 예에서는 SubForm에 PictureBox가 이미 추가 되어 있다고 가정합니다.

 

1) 이미지 한장 전달 시, 서브폼 SubForm 및 메인폼 MainForm

using System;
using System.Drawing;
using System.Windows.Forms;

// sub form
public partial class SubForm : Form
{
    public SubForm(Image image)
    {
        InitializeComponent();
        if (image != null)
        {
            pictureBox1.Image = image; // picture box
        }
    }
}

 

using System;
using System.Drawing;
using System.Windows.Forms;

// main form
public partial class MainForm : Form
{
    private Image lenaImage;

    public MainForm()
    {
        InitializeComponent();
        lenaImage = Image.FromFile("Lena.jpg");  // 로컬 이미지 로드
    }

    private void btnOpenSubForm_Click(object sender, EventArgs e) // 버튼
    {
        SubForm subForm = new SubForm(lenaImage);
        subForm.Show();
    }
}

 

필요따라 여러 장의 이미지를 전달할 경우도 있습니다. 두번째 예에서는 리스트 List를 이용하여 여러 이미지를 전달하는 방법입니다. 이미지 뿐만 아니라 다른 형 Type에 여러 데이터를 전달 할때에는 ArrayList를 활용하면 쉽게 전달 할 수 있습니다.

 

2) 이미지 여러장 전달 시, 서브폼 SubForm 및 메인폼 MainForm

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

// sub form
public partial class SubForm : Form
{
    public SubForm(List<Image> images)
    {
        InitializeComponent();
        
        if (images != null && images.Count > 0)
        {
           // 여러 Picture Box에 각 이미지 표시
            pictureBox1.Image = images[0];
            pictureBox2.Image = images[1];
            pictureBox3.Image = images[2];
            
        }
    }
}

 

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

// main form
public partial class MainForm : Form
{
    private List<Image> imageList = new List<Image>();

    public MainForm()
    {
        InitializeComponent();

        // 예시: 이미지 여러 개 로드
        imageList.Add(Image.FromFile("A.jpg"));
        imageList.Add(Image.FromFile("B.jpg"));
        imageList.Add(Image.FromFile("C.jpg"));
    }

    private void btnOpenSubForm_Click(object sender, EventArgs e)
    {
        SubForm subForm = new SubForm(imageList);
        subForm.Show();
    }
}
728x90

+ Recent posts