| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- file access
- SERIAL
- parameter
- aduino
- Gradient
- edge
- Binary
- memory
- compare
- Contour
- atmega328
- mfc
- wpf
- APP
- Unity
- subpixel
- c++
- UNO
- public
- flutter
- digitalRead
- Gaussian
- Class
- Read
- stream
- Filtering
- sensor
- Android
- Encapusulation
- Pointer
- Today
- Total
폴크(FOLC)
알고리즘 - Edge 추출 방법 ( Frangi-Vesselness ) 본문
이미지(또는 볼륨)에서 선형/관 구조(line-/tube-like structure)를 강조하는 필터로, 핵심은 각 픽셀에서 Hessian 행렬(2차 미분)의 고유값을 보고 한 축으로는 휘도 변화가 강하고(굵직함) 직교 축으로는 변화가 약한(가늘게 길게) 패턴을 높은 점수를 준다.
목적: 이미지에서 길쭉한 선형 패턴 검출
적용 분야: 혈관 영상 분석, 스크래치/크랙 검출, 섬유 패턴 분석 등
핵심 아이디어:
- 픽셀 주변의 Hessian 행렬(2차 미분) 분석
- 고유값 패턴으로 선형성 판단
- 다양한 **스케일(σ)**에서 선 구조 탐색
장점
- 선형 구조에 특화, 노이즈 강인
- 다양한 굵기 대응 가능 (멀티스케일)
한계
- 교차/분기부 반응 약함
- 극성 혼합 시 두 번 계산 필요
- σ 범위와 파라미터에 따라 성능 차이 큼
예시
- 혈관 네트워크 추출
- 금속 표면 스크래치 검출
- 섬유/패턴 분석
소스 코드
// C++14 / OpenCV 4.x
#include <opencv2/opencv.hpp>
#include <algorithm>
#include <vector>
#include <cmath>
cv::Mat FrangiVesselnessMultiScaleOCV(const cv::Mat& src8or32f,
const std::vector<double>& sigmas,
bool bright_on_dark = false, // 밝은 선 찾기(true) / 어두운 선(false, 기본)
double beta = 0.5,
double c = 15.0)
{
CV_Assert(!sigmas.empty());
CV_Assert(src8or32f.channels() == 1);
cv::Mat f;
if (src8or32f.type() == CV_32F) {
f = src8or32f.clone();
} else {
src8or32f.convertTo(f, CV_32F, 1.0 / 255.0);
}
cv::Mat V = cv::Mat::zeros(f.size(), CV_32F);
const double beta2 = 2.0 * beta * beta;
const double c2 = 2.0 * c * c;
for (double sigma : sigmas) {
if (sigma <= 0) continue;
// 1) 가우시안으로 스무딩(스케일-스페이스)
cv::Mat g;
cv::GaussianBlur(f, g, cv::Size(), sigma, sigma, cv::BORDER_REFLECT101);
// 2) Hessian 근사: 2차/혼합 미분 (스무딩된 영상에 Sobel 적용)
cv::Mat Ixx, Iyy, Ixy;
// d2/dx2, d2/dy2, d2/dxdy
cv::Sobel(g, Ixx, CV_32F, 2, 0, 3, 1, 0, cv::BORDER_REFLECT101);
cv::Sobel(g, Iyy, CV_32F, 0, 2, 3, 1, 0, cv::BORDER_REFLECT101);
cv::Sobel(g, Ixy, CV_32F, 1, 1, 3, 1, 0, cv::BORDER_REFLECT101);
// 3) 스케일 정규화 (sigma^2)
float s2 = static_cast<float>(sigma * sigma);
Ixx *= s2; Iyy *= s2; Ixy *= s2;
// 4) Frangi vesselness (단일 스케일) + 5) 멀티스케일 max-projection
const int rows = f.rows, cols = f.cols;
for (int y = 0; y < rows; ++y) {
const float* ax = Ixx.ptr<float>(y);
const float* bx = Ixy.ptr<float>(y);
const float* dx = Iyy.ptr<float>(y);
float* vx = V .ptr<float>(y);
for (int x = 0; x < cols; ++x) {
double a = ax[x], b = bx[x], d = dx[x];
// eigenvalues of [[a,b],[b,d]] with |λ1| ≤ |λ2|
double tr = a + d;
double det = a * d - b * b;
double disc2 = 0.25 * tr * tr - det;
double disc = std::sqrt(disc2 > 0.0 ? disc2 : 0.0);
double l1 = 0.5 * tr - disc;
double l2 = 0.5 * tr + disc;
// polarity (밝은 선/어두운 선)
if (bright_on_dark) { if (l2 < 0.0) continue; }
else { if (l2 > 0.0) continue; }
double Rb = (l2 == 0.0) ? 0.0 : std::abs(l1) / std::abs(l2); // blob 대비 선형성
double S2 = l1*l1 + l2*l2; // ‖H‖_F^2
double v = std::exp(-(Rb*Rb)/beta2) * (1.0 - std::exp(-S2/c2));
if (v > vx[x]) vx[x] = static_cast<float>(v); // max over scales
}
}
}
// 6) (선택) 0~1 클램프
cv::threshold(V, V, 1.0, 1.0, cv::THRESH_TRUNC);
cv::threshold(V, V, 0.0, 0.0, cv::THRESH_TOZERO);
return V;
}
#include <opencv2/opencv.hpp>
int main() {
cv::Mat gray = cv::imread("input.png", cv::IMREAD_GRAYSCALE);
std::vector<double> sigmas = {1.0, 2.0, 3.0, 4.0};
// 어두운 선(스크래치) 강조: bright_on_dark=false
cv::Mat vessel = FrangiVesselnessMultiScaleOCV(gray, sigmas, /*bright_on_dark=*/false, 0.5, 15.0);
// 보기 좋게 0~255로 변환해 저장
cv::Mat vis; vessel.convertTo(vis, CV_8U, 255.0);
cv::imwrite("vessel.png", vis);
return 0;
}