14.1 코너 검출
14.1.1 해리스 코너 검출 방법
특징(feature): 영상으로부터 추출할 수 있는 유용한 정보를 의미. 평균 밝기, 히스토그램, 에지, 직선 성분, 코너 등이 특징이 될 수 있음
지역 특징(local feature): 코너처럼 영상 전체가 아닌 일부 영역에서 추출할 수 있는 특징
특징점 (feature point): 코너처럼 한 점의 형태로 표현할 수 있는 특징
void cornerHarris(InputArray src, OutputArray dst, int blockSize,
int ksize, double k, int borderType = BORDER_DEFAULT);
- src: 입력 영상. CV_8UC1 또는 CV_32FC1
- dst: 해리스 코너 응답 함수 값을 저장할 행렬. src와 크기가 같고 CV_32FC1 타입임
- blockSize: 행렬 M 연산에 사용할 이웃 픽셀 크기. 픽셀 주변 blockSize x blockSize 윈도우를 설정하여 행렬 M을 계산
- ksize: 소벨 연산자를 위한 커널 크기
- k: 해리스 코너 검출 상수
- borderType: 가장자리 픽셀 확장 방식
void corner_harris()
{
Mat src = imread("building.jpg", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
Mat harris;
cornerHarris(src, harris, 3, 3, 0.04);
Mat harris_norm;
normalize(harris, harris_norm, 0, 255, NORM_MINMAX, CV_8U);
Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);
for (int j = 1; j < harris.rows - 1; j++) {
for (int i = 1; i < harris.cols - 1; i++) {
if (harris_norm.at<uchar>(j, i) > 120) {
if (harris.at<float>(j, i) > harris.at<float>(j - 1, i) &&
harris.at<float>(j, i) > harris.at<float>(j + 1, i) &&
harris.at<float>(j, i) > harris.at<float>(j, i - 1) &&
harris.at<float>(j, i) > harris.at<float>(j, i + 1) ) {
circle(dst, Point(i, j), 5, Scalar(0, 0, 255), 2);
}
}
}
}
imshow("src", src);
imshow("harris_norm", harris_norm);
imshow("dst", dst);
waitKey(0);
destroyAllWindows();
}
- 3행:building.jpg 영상을 그레이스케일 형식으로 불러와 src에 저장
- 10~11행: src 영상으로부터 해리스 코너 응답 함수 행렬 harris를 구함
- 13~14행: harris 행렬 원소 값 범위를 0 부터 255로 정규화하고, 타입을 CV_8UC1으로 변환하여 harris_norm에 저장. harris_norm은 그레이스케일 영상 형식을 따르며, 해리스 코너 응답 함수 분포를 영상 형태로 화면에 표시하기 위해 만듦
- 16행~17행: src 영상을 3채널 컬러 영상으로 변환하여 dst에 저장
- 21행: harris_norm 영상에서 값이 120보다 큰 픽셀을 코너로 간주
- 22~27행: 간단한 비최대 억제를 수행. (i, j) 위치에서 주변 네 개의 픽셀을 비교하여 지역 최대한 경우에만 dst 영상에 빨간색 원으로 코너를 표시
14.1.2 FAST 코너 검출 방법
FAST: 영상의 모든 픽셀에서 픽셀을 둘러싸고 있는 16개의 주변 픽셀과 밝기를 비교하여 코너 여부를 판별
void FAST(InputArray image, std::vector<KeyPoint>& keypoints, int threshold,
bool nomaxSuppression = true);
- image: 입력 그레이스케일 영상
- keypoints: 검출된 특징점을 표현하는 KeyPoint 객체의 벡터. KeyPoint::pt 멤버 변수에 코너 점 좌표가 저장
- threshold: 중심 픽셀 값과 주변 픽셀 값과의 차이 임계값
- nomaxSuppression: 비최대 억제 수행 여부. true이면 비최대 억제를 수행
void corner_fast()
{
Mat src = imread("building.jpg", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
vector<KeyPoint> keypoints;
FAST(src, keypoints, 60, true);
Mat dst;
cvtColor(src, dst, COLOR_GRAY2BGR);
for (KeyPoint kp : keypoints) {
Point pt(cvRound(kp.pt.x), cvRound(kp.pt.y));
circle(dst, pt, 5, Scalar(0, 0, 255), 2);
}
imshow("src", src);
imshow("dst", dst);
waitKey(0);
destroyAllWindows();
}
- 3행: building.jpg 영상을 그레이스케일 형식으로 불러와 src에 저장
- 10~11행: src 영상에서 FAST 방법으로 코너 점을 검출. 밝기 차이 임계값으로 60을 지정. 비최대 억제를 수행하도록 설정. 검출된 모든 코너 점 좌표는 keypoints 변수에 저장
- 13~14행: src 영상으 3채널 컬러 영상으로 변환하여 dst에 저장
- 16~19행: 검출된 모든 코너 점에 반지름이 5인 빨간색 원을 그림
14.2 크기 불변 특징점 검출과 기술
14.2.1 크기 불변 특징점 알고리즘
SIFT: 크기가 다른 영상에서도 지속적으로 검출될 수 있는 크기 불변 특징. 크기 불변 특징 변환의 약자.
14.2.2 OpenCV 특징점 검출과 기술
class KeyPoint
{
public:
KeyPoint();
KeyPoint(Point2f _pt, float _size, float _angle = -1, float _response = 0, int _octave = 0,
int _class_id = -1);
Point2f pt;
float size;
float angle;
float response;
int octave;
int class_id;
};
- 4~6행: KeyPoint 클래스 생성자 및 멤버 함수
- 9행: KeyPoint::pt 멤버 변수는 특징점 좌표를 나타냄
- 10행: KeyPoint::size 멤버 변수는 특징점 크기(지름)을 나타냄
- 11행: KeyPoint::angle 멤버 변수는 특징점의 주된 방향(각도)를 나타냄
- 12행: KeyPoint::response 멤버 변수는 특징점 반응성을 나타내며 좋은 특징점을 선별하는 용도로 사용
- 13행: KeyPoint::octave 멤버 변수는 특징점이 추출된 옥타브(피라미드 단계)를 나타냄
- 14행: KeyPoint::class_id 멤버 변수는 특징점이 포함된 객체 번호를 나타냄
static Ptr<ORB> ORB::create(int nfeatures = 500, float scaleFactor = 1.2f, int nlevels = 8,
int edgeThreshold = 31, int firstLevel = 0, int WTA_K = 2,
ORB::ScoreType scoreType = ORB::HARRIS_SCORE,
int patchSize = 31, int fastThreshold = 20);
- nfeatures: 검출할 최대 특징 개수
- scaleFactor: 피라미드 생성 비율(영상 축소 비율)
- nlevels: 피라미드 단계 개수
- edgeThreshold: 특징을 검출하지 않을 영상 가장자리 픽셀 크기
- firstLevel: 항상 0을 지정
- WTA_K: BRIEF 기술자 계산 시 사용할 점의 개수. 2, 3, 4 중 하나를 지정
- scoreType: 특징점 점수 결정 방법. ORB::HARRIS_SCORE 또는 ORB::FAST_SCORE 둘 중 하나를 지정
- patchSize: BRIEF 기술자 계산 시 사용할 패치 크기
- fastThreshold: FAST 코너 검출 방법에서 사용되는 임계값
- 반환값: ORB 객체를 참조하는 Ptr 스마트 포인터 객체
virtual void Feature2D::detect(InputArray image,
std::vector<KeyPoint>& keypoints, InputArray mask = noArray());
- image: 입력 영상
- keypoints: 검출된 키포인트 정보
- mask: 마스크 행렬. 마스크 행렬 원소가 0이 아닌 위치에서만 특징점을 검출
virtual void Feature2D::compute(InputArray image,
std::vector<KeyPoint>& keypoints, OutputArray descriptors);
- image: 입력 영상
- keypoints: 미리 검출해 둔 키포인트 정보
- descriptors: 계산된 기술자 행렬. i 번째 행은 i 번째 키포인트의 기술자를 나타냄
virtual void Feature2D::detectAndCompute(InputArray image, InputArray mask,
std::vector<KeyPoint>& keypoints, OutputArray desriptors, bool useProvidedKeyPoints = false);
- image: 입력 영상
- mask: 마스크 행렬. 마스크 행렬 원소가 0이 아닌 위치에서만 특징점을 검출
- keypoints: 검출된 키포인트 정보
- descriptors: 계산된 기술자 행렬
- useProvidedKeypoints: 이 값이 true이면 keypoints 인자로 전달된 키포인트 정보를 이용하여 기술자를 계산
void drawKeypoints(InputArray image, const std::vector<KeyPoint>& keypoints,
InputOutputArray outImage, const Scalar& color = Scalar::all(-1),
DrawMatchesFlags flags = DrawMatchesFlags::DEFAULT);
- image: 입력 영상
- keypoints: 입력 영상에서 검출된 키포인트
- outImage: 키포인트가 그려진 출력 영상
- color: 키포인트 색상. 이 값이 Scalar::all(-1)이면 각 특징점을 임의의 색상으로 그림
- flags: 키포인트 그리기 방법. DrawMatchesFlags 열거형 상수 중 하나를 지정
void detect_keypoints()
{
Mat src = imread("box_in_scene.png", IMREAD_GRAYSCALE);
if (src.empty()) {
cerr << "Image load failed!" << endl;
return;
}
Ptr<Feature2D> feature = ORB::create();
vector<KeyPoint> keypoints;
feature->detect(src, keypoints);
Mat desc;
feature->compute(src, keypoints, desc);
cout << "keypoints.size(): " << keypoints.size() << endl;
cout << "desc.size(): " << desc.size() << endl;
Mat dst;
drawKeypoints(src, keypoints, dst, Scalar::all(-1), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
imshow("src", src);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
- 10행: ORB 클래스 객체를 생성하여 feature 스마트 포인터에 저장
- 12~13행: ORB 키포인트를 검출하여 keypoints 벡터에 저장
- 15~16행: ORB 키포인트 기술자를 계산하여 desc 행렬에 저장
- 18~19행: keypoints 에 저장된 키포인트 개수와 desc 행렬 크기를 콘솔 창에 출력
- 21~22행: 입력 영상 src에 키포인트를 그린 결과를 dst에 저장. 키포인트 그리는 방식을 DrawMatchesFlags::DRAW_RICH_KEYPOINTS로 지정하여 키포인트 위치, 크기, 방향 정보를 함께 나타내도록 설정
14.3 특징점 매칭
14.3.1 OpenCV 특징점 매칭
class DMatch
{
public:
DMatch()
DMatch(int _queryIdx, int _trainIdx, floats _distance);
DMatch(int _queryIdx, int _trainIdx, int _imgIdx, float _distance);
int queryIdx;
int trainIdx;
int imgIdx;
float distance;
bool operator<(const DMatch &m) const;
};
- 4~6행: DMatch 클래스 생성자
- 8행: DMatch::queryIdx 멤버 변수는 질의 기술자 번호를 나타냄
- 9행: DMatch::trainIdx 멤버 변수는 훈련 기술자 번호를 나타냄
- 10행: DMatch::imgIdx 멤버 변수는 훈련 영상 번호를 나타냄. 여러 장의 영상을 훈련 영상으로 설정한 경우에 사용
- 12행: DMatch::distance 멤버 변수는 두 기술자 사이의 거리를 나타냄
- 14행: DMatch 클래스에 대한 크기 비교 연산자 재정의이며, DMatch::distance 멤버 변수 값을 이용하여 크기를 비교
static Ptr<BFMatcher> BFMatcher::create(int normType = NORM_L2, bool crossCheck = fasle);
- normType: 기술자 거리 측정 방식. NORM_L1, NORM_L2, NORM_HAMMING, NORM_HAMMING2 중 하나를 지정
- crossCheck: 이 값이 true 이면 i 번째 질의 기술자와 가장 유사한 훈련 기술자가 j이고, j번째 훈련 기술자와 가장 유사한 질의 기술자가 i인 경우에만 매칭 결과로 반환
- 반환값: BFMatcher 객체를 참조하는 Ptr 스마트 포인터 객체
static Ptr<FlannBasedMatcher> FlannBasedMatcher::create();
- 반환값: FlannBasedMatcher 객체를 참조하는 Ptr 스마트 포인터 객체
void DecriptorMatcher::match(InputArray queryDecsriptors, InputArray trainDescriptors,
std::vector<DMatch>& matches, InputArray mask = noArray()) const;
- queryDescriptors: 질의 기술자 집합
- trainDescriptors: 훈련 기술자 집합
- matches: 매칭 결과
- mask: 서로 매칭 가능한 질의 기술자와 훈련 기술자를 지정할 때 사용. 행 개수는 질의 기술자 개수와 같아야 하고, 열 개수는 훈련 기술자 개수와 같아야 한다.
void drawMatches(InputArray img1, const std::vector<KeyPoint>& keyPoints1, InputArray img2,
const std::vector<KeyPoint>& keypoint2, const std::vector<DMatch>& matches1to2,
InputOutputArray outImg, const Scalar& matchColor = Scalar::all(-1),
const Scalar& singlePointColor = Scalar::all(-1),
const std::vector<char>& matchesMask = std::vector<char>(),
DrawMatchesFlags flags = DrawMatchesFlags::DEFAULT);
- img1: 첫번째 입력 영상
- keypoints1: 첫 번째 입력 영상에서 검출된 특징점
- img2: 두 번째 입력 영상
- keypoints2: 두 번째 입력 영상에서 검출된 특징점
- matches1to2: 첫 번재 입력 영상에서 두 번째 입력 영상으로의 매칭 정보
- outImg: 출력 영상
- matchColor: 매칭된 특징점과 직선 색상. 만약 Scalar::all(-1) 을 지정하면 임의의 색상으로 그림
- singlePointColor: 매칭되지 않은 특징점 색상. 만약 Scalar::all(-1)을 지정하면 임의의 색상으로 그림
- matchesMask: 매칭 정보를 선택하여 그릴 때 사용할 마스크. 만약 std::vector<char>()를 지정하면 모든 매칭 결과를 그림
- flags: 매칭 정보 그리기 방법. DrawMatchesFlags 열거형 상수를 지정
void keypoint_matching()
{
Mat src1 = imread("box.png", IMREAD_GRAYSCALE);
Mat src2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);
if (src1.empty() || src2.empty()) {
cerr << "Image load failed!" << endl;
return;
}
Ptr<Feature2D> feature = ORB::create();
vector<KeyPoint> keypoints1, keypoints2;
Mat desc1, desc2;
feature->detectAndCompute(src1, Mat(), keypoints1, desc1);
feature->detectAndCompute(src2, Mat(), keypoints2, desc2);
cout << "desc1.size(): " << desc1.size() << endl;
cout << "desc2.size(): " << desc2.size() << endl;
Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING);
vector<DMatch> matches;
matcher->match(desc1, desc2, matches);
Mat dst;
drawMatches(src1, keypoints1, src2, keypoints2, matches, dst);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
- 3~4행: 두 장의 영상을 각각 src1과 src2에 저장
- 11행: ORB 클래스 객체를 생성
- 13~16행: src1과 src2 영상에서 각각 특징점을 검출하고 시술자를 계산. 각 영상의 기술자는 desc1과 desc2 행렬에 저장
- 18행: BRMatcher 클래스 객체를 생성. 기술자 거리 계산 방식은 해밍 거리를 사용
- 20~21행: desc1과 desc2 기술자를 서로 매칭하여 그 결과를 matches에 저장
- 23~24행: matches 정보를 이용하여 매칭 결과 영상 dst를 생성
void good_matching()
{
Mat src1 = imread("box.png", IMREAD_GRAYSCALE);
Mat src2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);
if (src1.empty() || src2.empty()) {
cerr << "Image load failed!" << endl;
return;
}
Ptr<Feature2D> feature = ORB::create();
vector<KeyPoint> keypoints1, keypoints2;
Mat desc1, desc2;
feature->detectAndCompute(src1, Mat(), keypoints1, desc1);
feature->detectAndCompute(src2, Mat(), keypoints2, desc2);
Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING);
vector<DMatch> matches;
matcher->match(desc1, desc2, matches);
std::sort(matches.begin(), matches.end());
vector<DMatch> good_matches(matches.begin(), matches.begin() + 50);
Mat dst;
drawMatches(src1, keypoints1, src2, keypoints2, good_matches, dst,
Scalar::all(-1), Scalar::all(-1), vector<char>(),
DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
- 3~21행: 위의 코드 keypoint_matching() 함수와 같다
- 23행: 두 영상의 특징점 매칭 결과를 정렬
- 24행: 정렬된 매칭 결과에서 상위 50개 매칭 결과를 good_matches에 저장
- 26~29행: good_matches를 이용하여 매칭 결과 영상을 생성. drawMatches() 함수의 10번째 인자에 DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS 상수를 지정하여 매칭되지 않은 특징점은 화면에 표시하지않음
14.3.2 호모그래피와 영상 매칭
호모그래피: 수학적으로 하나의 평면을 다른 평면으로 투시 변환하는 것과 같은 관계에 있음
Mat findHomography(InputArray srcPoints, InputArray dstPoints, int method = 0,
doubleransacReporjThreshold = 3, OutputArray mask = noArray(), const int maxIters = 2000,
const double confidence = 0.995);
- srcPoints: 원본 평면상의 점 좌표. CV_32FC2 타입의 Mat 객체 또는 vector<Point2f> 타입의 변수를 지정
- dstPoints: 목표 평면상의 점 좌표. CV_32FC2 타입의 Mat 객체 또는 vector<Point2f> 타입의 변수를 지정
- method: 호모 그래피 형렬 계산 방법. 다음 방법 중 하나를 지정
- 0 - 모든 점을 사용하는 일반적인 방법. 최소자승법
- LMEDS - 최소 메디안 제곱 방법
- RANSAC - RANSAC 방법
- RHQ - PROSAC 방법
- ransacReprojThreshold: 최대 허용 재투영 에러. 이 값 이내로 특징점이 재투영되는 경우에만 정상치로 간주. RANSAC과 RHO 방법에서만 사용
- mask: 호모그래피 계산에 사용된 점들을 알려주는 출력 마스크 행렬. LMEDS와 RANSAC 방법에서만 사용
- maxIters: RANSAC 최대 반복 횟수
- confidence: 신뢰도 레벨. 0에서 1 사이의 실수를 지정
- 반환값: CV_64FC1 타입의 3x3 호모그래피 행렬을 반환. 만약 호모그래피를 계산할 수 없는 상황이라면 비어 있는 Mat 객체가 반환
void find_homography()
{
Mat src1 = imread("box.png", IMREAD_GRAYSCALE);
Mat src2 = imread("box_in_scene.png", IMREAD_GRAYSCALE);
if (src1.empty() || src2.empty()) {
cerr << "Image load failed!" << endl;
return;
}
Ptr<Feature2D> feature = ORB::create();
vector<KeyPoint> keypoints1, keypoints2;
Mat desc1, desc2;
feature->detectAndCompute(src1, Mat(), keypoints1, desc1);
feature->detectAndCompute(src2, Mat(), keypoints2, desc2);
Ptr<DescriptorMatcher> matcher = BFMatcher::create(NORM_HAMMING);
vector<DMatch> matches;
matcher->match(desc1, desc2, matches);
std::sort(matches.begin(), matches.end());
vector<DMatch> good_matches(matches.begin(), matches.begin() + 50);
Mat dst;
drawMatches(src1, keypoints1, src2, keypoints2, good_matches, dst,
Scalar::all(-1), Scalar::all(-1), vector<char>(),
DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
vector<Point2f> pts1, pts2;
for (size_t i = 0; i < good_matches.size(); i++) {
pts1.push_back(keypoints1[good_matches[i].queryIdx].pt);
pts2.push_back(keypoints2[good_matches[i].trainIdx].pt);
}
Mat H = findHomography(pts1, pts2, RANSAC);
vector<Point2f> corners1, corners2;
corners1.push_back(Point2f(0, 0));
corners1.push_back(Point2f(src1.cols - 1.f, 0));
corners1.push_back(Point2f(src1.cols - 1.f, src1.rows - 1.f));
corners1.push_back(Point2f(0, src1.rows - 1.f));
perspectiveTransform(corners1, corners2, H);
vector<Point> corners_dst;
for (Point2f pt : corners2) {
corners_dst.push_back(Point(cvRound(pt.x + src1.cols), cvRound(pt.y)));
}
polylines(dst, corners_dst, true, Scalar(0, 255, 0), 2, LINE_AA);
imshow("dst", dst);
waitKey();
destroyAllWindows();
}
- 31~35행: good_matches 매칭 결과에 저장된 질의 영상과 훈련 영상의 특징점 좌표를 추출하여 vector<Point2f> 타입의 변수 pts1, pts2에 저장
- 37행: pts1 점들이 pts2 점들로 이동하는 호모그래피 행렬을 구하여 H에 저장. 호모그래피 계산 방법은 RANSAC 알고리즘 사용
- 39~44행: src1 영상의 네 모서리 점을 corners1에 저장한 후, 호모그래피 행렬 H를 이용하여 이 점들이 이동하는 위치를 계산하여 corners2에 저장
- 46~49행: 매칭 결과 영상 dst에서 corner2 점들이 위치하는 좌표를 corners_dst에 저장
- 51행: 매칭 결과 영상 dst에서 box,png 스낵 박스가 있는 위치에 녹색으로 사각형을 그림
14.4 영상 이어 붙이기
static Ptr<Stitcher> Stitcher::create(Mode mode = Stitcher::PANORAMA);
- mode: 이어 붙이기 방식. Stitcher::PANORAMA 또는 Stitcher::SCANS를 지정
- 반환값: Stitcher 객체를 참조하는 Ptr 스마트 포인터 객체
Stitcher::Status Stitcher::stitch(InputArrayOfArrays images, OutputArray pano);
- image: 입력 영상의 벡터. vector<Mat> 타입을 사용
- pano: 출력 파노라마 영상
- 반환값: 함수 동작 결과 코드. 이 값이 Stitcher::Status::OK 이면 정상 동작을 의미
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main(int argc, char* argv[])
{
if (argc < 3) {
cerr << "Usage: stitching.exe <image_file1> <image_file2> [<image_file3> ...]" << endl;
return -1;
}
vector<Mat> imgs;
for (int i = 1; i < argc; i++) {
Mat img = imread(argv[i]);
if (img.empty()) {
cerr << "Image load failed!" << endl;
return -1;
}
imgs.push_back(img);
}
Ptr<Stitcher> stitcher = Stitcher::create();
Mat dst;
Stitcher::Status status = stitcher->stitch(imgs, dst);
if (status != Stitcher::Status::OK) {
cerr << "Error on stitching!" << endl;
return -1;
}
imwrite("result.jpg", dst);
imshow("dst", dst);
waitKey();
return 0;
}
- 9~12행: 명령행 인자 개수가 3보다 작으면 프로그램 사용법을 콘솔 창에 출력하고 프로그램을 종료
- 14~24행: 명령행 인자로 전달된 영상 파일을 각각 불러와서 vector<Mat> 타입의 변수 imgs 에 추가. 만약 영상 파일을 불러오지 못하면 에러 메시지를 출력하고 프로그램을 종료
- 26행: Stitcher 객체를 생성
- 28~29행: imgs에 저장된 입력 영상을 이어 붙여서 결과 영상 dst를 생성
- 31~34행: 영상 이어붙이기가 실패하면 에러 메시지를 출력하고 프로그램을 종료
- 36행: 결과 영상을 result.jpg 파일로 저장
- 38행: 결과영상을 dst창에 나타냄
'3학년 > OpenCV' 카테고리의 다른 글
[OpenCV] 15장 머신 러닝 (0) | 2023.07.03 |
---|---|
[OpenCV] 13장 객체 검출 (0) | 2023.07.03 |
[OpenCV] 12장 레이블링과 외곽선 검출 (0) | 2023.07.02 |
[OpenCV] 11장 이진화와 모폴로지 (0) | 2023.07.02 |
[OpenCV] 10장 컬러 영상 처리 (0) | 2023.07.02 |