Skip to content

Repository files navigation

Halcon-Library

HALCON 13 · Regions ▸ Features 자체 구현 엔진

MVTec HALCON 의 리전 특징값 오퍼레이터를, SDK 없이 C++ 로 재현합니다.

C++ Platform Dependencies Header Operators Tests Pending


이게 뭔가

검사 프로그램이 불량 Blob 의 형상 특징값(면적·원형도·볼록도·장단축·방향 등)을 HALCON 런타임 라이선스 없이 HALCON 과 같은 수치로 얻게 해주는 C++ 라이브러리입니다.

배포 형태 static lib (GlimHalcon.lib) 또는 DLL (GlimHalcon.dll + import lib)
공개 면적 헤더 1개include/GlimHalcon.h
의존성 없음. 표준 라이브러리만 씁니다. OpenCV·Halcon 을 링크하지 않습니다
표준 / 문자셋 C++14 / 멀티바이트(_MBCS). 유니코드 매크로를 켜지 않습니다
스레드 오퍼레이터는 무상태 순수 함수. Region 을 스레드마다 따로 만들면 락 없이 병렬 호출 가능

성공 기준은 하나뿐입니다 — HALCON 실측값과 일치하는가. 구현 개수는 성과가 아닙니다. 값이 틀린 40종보다 값이 맞는 5종이 낫습니다.


1. 검사기 프로젝트에 붙이기

1-1. static lib 로 링크 (권장)

DLL 보다 이쪽을 권장합니다. CRT 버전·/MT·/MD 불일치 문제가 원천적으로 없고, 배포 시 DLL 을 따라다니게 할 필요가 없습니다.

① 빌드해서 산출물 얻기

cmake -S . -B build -G "Visual Studio 18 2026" -A x64
cmake --build build --config Release
build\Release\GlimHalcon.lib      ← 이 파일
include\GlimHalcon.h              ← 이 헤더

② Visual Studio 프로젝트 속성 (검사기 프로젝트에서, 구성·플랫폼 모두 동일하게)

속성 페이지 항목
C/C++ ▸ 일반 추가 포함 디렉터리 C:\glim\pgm\Halcon-Library\include
링커 ▸ 일반 추가 라이브러리 디렉터리 ...\build\Release
링커 ▸ 입력 추가 종속성 GlimHalcon.lib

③ 반드시 맞춰야 하는 것 — 안 맞으면 링크가 깨집니다

항목 설명
플랫폼 검사기가 x86 이면 라이브러리도 x86 (-A Win32). 섞이면 LNK1112
런타임 라이브러리 C/C++ ▸ 코드 생성 ▸ 런타임 라이브러리가 양쪽 동일해야 합니다 (/MD/MD, /MT/MT). 다르면 LNK2038
플랫폼 도구 집합 되도록 같은 툴셋(v140 / v143 …)으로. 다르면 표준 라이브러리 심볼이 어긋날 수 있습니다
문자셋 라이브러리는 _MBCS 로 빌드됩니다. 공개 헤더에 문자열 타입이 없어 실제 영향은 없지만, 맞춰 두는 편이 안전합니다

LNK2019: ComputeCentralMoments2nd 외부 기호를 확인할 수 없습니다 가 뜨면 라이브러리 쪽에 src/Core/Moments.cpp 가 빠진 것입니다. CMake 로 재생성하면 해결됩니다.

1-2. DLL 로 링크

cmake -S . -B build -G "Visual Studio 18 2026" -A x64 -DGLIM_HALCON_BUILD_SHARED=ON
cmake --build build --config Release
build\Release\GlimHalcon.dll      ← 실행 파일 옆에 복사
build\Release\GlimHalcon.lib      ← import lib. 링커 ▸ 입력에 추가

사용처에서는 아무것도 정의하지 않습니다. 헤더가 알아서 dllimport 로 동작합니다.

DLL 을 쓸 때 지켜야 할 것 공개 헤더에 std:: 타입이 하나도 없고, Region 의 생성·복사·소멸이 전부 DLL 안에서 일어나도록 설계했습니다. 그래서 CRT 가 달라도 힙이 섞이지 않습니다. 대신 헤더에 인라인 함수를 추가하지 마세요. 사용처에서 인라인으로 할당·해제가 일어나는 순간 이 보장이 깨집니다.


2. 호출하기

2-1. 가장 흔한 경우 — 이진 마스크 crop 한 장

검사기가 이진화한 128×128 crop 을 던지면, 그 안의 최대 blob 하나를 골라 특징값 7개를 돌려줍니다.

#include "GlimHalcon.h"

using namespace glim::halcon;

RegionFeatures f;
if (ComputeLargestBlobFeatures(pMask, 128, 128, 128, f))
{
	// f.area          면적 (픽셀 개수)
	// f.row, f.column 무게중심 (HALCON 관례 = y, x)
	// f.contLength    윤곽 길이 (구멍 제외)
	// f.circularity   원형도  0~1
	// f.compactness   조밀도  >= 1
	// f.convexity     볼록도  <= 1

	if (f.convexity < 0.85)
		nDefectType = DEFECT_TEAR;		// 볼록도가 낮으면 찢김
}
else
{
	// 입력이 잘못됐거나(널 포인터 / w,h <= 0 / 0 < stride < width)
	// 전경이 하나도 없다. 이때 f 는 전 필드 0 이다.
}

stride 규약

동작
0 이하 width 로 간주
>= width 그대로 사용 (행 패딩이 있는 버퍼)
0 < stride < width 빈 리전 반환. 성립할 수 없는 값이라 읽지 않습니다

2-2. 마스크에서 Region 을 만들어 개별 오퍼레이터 호출

기존 HALCON 스크립트를 1:1 로 옮길 때 씁니다. 함수 이름이 HALCON 원명과 대응됩니다.

Region region = Region::FromMask(pMask, width, height, stride);

long   area;  double row, column;
AreaCenter(region, area, row, column);			// HALCON: area_center

double ra, rb, phi;
EllipticAxis(region, ra, rb, phi);				// HALCON: elliptic_axis

double aniso, bulk, sf;
Eccentricity(region, aniso, bulk, sf);			// HALCON: eccentricity

Region 은 값 타입입니다. 복사는 O(1)(내부 데이터 공유), 소멸은 자동입니다. delete 하지 마세요.

2-3. 값이 2개 이상 필요하면 배치 API 를 쓰세요

개별로 호출하면 같은 중간산물을 매번 다시 계산합니다. 1차 5종을 따로 부르면 윤곽 추적만 4번 돕니다. 배치 API 는 리전당 1회입니다.

RegionFeatures f;
RegionMoments  m;
ComputeFeaturesAndMoments(region, f, m);		// 값은 개별 호출과 완전히 동일

3. 검사 결과를 CSV 로 쓰기

실제 목적지가 CSV 라면 이 형태가 됩니다. 멀티바이트 MFC 기준입니다.

#include "GlimHalcon.h"
using namespace glim::halcon;

// 헤더는 파일을 새로 만들 때 한 번만
static void WriteCsvHeader(FILE* fp)
{
	fprintf(fp,
		"Frame,Lane,BlobNo,"
		"Area,Row,Column,ContLength,Circularity,Compactness,Convexity,"
		"M11,M20,M02,Ia,Ib,Ra,Rb,Phi,Anisometry,Bulkiness,StructureFactor,Orientation\n");
}

// blob 한 개를 한 줄로
static void WriteCsvRow(FILE* fp, int frame, int lane, int blobNo,
						const RegionFeatures& f, const RegionMoments& m)
{
	// %.9g : 유효자리를 보존하면서 지수표기 남발을 막는다.
	//        %f 로 쓰면 M20 같은 큰 값에서 자리수가 잘리고, 엑셀에서 되돌릴 수 없다.
	fprintf(fp,
		"%d,%d,%d,"
		"%ld,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,"
		"%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g,%.9g\n",
		frame, lane, blobNo,
		f.area, f.row, f.column, f.contLength, f.circularity, f.compactness, f.convexity,
		m.m11, m.m20, m.m02, m.ia, m.ib,
		m.ra, m.rb, m.phi, m.anisometry, m.bulkiness, m.structureFactor, m.orientation);
}

// 검사 루프
void CInspector::SaveBlobFeatures(int frame, int lane,
								  const unsigned char* pMask, int w, int h, int stride)
{
	Region region = Region::FromMask(pMask, w, h, stride);
	Region blob   = SelectLargestBlob(region);		// 최대 blob 하나만

	RegionFeatures f;
	RegionMoments  m;
	ComputeFeaturesAndMoments(blob, f, m);

	FILE* fp = fopen(m_strCsvPath, "at");			// 멀티바이트 경로
	if (fp == NULL)
		return;

	if (_filelength(_fileno(fp)) == 0)
		WriteCsvHeader(fp);

	WriteCsvRow(fp, frame, lane, 0, f, m);
	fclose(fp);
}

CSV 로 쓸 때 실수하기 쉬운 것 3가지

%f 로 쓰지 마세요 M20 은 큰 면적에서 1e17 규모까지 갑니다. %f 는 자리수를 잘라버려 되돌릴 수 없습니다. %.9g 또는 %.17g 를 쓰세요
프레임당 열지 마세요 매 프레임 fopen/fclose 는 실시간 경로에서 비쌉니다. 핸들을 유지하거나 메모리에 모아 배치로 flush 하세요
Anisometry = 0 을 필터로 지우지 마세요 아래 §5 를 보세요. 가장 길쭉한 불량이 0 을 냅니다

4. 구현된 오퍼레이터 — 10 / 40

1차 배치 · 기본 형상 ✅ 실측 통과 (PASS 70 / FAIL 0)

오퍼레이터 C++ 함수 출력
area_center AreaCenter 면적 · 무게중심
contlength ContLength 윤곽 길이 (구멍 제외)
circularity Circularity min(1, F/(max²π))
compactness Compactness max(1, L²/(4Fπ))
convexity Convexity F_o / F_c

2차 배치 · 2차 모멘트 계열 🟡 빌드 검증 대기

오퍼레이터 C++ 함수 출력 정규화
moments_region_2nd MomentsRegion2nd M11 M20 M02 Ia Ib 없음 (순수 합)
moments_region_2nd_invar MomentsRegion2ndInvar M11 M20 M02
elliptic_axis EllipticAxis Ra Rb Phi 내부 F
eccentricity Eccentricity Anisometry Bulkiness StructureFactor
orientation_region OrientationRegion Phi (-π ≤ φ < π)

HALCON 원명 대응이 없는 추가 API 5개

함수 용도
ComputeFeatures(region, RegionFeatures&) 1차 5종의 값 7개를 한 번에
ComputeMoments(region, RegionMoments&) 2차 5종의 값 15개를 한 번에
ComputeFeaturesAndMoments(region, f, m) 위 둘을 합쳐 윤곽 추적 1회 · hull 1회 · 모멘트 1회
SelectLargestBlob(region) 최대 연결성분(8-연결) 하나만 남긴 리전. 동률이면 스캔 순서상 먼저인 것
ComputeLargestBlobFeatures(data, w, h, stride, f) 검사기 원샷. 이 함수만 bool 반환

ComputeLargestBlobFeaturesbool 인가 다른 함수는 이미 검증을 통과한 Region 을 받으므로 "실패" 가 사실상 빈 리전뿐입니다. 그러나 이 함수는 원시 포인터와 치수를 직접 받아 입력 자체가 틀릴 수 있고, 그것은 "전경이 없는 정상 리전(면적 0)" 과 의미가 다릅니다. 검사기가 둘을 구분하지 못하면 조용히 틀린 판정을 내립니다.

전체 40종 현황은 docs/00_OVERVIEW.md 에 있습니다.


5. 값 규약 — 모르면 오판합니다

빈 리전 / 실패는 전부 0

HALCON 규약을 그대로 따릅니다. 예외를 공개 API 밖으로 던지지 않습니다.

Anisometry 는 1픽셀 두께 리전에서 0 입니다

픽셀을 면적 1의 사각형이 아니라 무한소 점으로 보기 때문에 Rb = 0 이 되고, Anisometry = Ra/Rb 가 정의될 수 없어 0 이 됩니다. HALCON 실제 동작을 재현한 것입니다.

if (m.anisometry > 5.0)  nType = SCRATCH;   // ✗ 가장 길쭉한 1px 스크래치를 놓칩니다
if (m.rb == 0.0 || m.anisometry > 5.0)  nType = SCRATCH;   //

같은 이유로 StructureFactor 는 이때 -1.0 입니다. 자세한 내용은 docs/05_USER_GUIDE.md §4.8.

RegionFeatures · RegionMoments 는 POD 이고, 레이아웃이 곧 ABI 입니다

필드를 추가하면 sizeof 가 바뀌어, 옛 헤더로 컴파일된 호출부가 링크는 되면서 스택을 넘겨 씁니다.

  • 필드는 끝에만 추가합니다 (중간 삽입·순서 변경·타입 변경 금지)
  • 필드를 추가한 버전으로 올릴 때는 라이브러리와 검사기를 함께 재컴파일합니다

6. 내부가 어떻게 돌아가는가

┌──────────────────────────────────────────────────────────┐
│  Facade    namespace glim::halcon                        │  ← 사용처가 보는 전부
│            AreaCenter() EllipticAxis() …                 │     HALCON 이름 그대로
├──────────────────────────────────────────────────────────┤
│  Feature   FeatureContext   중간산물 캐시(지연계산)        │  ← 성능의 핵심
├──────────────────────────────────────────────────────────┤
│  Core      Region (런렝스, 불변)                          │
│            Contour · ConvexHull · Moments                │
└──────────────────────────────────────────────────────────┘
        전 계층 표준 라이브러리만 사용 — 외부 의존성 없음
요소 어떻게 구현했나
Region 픽셀 배열이 아니라 런렝스(row, colBegin, colEnd) 목록입니다. 정렬·중복·인접을 정규화해 보관하므로, 면적과 모멘트를 O(면적)이 아니라 O(런 수) 로 계산합니다. PIMPL + 내부 공유라 복사가 O(1) 입니다
윤곽 추적 8-연결 런 라벨링(union-find) 후 Moore 경계 추적. 픽셀 소속 판정을 dense 라벨 이미지가 아니라 런 이진탐색으로 하기 때문에 메모리가 O(런 수) 입니다(풀프레임에서 1.3GB 짜리 라벨 이미지가 사라집니다)
ContLength 체인 스텝을 직교 1 · 대각 √2 로 누적합니다. 구멍 윤곽은 제외합니다(원문 명시)
ConvexHull monotone chain 으로 껍질을 구한 뒤 스캔라인 래스터화해 픽셀을 셉니다. 폴리곤 면적을 쓰면 정사각형에서 convexity = 1.0203 이 나와 HALCON 자신의 Assertion(≤1)을 위반합니다
2차 모멘트 런당 상수시간(등차수열 합 + Faulhaber 제곱합). 무게중심에 가장 가까운 격자점으로 좌표를 먼저 옮기고 소수부만 보정합니다 — 원시 모멘트를 그대로 빼면 큰 이미지에서 자리수가 통째로 날아갑니다
Ia / Ib 공분산 고유값을 원문 형태가 아니라 항등 변형 ((M20−M02)/2)² + M11² 으로 계산합니다. 원문 형태는 큰 두 수의 차라 대면적에서 유효자리가 전멸하고, 부동소수 오차로 음수가 되어 sqrt 가 NaN 을 냅니다. 값은 대수적으로 같습니다
FeatureContext 윤곽·hull·모멘트·최원점을 리전당 1회만 계산하고 캐시합니다. 배치 API 가 빠른 이유가 이것입니다
OpenCV 미사용 편의상 뺀 게 아니라 값이 달라서 뺐습니다. cv::arcLength 는 근사 폴리곤, cv::contourArea 는 폴리곤 면적, cv::minAreaRect 는 HALCON 대비 2배(반변 규약)입니다

설계 제약과 근거는 docs/01_ARCHITECTURE.md, 구현 세부는 docs/04_IMPLEMENTATION_NOTES.md.


7. 빌드

cmake -S . -B build -G "Visual Studio 18 2026" -A x64
cmake --build build --config Release

cd build && ctest -C Release --output-on-failure
옵션 기본 설명
-A x64 / -A Win32 검사기 플랫폼에 맞춥니다
-DGLIM_HALCON_BUILD_SHARED=ON OFF DLL 로 빌드 (기본은 static lib)
-DGLIM_HALCON_BUILD_EXAMPLES=OFF ON 예제 실행파일 제외

생성기는 설치된 Visual Studio 에 맞춰 바꿉니다 (Visual Studio 17 2022 등). 실행 가능한 예제 5개가 examples/ 에 있습니다.


8. 문서

📚 문서 허브 — docs/README.md 가 진입점입니다.

읽는 순서 · 카탈로그 · 오퍼레이터별 정의/검증/원문 역인덱스가 그곳에 있습니다.

문서 내용
docs/05_USER_GUIDE.md 사용처(검사기) 관점의 API 매뉴얼
CLAUDE.md 개발 참여 전 필수. AI 에이전트 / 신규 참여자 지침
docs/00_OVERVIEW.md 범위 · 원칙 · 함정 14건 · 40종 현황표
docs/01_ARCHITECTURE.md 설계 제약과 적용 패턴
docs/02_DEFINITIONS.md 특징값 정의서 — 구현의 유일한 근거
docs/03_VERIFICATION.md 기준 도형 · 기댓값 손계산 · 검증 스크립트
docs/04_IMPLEMENTATION_NOTES.md 구현 노트 · 실측 결과
docs/reference/halcon13/ HALCON 13 원문 아카이브
CONTRIBUTING.md 빌드·테스트·기여 절차

HALCON 은 MVTec Software GmbH 의 상표입니다. 이 프로젝트는 MVTec 과 무관하며,
공개된 오퍼레이터 명세를 근거로 한 독립 구현입니다. HALCON 코드를 포함하지 않습니다.

About

HALCON 13 Regions/Features 오퍼레이터 40종을 SDK 없이 C++ 로 재현하는 라이브러리. 정확도 우선 — 정의→검증→구현.

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages