cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC1);
for (int y = 0; y < src.rows; y++)
{
for(int x = 0; x < src.cols; x++)
{
int x_ = x - 200;
int y_ = y + 100;
if(x_ < 0 || x_ >= dst.cols) continue;
if(y_ < 0 || y_ >= dst.rows) continue;
dst.at<uchar>(y_, x_) = src.at<uchar>(y, x);
}
}
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst(src.rows, src.cols*3/2, src.type(), cv::Scalar(0));
double m(0.5);
for (int y = 0; y < src.rows; y++)
{
for(int x = 0; x < src.cols; x++)
{
int nx = int(x + m*y);
int ny = y;
dst.at<uchar>(ny, nx) = src.at<uchar>(y, x);
}
}
//cv::Mat aff = (cv::Mat_<float>(2, 3) << 1, 0.5, 0, 0, 1, 0); // 같은 결과
//cv::warpAffine(src, dst, aff, cv::Size(src.cols*3/2, src.rows));
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
backward mapping
cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst= cv::Mat::zeros(src.rows * 2, src.cols * 2, src.type());
// backward mapping
for(int y_ = 0; y_ < dst.rows; y_++)
{
for(int x_ = 0; x_ < dst.cols; x_++)
{
int x = x_ / 2;
int y = y_ / 2;
dst.at<uchar>(y_, x_) = src.at<uchar>(y, x);
}
}
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
양선형 보간법 구현
cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst;
dst.create(src.rows*3/2, src.cols*3/2, CV_8U);
int x1, y1, x2, y2;
double rx, ry, p, q, value;
// scaling factor
double sx = static_cast<double>(src.cols - 1) / (dst.cols - 1);
double sy = static_cast<double>(src.rows - 1) / (dst.rows - 1);
for (int y = 0; y < dst.rows; y++)
{
for(int x = 0; x < dst.cols; x++)
{
rx = sx * x;
ry = sy * y;
x1 = cvFloor(rx);
y1 = cvFloor(ry);
x2 = x1 + 1; if(x2 == src.cols) x2 = src.cols - 1;
y2 = y1 + 1; if(y2 == src.rows) y2 = src.rows - 1;
p = rx - x1;
q = ry - y1;
value = (1. - p) * (1. - q) * src.at<uchar>(y1, x1)
+ p * (1. - q) * src.at<uchar>(y1, x2)
+ (1. - p) * q * src.at<uchar>(y2, x1)
+ p * q * src.at<uchar>(y2, x2);
dst.at<uchar>(y, x) = static_cast<uchar>(value + .5);
}
}
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::resize()
cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst;
cv::resize(src, dst, cv::Size(), 1.5, 1.5, cv::INTER_NEAREST);
// INTER_LINEAR, INTER_CUBIC, INTER_LANCZOS4, INTER_AREA(축소 시 사용)
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
float degree = 45.; // 45도 회전
cv::Point2f pt(src.cols / 2.f, src.rows / 2.f); // center 기준으로 회전.
cv::Mat rot = cv::getRotationMatrix2D(pt, degree, 1.0);
cv::Mat dst;
cv::warpAffine(src, dst, rot, cv::Size());
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst;
cv::flip(src, dst, 1); // 0 = 상하대칭, -1 = 좌우&상하대칭
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
std::vector<cv::Point2f> src_pts = {cv::Point2f(0, 0),
cv::Point2f(0, src.rows-1), cv::Point2f(src.cols-1, src.rows-1)};
std::vector<cv::Point2f> dst_pts = {cv::Point2f(0, 0),
cv::Point2f(0, src.rows-1), cv::Point2f(src.cols/2, src.rows/2)};
cv::Mat mat = cv::getAffineTransform(src_pts, dst_pts);
cv::Mat dst;
cv::warpAffine(src, dst, mat, cv::Size());
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat src = cv::imread("lane01.bmp");
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
std::vector<cv::Point2f> src_pts(4), dst_pts(4);
int w = 500, h = 260;
src_pts[0] = cv::Point2f(470, 400);
src_pts[1] = cv::Point2f(710, 400);
src_pts[2] = cv::Point2f(860, 530);
src_pts[3] = cv::Point2f(370, 530);
dst_pts[0] = cv::Point2f(0, 0);
dst_pts[1] = cv::Point2f(w-1, 0);
dst_pts[2] = cv::Point2f(w-1, h-1);
dst_pts[3] = cv::Point2f(0, h-1);
cv::Mat mat = cv::getPerspectiveTransform(src_pts, dst_pts);
cv::Mat dst;
cv::warpPerspective(src, dst, mat, cv::Size(w, h));
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat src = cv::imread("tekapo.bmp");
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat map_x = cv::Mat::zeros(src.rows, src.cols, CV_32FC1);
cv::Mat map_y = cv::Mat::zeros(src.rows, src.cols, CV_32FC1);
for (int y = 0; y < src.rows; y++)
{
for(int x = 0; x < src.cols; x++)
{
map_x.at<float>(y, x) = (float)x;
map_y.at<float>(y, x) = (float)src.rows -1 - y; // 상하대칭
}
}
cv::Mat dst;
cv::remap(src, dst, map_x, map_y, cv::INTER_LINEAR);
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
산술 연산
cv::Mat src = cv::imread("tekapo.bmp");
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst = cv::Scalar(255, 255, 255) - src;
//cv::Mat dst = ~src;
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
픽셀 값 접근
cv::Mat src = cv::imread("tekapo.bmp");
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst(src.rows, src.cols, CV_8UC3);
for(int y = 0; y < src.rows; y++)
{
for(int x = 0; x < src.cols; x++)
{
cv::Vec3b& p = src.at<cv::Vec3b>(y, x);
cv::Vec3b& q = dst.at<cv::Vec3b>(y, x);
// p[0], p[1] .. 으로 접근하거나
q = cv::Vec3b(255, 255, 255) - p;
}
}
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
구현
cv::Mat src = cv::imread("tekapo.bmp");
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst(src.rows, src.cols, CV_8UC1);
for(int y = 0; y < src.rows; y++)
{
for(int x = 0; x < src.cols; x++)
{
cv::Vec3b& p = src.at<cv::Vec3b>(y, x);
//dst.at<uchar>(y, x) = (uchar)((299*p[2] + 587*p[1] + 114*p[0]) / 1000);
dst.at<uchar>(y, x) = (4899*p[2]+9617*p[1]+1868*p[0]) >> 14;
}
}
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::cvtColor()
COLOR_BGR2GRAY, COLOR_BGR2HSV, COLOR_BGR2YCrCb 등..
cv::Mat src = cv::imread("tekapo.bmp");
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst;
cv::cvtColor(src, dst, cv::COLOR_BGR2GRAY);
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
YCrCb로 변환해 밝기 성분에 해당하는 Y만 평활화한 후 다시 BGR로 변환.
cv::Mat src = cv::imread("peppers.bmp");
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat src_ycrcb;
cv::cvtColor(src, src_ycrcb, cv::COLOR_BGR2YCrCb);
std::vector<cv::Mat> planes;
cv::split(src_ycrcb, planes);
cv::equalizeHist(planes[0], planes[0]);
cv::Mat dst_ycrcb;
cv::merge(planes, dst_ycrcb);
cv::Mat dst;
cv::cvtColor(dst_ycrcb, dst, cv::COLOR_YCrCb2BGR);
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
HSV: h(0 ~ 179), s(0 ~ 255), v(0 ~ 255)
트랙바를 이용해 Hue, Saturation threshold 조절.
int h_1(50), h_2(80), s_1(150), s_2(255);
cv::Mat src_, hsv_, mask_, dst_;
void callback(int, void*)
{
// mask 생성
cv::Scalar lowerb(h_1, s_1, 0);
cv::Scalar upperb(h_2, s_2, 255);
cv::inRange(hsv_, lowerb, upperb, mask_);
cv::cvtColor(src_, dst_, cv::COLOR_BGR2GRAY);
cv::cvtColor(dst_, dst_, cv::COLOR_GRAY2BGR);
src_.copyTo(dst_, mask_);
cv::imshow("mask", mask_);
cv::imshow("dst", dst_);
}
int main()
{
src_ = cv::imread("candies.png");
if (src_.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::cvtColor(src_, hsv_, cv::COLOR_BGR2HSV);
cv::namedWindow("src");
cv::namedWindow("mask");
cv::namedWindow("dst");
cv::imshow("src", src_);
cv::createTrackbar("Lower Hue", "dst", &h_1, 179, callback);
cv::createTrackbar("Upper Hue", "dst", &h_2, 179, callback);
cv::createTrackbar("Lower Sat", "dst", &s_1, 255, callback);
cv::createTrackbar("Upper Sat", "dst", &s_2, 255, callback);
while(cv::waitKey(0) != 'q')
continue;
return 0;
}
마우스로 선택한 영역과 유사한 색상 영역들을 검출.
cv::Mat src = cv::imread("cropland.png");
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Rect rect = cv::selectROI(src);
cv::Mat src_ycrcb;
cv::cvtColor(src, src_ycrcb, cv::COLOR_BGR2YCrCb);
cv::Mat crop = src_ycrcb(rect);
cv::Mat hist;
int channels[] = {1, 2}; // cr, cb channel
int cr_bins = 128;
int cb_bins = 128;
int histSize[] = {cr_bins, cb_bins};
float cr_range[] = {0, 256};
float cb_range[] = {0, 256};
const float* ranges[] = {cr_range, cb_range};
cv::calcHist(&crop, 1, channels, cv::Mat(), hist, 2, histSize, ranges);
// mask 생성
cv::Mat backproj;
cv::calcBackProject(&src_ycrcb, 1, channels, hist, backproj, ranges);
//cv::GaussianBlur(backproj, backproj, cv::Size(), 1.0);
//backproj = backproj > 50;
cv::Mat dst;
src.copyTo(dst, backproj);
//cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat src = cv::imread("lenna.bmp", cv::IMREAD_GRAYSCALE);
cv::Mat dx, dy;
cv::Sobel(src, dx, CV_32FC1, 1, 0); // x방향 gradient
cv::Sobel(src, dy, CV_32FC1, 0, 1); // y방향 gradient
cv::Mat mag; // magnitude
cv::magnitude(dx, dy, mag);
mag.convertTo(mag, CV_8UC1);
cv::Mat dst = mag > 120; // threshold
cv::imshow("src", src);
cv::imshow("mag", mag);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat src = cv::imread("circuit.bmp", cv::IMREAD_GRAYSCALE);
cv::Mat dst;
cv::Canny(src, dst, 50, 150);
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat src = cv::imread("building.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat edge;
cv::Canny(src, edge, 50, 150);
std::vector<cv::Vec2f> lines1;
cv::HoughLines(edge, lines1, 1, CV_PI / 180, 250);
// draw hough lines
cv::Mat dst1;
cv::cvtColor(edge, dst1, cv::COLOR_GRAY2BGR);
for(const cv::Vec2f& line : lines1)
{
float r = line[0], t = line[1];
double cos = std::cos(t), sin = std::sin(t);
double x = r * cos, y = r * sin;
double alpha = 1000;
cv::Point pt1(cvRound(x + alpha * (-sin)), cvRound(y + alpha * cos));
cv::Point pt2(cvRound(x - alpha * (-sin)), cvRound(y - alpha * cos));
cv::line(dst1, pt1, pt2, cv::Scalar(0, 0, 255), 1, cv::LINE_AA);
}
std::vector<cv::Vec4i> lines2;
cv::HoughLinesP(edge, lines2, 1, CV_PI / 180, 160, 50, 5);
cv::Mat dst2;
cv::cvtColor(edge, dst2, cv::COLOR_GRAY2BGR);
for(const cv::Vec4i& line : lines2)
{
cv::line(dst2, cv::Point(line[0], line[1]), cv::Point(line[2], line[3])
, cv::Scalar(0, 0, 255), 2, cv::LINE_AA);
}
cv::imshow("src", src);
cv::imshow("dst1", dst1);
cv::imshow("dst2", dst2);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat src = cv::imread("coins.jpg", cv::IMREAD_GRAYSCALE);
if(src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst;
cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);
std::vector<cv::Vec3f> circles;
cv::HoughCircles(src, circles, cv::HOUGH_GRADIENT_ALT, 1.5, 10, 300, 0.9, 20, 50);
for(const cv::Vec3f& circle : circles)
{
cv::Vec3i c = circle;
cv::circle(dst, cv::Point(c[0], c[1]), c[2], cv::Scalar(0, 0, 255), 2, cv::LINE_AA);
}
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
good features to track / FAST corner detection
cv::Mat src = cv::imread("building.jpg", cv::IMREAD_GRAYSCALE);
if(src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
// GFTT
std::vector<cv::Point2f> corners;
cv::goodFeaturesToTrack(src, corners, 400, 0.01, 10);
cv::Mat dst1;
cv::cvtColor(src, dst1, cv::COLOR_GRAY2BGR);
for(const auto& corner : corners)
{
cv::circle(dst1, corner, 5, cv::Scalar(0, 0, 255), 2, cv::LINE_AA);
}
// FAST
std::vector<cv::KeyPoint> kps;
cv::FAST(src, kps, 60);
cv::Mat dst2;
cv::cvtColor(src, dst2, cv::COLOR_GRAY2BGR);
for(const auto& kp : kps)
{
cv::circle(dst2, cv::Point(kp.pt.x, kp.pt.y), 5, cv::Scalar(0, 0, 255), 2, cv::LINE_AA);
}
cv::imshow("src", src);
cv::imshow("GFTT", dst1);
cv::imshow("FAST", dst2);
while(cv::waitKey(0) != 'q')
continue;
int t_val = 128;
cv::Mat src_, dst_;
void on_trackbar_threshold(int, void*)
{
cv::threshold(src_, dst_, t_val, 255, cv::THRESH_BINARY_INV);
cv::imshow("dst", dst_);
}
int main()
{
s
src_ = cv::imread("neutrophils.png", cv::IMREAD_GRAYSCALE);
if(src_.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::namedWindow("src");
cv::imshow("src", src_);
cv::namedWindow("dst");
cv::createTrackbar("Threshold", "dst", &t_val, 255, on_trackbar_threshold);
on_trackbar_threshold(0, 0);
while(cv::waitKey(0) != 'q')
continue;
return 0;
}
OTSU 이진화
히스토그램 분포가 bimodal 형태인 경우 사용.
cv::Mat src = cv::imread("rice.png", cv::IMREAD_GRAYSCALE);
if(src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat dst;
double threshold = cv::threshold(src, dst, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); // 0, 255는 사용되지 않는다. BINARY_INV를 대신 넣어주면 반전됨.
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
영상의 조명이 균일하지 않을 때 효과적인 기법.
원본 이미지를 16개로 나눠 각각 OTSU 이진화 수행.
cv::Mat src = cv::imread("rice.png", cv::IMREAD_GRAYSCALE);
if(src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
int w = src.cols / 4;
int h = src.rows / 4;
cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC1);
double threshold = cv::threshold(src, dst, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
for(int y = 0; y < 4; y++)
{
for(int x = 0; x < 4; x++)
{
cv::Mat src_ = src(cv::Rect(x*w, y*h, w, h));
cv::Mat dst_ = dst(cv::Rect(x*w, y*h, w, h));
cv::threshold(src_, dst_, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
}
}
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
각각의 픽셀마다 다른 threshold 사용.
blocksize(6번째 인자) 크기의 window를 생성. window 내부 픽셀 값의 평균값을 사용.
평균값에서 C(7번째 인자)값을 뺀 값이 threshold가 됨.
4번째 인자
int block_size = 51;
cv::Mat src_, dst_;
void on_trackbar(int, void*)
{
int bsize = block_size;
std::cout << bsize << std::endl;
if((bsize & 1) == 0)
bsize--;
if(bsize < 3)
bsize = 3;
std::cout << "->" << bsize << std::endl;
cv::adaptiveThreshold(src_, dst_, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C,
cv::THRESH_BINARY, bsize, 5);
cv::imshow("dst", dst_);
}
int main()
{
src_ = cv::imread("sudoku.jpg", cv::IMREAD_GRAYSCALE);
if(src_.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::namedWindow("src");
cv::imshow("src", src_);
cv::namedWindow("dst");
cv::createTrackbar("Block size", "dst", &block_size, 201, on_trackbar);
on_trackbar(0, 0);
while(cv::waitKey(0) != 'q')
continue;
return 0;
}
침식(erode) 연산: 작은 크기의 잡음 제거 효과.
팽창(dilation) 연산: 객체 외곽을 확대시키는 효과.
cv::erode(src, dst, cv::Mat());
cv::dilate(src, dst, cv::Mat());
// 3번째 인자를 직접 입력할 경우
// cv::getStructuringElement() 함수로 kernel을 생성 가능.
열기(opening) 연산: 침식 -> 팽창
닫기(closing) 연산: 팽창 -> 침식
cv::Mat src = cv::imread("rice.png", cv::IMREAD_GRAYSCALE);
if(src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
// local threshold
int w = src.cols / 4;
int h = src.rows / 4;
cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC1);
for(int y = 0; y < 4; y++)
{
for(int x = 0; x < 4; x++)
{
cv::Mat src_ = src(cv::Rect(x*w, y*h, w, h));
cv::Mat dst_ = dst(cv::Rect(x*w, y*h, w, h));
cv::threshold(src_, dst_, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
}
}
cv::Mat dst2;
cv::morphologyEx(dst, dst2, cv::MORPH_OPEN, cv::Mat()); // MORPH_CLOSE ...
cv::imshow("src", src);
cv::imshow("dst", dst);
cv::imshow("dst2", dst2);
while(cv::waitKey(0) != 'q')
continue;
레이블링 함수
cv::Mat labels;
int N = cv::connectedComponents(src, labels);
// N = 객체+배경 갯수 (0은 배경, 1~N-1은 객체)
객체 정보를 함께 반환하는 레이블링 함수
cv::Mat labels, stats, centroids;
// stats -> 객체별 바운딩박스 + 픽셀 개수 행렬(N by 5)
// centroids -> 객체별 무게중심 위치 행렬(N by 2)
int N = connectedComponentsWithStats(src, labels, stats, centroids);
예제
uchar data[] = {
0, 0, 1, 1, 0, 0, 0, 0,
1, 1, 1, 1, 0, 0, 1, 0,
1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 0,
0, 0, 1, 1, 0, 0, 1, 0,
0, 0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0,
};
cv::Mat src(8, 8, CV_8UC1, data);
cv::Mat labels, stats, centroids;
int num_labels = cv::connectedComponentsWithStats(src, labels, stats, centroids);
std::cout << "src:\n" << src << std::endl;
std::cout << "num of labels: " << num_labels<< std::endl;
std::cout << "labels:\n" << labels << std::endl;
std::cout << "stats:\n" << stats << std::endl;
std::cout << "centroids:\n" << centroids << std::endl;
output:
src:
[ 0, 0, 1, 1, 0, 0, 0, 0;
1, 1, 1, 1, 0, 0, 1, 0;
1, 1, 1, 1, 0, 0, 0, 0;
0, 0, 0, 0, 0, 1, 1, 0;
0, 0, 0, 1, 1, 1, 1, 0;
0, 0, 1, 1, 0, 0, 1, 0;
0, 0, 1, 1, 1, 1, 1, 0;
0, 0, 0, 0, 0, 0, 0, 0]
num of labels: 4
labels:
[0, 0, 1, 1, 0, 0, 0, 0;
1, 1, 1, 1, 0, 0, 2, 0;
1, 1, 1, 1, 0, 0, 0, 0;
0, 0, 0, 0, 0, 3, 3, 0;
0, 0, 0, 3, 3, 3, 3, 0;
0, 0, 3, 3, 0, 0, 3, 0;
0, 0, 3, 3, 3, 3, 3, 0;
0, 0, 0, 0, 0, 0, 0, 0]
stats:
[0, 0, 8, 8, 39;
0, 0, 4, 3, 10;
6, 1, 1, 1, 1;
2, 3, 5, 4, 14]
centroids:
[3.615384615384615, 3.692307692307693;
1.7, 1.2;
6, 1;
4.285714285714286, 4.785714285714286]
키보드 영상에서 문자 영역 분할 예제
cv::Mat src = cv::imread("keyboard.bmp", cv::IMREAD_GRAYSCALE);
if(src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat src_bin;
cv::threshold(src, src_bin, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
cv::Mat labels, stats, centroids;
int cnt = cv::connectedComponentsWithStats(src_bin, labels, stats, centroids);
cv::Mat dst;
cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);
for(int i = 1; i < cnt; i++)
{
int* p = stats.ptr<int>(i);
if (p[4] < 20) continue; // pixel 개수가 20 미만이면(너무 작은 객체) continue
cv::rectangle(dst, cv::Rect(p[0], p[1], p[2], p[3]), cv::Scalar(0, 255, 255));
}
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;s
외곽선 검출 함수
void cv::findContours(src, vector<vector<Point>> contours, vector<Vec4i> hierarchy, int mode, int method);
// hierarchy[i] : next, prev, child, parent를 담고 있음.
// mode : 외곽선 검출모드 RETR_EXTERNAL, RETR_LIST, RETR_CCOMP, RETR_TREE 중 선택.
// method : 외곽선 근사화방법 CHAIN_APPROX_NONE or CHAIN_APPROX_SIMPLE.
외곽선 그리기 함수
void cv::drawContours(src, contours, -1, color, thickness, LINE_8, hierarchy);
외곽선 검출, 그리기 예제
cv::Mat src = cv::imread("milkdrop.bmp", cv::IMREAD_GRAYSCALE);
if(src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat src_bin;
cv::threshold(src, src_bin, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU); // binarization
std::vector<std::vector<cv::Point>> contours;
cv::findContours(src_bin, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE);
cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);
for(int i = 0; i < contours.size(); i++)
{
cv::Scalar color(rand() & 255, rand() & 255, rand() & 255);
cv::drawContours(dst, contours, i, color, 2, cv::LINE_8);
}
cv::imshow("src", src);
cv::imshow("src_bin", src_bin);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
외곽선 검출 및 계층정보 기반 그리기 예제
cv::Mat src = cv::imread("contours.bmp", cv::IMREAD_GRAYSCALE);
if(src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(src, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_NONE);
cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3);
for(int i = 0; i >= 0; i = hierarchy[i][0])
{
cv::Scalar color(rand() & 255, rand() & 255, rand() & 255);
cv::drawContours(dst, contours, i, color, 2, cv::LINE_8, hierarchy);
}
cv::imshow("src", src);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
외곽선 관련 함수들
다각형 판별 예제
영상에서 삼각형, 사각형, 원을 찾는 예제.
void setLabel(cv::Mat& img, const std::vector<cv::Point>& pts, const cv::String& label)
{
cv::Rect rc = cv::boundingRect(pts);
cv::rectangle(img, rc, cv::Scalar(0, 0, 255), 1);
cv::putText(img, label, rc.tl(), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(0, 0, 255));
}
int main()
{
cv::Mat src = cv::imread("polygon.bmp", cv::IMREAD_COLOR);
if(src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
cv::Mat gray;
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
cv::Mat bin;
cv::threshold(gray, bin, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
std::vector<std::vector<cv::Point>> contours;
cv::findContours(bin, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
for(const std::vector<cv::Point>& pts : contours)
{
// 작은 contour 제외
if(cv::contourArea(pts) < 400)
continue;
// contour 근사화
std::vector<cv::Point> approx;
cv::approxPolyDP(pts, approx, cv::arcLength(pts, true)*0.02, true);
// non convex 객체 제외
if(!cv::isContourConvex(approx))
continue;
int vtc = (int)approx.size(); // 꼭지점 개수
if(vtc == 3)
{
setLabel(src, pts, "TRI");
}
else if(vtc == 4)
{
setLabel(src, pts, "RECT");
}
else
{
// 원 판별
double len = cv::arcLength(pts, true);
double area = cv::contourArea(pts);
double ratio = 4. * CV_PI * area / (len * len);
if (ratio > 0.85)
setLabel(src, pts, "CIR");
}
}
cv::imshow("src", src);
while(cv::waitKey(0) != 'q')
continue;
return 0;
}
템플릿 매칭 비교 방법
cv::Mat src = cv::imread("circuit.bmp", cv::IMREAD_GRAYSCALE);
cv::Mat tmpl = cv::imread("crystal.bmp", cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
src = src + 50;
#if 1
// add random noise (optional)
cv::Mat noise(src.size(), CV_32S);
cv::randn(noise, 0, 10); // mean=0, stddev = 10
cv::add(src, noise, src, cv::noArray(), CV_8U);
// remove noise using gaussianblur (optional)
cv::GaussianBlur(src, src, cv::Size(), 1);
#endif
cv::Mat res, res_norm;
cv::matchTemplate(src, tmpl, res, cv::TM_CCOEFF_NORMED);
cv::normalize(res, res_norm, 0, 255, cv::NORM_MINMAX, CV_8U);
double maxVal;
cv::Point maxLoc;
cv::minMaxLoc(res, 0, &maxVal, 0, &maxLoc);
std::cout << "maxVal: " << maxVal << std::endl;
std::cout << "max location: " << maxLoc << std::endl;
cv::Mat dst;
cv::cvtColor(src ,dst, cv::COLOR_GRAY2BGR);
cv::rectangle(dst, cv::Rect(maxLoc.x, maxLoc.y, tmpl.cols, tmpl.rows),
cv::Scalar(0, 0, 255), 2);
// cv::imshow("src", src);
cv::imshow("template", tmpl);
cv::imshow("res_norm", res_norm);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
output:
maxVal: 0.945132
max location: [558, 323]
템플릿이 여러개인 경우.
cv::Mat src = cv::imread("cookierun.png");
cv::Mat tmpl = cv::imread("item.png");
if (src.empty() || tmpl.empty())
{
std::cerr << "Image load failed" << std::endl;
return -1;
}
// 템플릿 매칭
cv::Mat res, res_norm;
cv::matchTemplate(src, tmpl, res, cv::TM_CCOEFF_NORMED);
cv::normalize(res, res_norm, 0, 255, cv::NORM_MINMAX, CV_8UC1);
// 값이 큰 것만 남김.
cv::Mat local_max = res_norm > 220;
// 레이블링
cv::Mat labels;
int num = cv::connectedComponents(local_max, labels);
cv::Mat dst = src.clone();
for(int i = 1; i < num; i++)
{
cv::Point maxLoc;
cv::Mat mask = (labels == i);
cv::minMaxLoc(res, 0, 0, 0, &maxLoc, mask);
cv::rectangle(dst, cv::Rect(maxLoc.x, maxLoc.y, tmpl.cols, tmpl.rows),
cv::Scalar(0, 255, 0), 2);
}
// cv::imshow("src", src);
cv::imshow("template", tmpl);
cv::imshow("local_max", local_max);
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
cv::Mat img_digits[10];
bool load_digits()
{
for(int i = 0; i < 10; i++)
{
cv::String filename = cv::format("./digits/digit%d.bmp", i);
img_digits[i] = cv::imread(filename, cv::IMREAD_GRAYSCALE);
if(img_digits[i].empty())
return false;
}
return true;
}
void set_label(cv::Mat& img, int digit, const std::vector<cv::Point>& contour)
{
int font = cv::FONT_HERSHEY_SIMPLEX;
double font_scale = 1.5;
int thickness = 3;
int baseline = 0;
cv::String text = cv::format("%d", digit);
cv::Size text_sz = cv::getTextSize(text, font, font_scale, thickness, &baseline);
cv::Rect bbox = cv::boundingRect(contour);
cv::Point text_pt(bbox.x + ((bbox.width - text_sz.width) / 2),
bbox.y - ((bbox.height + text_sz.height) / 2));
cv::putText(img, text, text_pt, font, font_scale,
cv::Scalar(255, 0, 0), thickness, cv::LINE_AA);
}
int find_digit(const cv::Mat& img)
{
int max_idx = -1;
float max_ccoeff = -1;
for(int i = 0; i < 10; i++)
{
cv::Mat src, res;
// 템플릿과 같은 크기로 resize
cv::resize(img, src, cv::Size(100, 150));
cv::matchTemplate(src, img_digits[i], res, cv::TM_CCOEFF_NORMED);
// res는 1 by 1 matrix이다.
float ccoeff = res.at<float>(0, 0);
if(ccoeff > max_ccoeff)
{
max_idx = i;
max_ccoeff = ccoeff;
}
}
return max_idx;
}
int main()
{
cv::Mat src = cv::imread("digits.png");
if(src.empty() || !load_digits())
{
std::cerr << "Image load failed!" << std::endl;
return -1;
}
cv::Mat src_gray, src_bin;
cv::cvtColor(src, src_gray, cv::COLOR_BGR2GRAY);
// 이진화
cv::threshold(src_gray, src_bin, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
// 외곽선 검출
std::vector<std::vector<cv::Point>> contours;
cv::findContours(src_bin, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
cv::Mat dst = src.clone();
for(int i = 0; i < contours.size(); i++)
{
// 객체의 크기가 작은 경우 continue
if (cv::contourArea(contours[i]) < 1000)
continue;
cv::Rect rect = cv::boundingRect(contours[i]);
int digit = find_digit(src_gray(rect));
cv::drawContours(dst, contours, i, cv::Scalar(0, 255, 255), 1, cv::LINE_AA);
set_label(dst, digit, contours[i]);
}
cv::imshow("dst", dst);
while(cv::waitKey(0) != 'q')
continue;
return 0;
}
subpixel accuracy: 실수 단위로 특정 좌표를 기술하는 기법.
정수 단위 에지 위치 검출
실수 단위 에지 위치 검출
subpixel에 대해서 궁금한게 있어요.
cv::minMaxLoc로 근사치값을 받을때 약간의 미동이라도 감지하려면 픽셀 가지고 안되서요.
혹시 가능한 방법이 있을까요? 정수 픽셀이 아닌 float 형의 픽셀값?은 받을수 없을까요?