|
|
1. 입력 영상의 화소와 마스크 행렬로 회선 알고리즘을 상세히 설명하시오.
회선은 입력 영상의 각 화소값에 대하여 일정 크기와 데이터를 소지한 마스크에 대하여 화소와 마스크의 좌표값을 대응되게 곱하여 출력 화소값을 출력한다. 마스크 행렬에 따라 블러링, 샤프닝, 에지 등의 역할을 수행한다.
2. 블러링과 샤프닝을 비교 설명하시오.
블러링은 화소값이 급진적으로 변하는 부분에 대하여 화소값을 감소시켜 부드러운 느낌을 주는 기법이고
샤프닝은 이웃 화소끼리의 차이를 늘려 날카로운 느낌이 나게 하는 기법이다.
3. 에지 검출 방법의 종류를 아는 데로 적으시오.
차분연산, 1차 미분 마스크(로버츠, 프리윗, 소벨), 2차 미분 마스크(라플라시안, LoG, DoG), 캐니
4. 대표적인 1차 미분 마스크의 종류를 적고, 그 장단점을 기술하시오.
소벨(Sobel) 마스크 : 프리윗 마스크에서 중앙 요소값의 크기를 늘린 마스크를 가진 에지 검출 방법으로 다른 에지 마스크에 비해서 대각선 방향의 에지도 검출하기 좋다. 하지만 점진적으로 변화하는 영역에 대해서도 민감하게 반응하는 1차 미분 마스크의 한계를 벗어나지 못한다.
5. 2차 미분 마스크의 종류와 특징에 대해서 설명하시오.
일반 1차 미분 마스크의 단점인 점진적 명암변화에 대해서 에지로 판별하는 단점을 보안한 마스크로 1차 미분에서 한번 더 미분한 2차 미분을 통해 에지가 발생한 지점에서의 변화율을 검사함으로써 점진적 변화에 대한 단점을 보완한다.
라플라시안 : 중심화소를 4배로 하고 나머지 화소를 중심 화소의 반대로 설정한다. 경우에 따라 중심 화소를 8로 설정하면 나머지 8방향의 화소를 -1로 설정하여 총 합은 0이 되도록 한다.
LoG : 라플라시안 마스크의 단점을 보완한 마스크로 잡음에 대해서 에지로 판별하는것을 방지하기 위해 가우시안블러를 통해 잡음을 제거한 이후 라플라시안 연산을 진행한다.
DoG : LoG는 연산이 많이 필요하여 가우시안 블러를 2번 구해 그 차를 구한뒤 정규화 하는 과정을 통해 에지를 구한다.
캐니 : 영상 내에서 다른 부분과 경계를 이루는 잡음에 대해서 보완한 마스크로
6. 캐니 에지 알고리즘의 과정을 설명하시오.
1. 블러링을 통하여 이미지에 남아있는 잡음을 제거한다.
2. 마스크를 통하여 값이 급변하는 영역에 대해서 에지를 감지한다.
3. 에지가 발생한 지점에 대하여 x축과 y축을 구별하여 미분하여 방향성과 크기를 구할 값들을 얻는다.
4. 에지의 크기에 대하여 주변의 에지보다 자신의 에지값이 더 크다면 값을 유지하고 아니면 에지로 판별하지 않는다.
5. 낮은 임계값과 높은 임계값을 부여하여 높은 임계값 이상의 값은 유지하고 낮은 임계값과 높은 임계값에 있는 값들은 높은 임계값에 대한 연관성을 보아 에지로 결정하거나 에지에서 제외한다.
참고 링크
https://carstart.tistory.com/188
7. 최댓값 / 최솟값 필터링에 대해서 설명하시오.
마스크 영역 내의 배열에서 가장 큰값과 작은값을 현재 화소값으로 부여하는 필터링 기법
8. 평균값 필터링과 미디언 필터링을 비교 설명하시오.
마스크 영역 내의 배열에 대한 평균을 화소값에 부여하는것이 평균값 필터링
마스크 영역 내의 배열을 정렬해서 중간값에 위치하는 화소값을 부여하는것이 미디언 필터링
9. 모폴로지의 연산 종류에 대해서 적고, 각각에 대해서 설명하시오.
침식 : 마스크에 대하여 일치하는 원소가 하나라도 없으면 해당 마스크를 0으로 없애는 마스크이다.
팽창 : 마스크에 대하여 일치하는 원소가 하나라도 있을시에 해당 마스크를 1로 유지하는 마스크
열림 : 침식 연산 이후 닫힘 연산을 진행하는 것으로 잡음을 제거한 이후 팽창연산을 통해 본래 객체 크기를 회복하는 연산
닫힘 : 팽창 연산 이후 침식 연산을 진행하여 객체 내부의 공간을 채우고 침식연산을 통해 본래 객체 크기를 회복하는 연산
10. 각기 다른 OpneCV 함수로 블러링을 수행하도록 작성하시오(3가지 함수 이상).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main(void) { Mat img = imread("C:/Users/AIRLAB/Desktop/img/BGR_image.jpg", IMREAD_GRAYSCALE); CV_Assert(img.data); Mat Gaussian_blur, avg_blur, median_blur; GaussianBlur(img, Gaussian_blur, Size(9, 9), 3); medianBlur(img, median_blur, 5); blur(img, avg_blur, Size(3, 3)); imshow("img", img); imshow("Gaussian", Gaussian_blur); imshow("median", median_blur); imshow("avg", avg_blur); waitKey(); return 0; } | cs |
11. OpenCV 함수를 사용해서 컬러 영상에서 샤프닝을 수행하는 프로그램을 작성하시오.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main(void) { Mat img = imread("C:/Users/AIRLAB/Desktop/test_car/01.jpg"); CV_Assert(img.data); imshow("img_origin", img); Mat img_arr[3]; split(img, img_arr); float data[] = { -1, -1, -1, -1, 9, -1, -1, -1, -1 }; Mat mask(3, 3, CV_32F, data); filter2D(img_arr[0], img_arr[0], -1, mask); filter2D(img_arr[1], img_arr[1], -1, mask); filter2D(img_arr[2], img_arr[2], -1, mask); merge(img_arr, 3, img); imshow("img", img); waitKey(); return 0; } | cs |
12. 예제 7.1.2의 사용자 정의 함수인 filter()를 사용해서 컬러 영상에 블러링과 샤프닝을 수행하도록 프로그램을 작성하시오.
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; void user_filter(Mat img, Mat& dst, Mat mask); void filter(Mat img, Mat& dst, Mat mask) { dst = Mat(img.size(), CV_32F, Scalar(0.0)); Point h_m = mask.size() / 2; for (int i = h_m.y; i < img.rows - h_m.y; i++) { for (int j = h_m.x; j < img.cols - h_m.x; j++) { float sum = 0; for (int u = 0; u < mask.rows; u++) { for (int v = 0; v < mask.cols; v++) { int y = i + u - h_m.y; int x = j + v - h_m.x; sum += mask.at<float>(u, v) * img.at<uchar>(y, x); } } dst.at<float>(i, j) = static_cast<float>(sum); } } } int main(void) { Mat image = imread("C:/Users/AIRLAB/Desktop/img/background_01.jpg"); CV_Assert(image.data); float data1[] = { 1 / 9.0, 1 / 9.0, 1 / 9.0, 1 / 9.0, 1 / 9.0, 1 / 9.0, 1 / 9.0, 1 / 9.0, 1 / 9.0 }; float data2[] = { -1,-1,-1, -1, 9,-1, -1,-1,-1 }; Mat bluring(3, 3, CV_32F, data1); Mat sharp(3, 3, CV_32F, data2); Mat img_s, img_b; user_filter(image, img_b, bluring); user_filter(image, img_s, sharp); img_b.convertTo(img_b, CV_8UC3); img_s.convertTo(img_s, CV_8UC3); imshow("blur", img_b); imshow("sharp", img_s); imshow("img", image); waitKey(); return 0; } void user_filter(Mat img, Mat& dst, Mat mask) { Mat image_arr[3]; split(img, image_arr); filter(image_arr[0], image_arr[0], mask); filter(image_arr[1], image_arr[1], mask); filter(image_arr[2], image_arr[2], mask); merge(image_arr, 3, dst); } | cs |
13. 1차 미분 연산을 수행하도록 마스크를 생성하여 직접 회선을 수행하시오(3가지 연산 마스크 적용).
로버츠(Roberts), 프리윗(Prewitt), 소벨(Sobel)
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; void filter(Mat img, Mat& dst, Mat mask) { dst = Mat(img.size(), CV_32F, Scalar(0)); Point h_m = mask.size() / 2; for (int i = h_m.y; i < img.rows - h_m.y; i++) { for (int j = h_m.x; j < img.cols - h_m.x; j++) { float sum = 0; for (int u = 0; u < mask.rows; u++) { for (int v = 0; v < mask.cols; v++) { int y = i + u - h_m.y; int x = j + v - h_m.x; sum += mask.at<float>(u, v) * img.at<uchar>(y, x); } } dst.at<float>(i, j) = sum; } } } void differential(Mat image, Mat& dst, float data1[], float data2[]) { Mat dst1, dst2; Mat mask1(3, 3, CV_32F, data1); Mat mask2(3, 3, CV_32F, data2); filter(image, dst1, mask1); filter(image, dst2, mask2); magnitude(dst1, dst2, dst); dst1 = abs(dst1); dst2 = abs(dst2); dst.convertTo(dst, CV_8U); } int main(void) { Mat img = imread("C:/Users/AIRLAB/Desktop/find_object/coin_02.jpg", IMREAD_GRAYSCALE); CV_Assert(img.data); float filter_R1[] = { -1, 0, 0, 0, 1, 0, 0, 0, 0 }; float filter_R2[] = { 0, 0, -1, 0, 1, 0, 0, 0, 0 }; float filter_Px[] = { -1, 0, 1, -1, 0, 1, -1, 0, 1 }; float filter_Py[] = { -1, -1, -1, 0 , 0, 0, 1, 1, 1 }; float filter_Sy[] = { -1, -2, -1, 0 , 0, 0, 1, 2, 1 }; float filter_Sx[] = { -1, 0, 1, -2, 0, 2, -1, 0, 1 }; // 로버츠 Mat Roberts; differential(img, Roberts, filter_R1, filter_R2); // 프리윗 Mat prewitt; differential(img, prewitt, filter_Px, filter_Py); // 소벨 Mat sobel; differential(img, sobel, filter_Sx, filter_Sy); imshow("roberts", Roberts); imshow("prewitt", prewitt); imshow("sobel", sobel); imshow("img", img); waitKey(); return 0; } | cs |
| img | Roberts |
| Prewitt | Sobel |
14. 2차 미분 연산을 수행하는 함수로 에지 검출을 수행하시오(2가지 함수 이상).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main(void) { Mat img = imread("C:/Users/AIRLAB/Desktop/find_object/coin_02.jpg", IMREAD_GRAYSCALE); CV_Assert(img.data); Mat laplacia; Laplacian(img, laplacia, CV_8U, 1); Mat dog1, dog2; GaussianBlur(img, dog1, Size(1, 1), 0); GaussianBlur(img, dog2, Size(9, 9), 1.6); Mat DoG = dog1 - dog2; normalize(DoG, DoG, 0, 255, NORM_MINMAX); imshow("laplacia", laplacia); imshow("DoG", DoG); waitKey(); return 0; } | cs |
15. 5x5 크기의 마스크로 최대값 필터링을 수행하는 함수를 작성하고, 수행 결과를 윈도우창에 표시하시오.
단, 경계부분의 화소값은 BORDER_REFLECT 방법으로 나타나게 하시오.
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 32 33 34 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; void max_filtering(Mat src, Mat& dst, Size size); int main(void) { Mat image = imread("C:/Users/AIRLAB/Desktop/test_car/04.jpg", IMREAD_GRAYSCALE); CV_Assert(image.data); copyMakeBorder(image, image, 5, 5, 5, 5, BORDER_REFLECT); Mat dst; max_filtering(image, dst, Size(3, 3)); imshow("dst", dst); imshow("img", image); waitKey(); return 0; } void max_filtering(Mat src, Mat& dst, Size size) { dst = Mat(src.size(), CV_8U, Scalar(0)); Point pt(size / 2); for (int i = pt.y; i < src.rows - pt.y; i++) { for (int j = pt.x; j < src.cols - pt.x; j++) { Point start = Point(j, i) - pt; Rect roi(start, size); Mat mask = src(roi); double max; minMaxLoc(mask, 0, &max); dst.at<uchar>(i, j) = max; } } } | cs |
16. 미디언 필터링을 수행하는 함수를 직접 작성하고, 수행결과를 윈도우 창에 표시하시오.
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 32 33 34 35 36 37 38 39 40 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; void median_filtering(Mat src, Mat& dst, Size size); int main(void) { Mat image = imread("C:/Users/AIRLAB/Desktop/img/BGR_image.jpg", IMREAD_GRAYSCALE); CV_Assert(image.data); copyMakeBorder(image, image, 5, 5, 5, 5, BORDER_REFLECT); Mat dst; median_filtering(image, dst, Size(3, 3)); imshow("dst", dst); imshow("img", image); waitKey(); return 0; } void median_filtering(Mat src, Mat& dst, Size size) { dst = Mat(src.size(), CV_8U, Scalar(0)); Point pt(size / 2); for (int i = pt.y; i < src.rows - pt.y; i++) { for (int j = pt.x; j < src.cols - pt.x; j++) { Point start = Point(j, i) - pt; Rect roi(start, size); Mat mask = src(roi); vector<uchar> v; for (int x = 0; x < 3; x++) { for (int y = 0; y < 3; y++) { v.push_back(mask.at<uchar>(x, y)); } } sort(v.begin(), v.end()); int median = v[v.size()/2]; dst.at<uchar>(i, j) = median; } } } | cs |
17. 캐니 에지 알고리즘에서 이중 임계값을 트랙바로 만들어서 두 개의 임계값을 조절하여 에지를 검출하도록 프로그램을 작성하시오.
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 32 33 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; void min_trackbar(int pos, void* userdata); void max_trackbar(int pos, void* userdata); int main(void) { Mat image = imread("C:/Users/AIRLAB/Desktop/img/background_01.jpg"); CV_Assert(image.data); int min = 50, max = 100; namedWindow("canny"); createTrackbar("min", "canny", &min, 100, min_trackbar); createTrackbar("max", "canny", &max, 200, max_trackbar); Mat cany; while (true) { Canny(image, cany, min, max); imshow("canny", cany); char c = waitKey(1); if (c == 'q' || c == 'Q') break; } return 0; } void min_trackbar(int pos, void* userdata) { setTrackbarPos("min", "canny", pos); } void max_trackbar(int pos, void* userdata) { if (pos < 100) pos = 100; setTrackbarPos("max", "canny", pos); } | cs |
18. 동전 영상에서 동전 객체를 검출하기 위한 전처리를 다음과 같이 수행하시오.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main(void) { Mat img = imread("C:/Users/AIRLAB/Desktop/find_object/coin_02.jpg"); CV_Assert(img.data); Mat gray_img; cvtColor(img, gray_img, COLOR_BGR2GRAY); GaussianBlur(gray_img, gray_img, Size(3, 3), 3); threshold(gray_img, gray_img, 50, 255, THRESH_BINARY); Matx<uchar, 3, 3> mask; mask << 0, 1, 0, 1, 1, 1, 0, 1, 0; morphologyEx(gray_img, gray_img, MORPH_OPEN, mask); imshow("coin", gray_img); waitKey(); return 0; } | cs |
| 1) 명암도 영상 변환 2) 가우시안 블러링 3) 이진화 4) 모폴로지 열림 연산 |
| 실행 결과 | |
검은 배경과 다르게 테이블에서 찍은 동전은 이진화 단계에서 여러 문제가 생김.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main(void) { Mat img = imread("C:/Users/AIRLAB/Desktop/find_object/coin_02.jpg"); CV_Assert(img.data); Mat gray_img; cvtColor(img, gray_img, COLOR_BGR2GRAY); Sobel(gray_img, gray_img, -1, 1, 0); GaussianBlur(gray_img, gray_img, Size(3, 3), 3); threshold(gray_img, gray_img, 30, 255, THRESH_BINARY); Matx<uchar, 3, 3> mask; mask << 0, 1, 0, 1, 1, 1, 0, 1, 0; morphologyEx(gray_img, gray_img, MORPH_ERODE, mask); imshow("MORTH", gray_img); medianBlur(gray_img, gray_img, 3); imshow("coin", gray_img); waitKey(); return 0; } | cs |
에지 연산과 몇가지 함수를 더해서 코드 수정
19. 예제_7.4.1과 예제_7.4.2에서 구현한 침식 연산과 팽창 연산 함수 erosion()와 dilation()는 소스 내용이 거의 동일하다, 두 함수를 참고하여 하나로 통일해서 morphology()함수로 구현하시오.
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | #include <opencv2/opencv.hpp> using namespace cv; bool check_match(Mat img, Point start, Mat mask, int mode = 0) { for (int u = 0; u < mask.rows; u++) { for (int v = 0; v < mask.cols; v++) { Point pt(v, u); int m = mask.at<uchar>(pt); int p = img.at<uchar>(start + pt); bool ch = (p == 255); if (m == 1 && ch == mode) return false; } } } void morphology(Mat img, Mat& dst, Mat mask, int mode) { dst = Mat(img.size(), CV_8U, Scalar(0)); if (mask.empty()) mask = Mat(3, 3, CV_8UC1, Scalar(1)); Point h_m = mask.size() / 2; for (int i = h_m.y; i < img.rows - h_m.y; i++) { for (int j = h_m.x; j < img.cols - h_m.x; j++) { Point start = Point(j, i) - h_m; bool check = check_match(img, start, mask, mode); if (mode == 0) { dst.at<uchar>(i, j) = (check) ? 255 : 0; } else dst.at<uchar>(i, j) = (check) ? 0 : 255; } } } int main(void) { Mat img = imread("C:/Users/AIRLAB/Desktop/test_car/01.jpg", 0); CV_Assert(img.data); Mat th_img, dst1, dst2; threshold(img, th_img, 128, 255, THRESH_BINARY); Matx<uchar, 3, 3> mask; mask << 0, 1, 0, 1, 1, 1, 0, 1, 0; morphology(th_img, dst1, (Mat)mask, 1); morphology(th_img, dst2, (Mat)mask, 0); imshow("dst1", dst1), imshow("dst2", dst2); waitKey(); return 0; } | cs |
20. 심화예제_7.4.4는 키보드로부터 영상 파일의 번호를 입력받아서 열림 연산을 수핸한다. 이 예제를 위쪽과 아래쪽 화살표 키를 이용해서 다음 영상을 로드하여 수행하도록 하며, ESC 키를 누르면 종료하도록 수정하시오.
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 32 33 34 35 | #include <opencv2/opencv.hpp> using namespace cv; using namespace std; int main() { while (1) { static int num = 0; int no; cout << "4 또는 6 입력(종료 27) : "; cin >> no; if (no == 27) break; else if (no == 4) num--; else if (no == 6) num++; string fname = format("C:/Users/AIRLAB/Desktop/test_car/%02d.jpg", num); cout << fname << endl; Mat image = imread(fname, 1); if (image.empty()) { cout << to_string(no) + "번 영상이 없습니다." << endl; continue; } Mat gray, sobel, th_img, morph; Mat kernel(10, 10, CV_8UC1, Scalar(1)); cvtColor(image, gray, COLOR_BGR2GRAY); blur(gray, gray, Size(5, 5)); Sobel(gray, gray, CV_8U, 1, 0, 3); threshold(gray, th_img, 120, 255, THRESH_BINARY); morphologyEx(th_img, morph, MORPH_CLOSE, kernel); imshow("image", image); imshow("이진 영상", th_img), imshow("닫힘 연산", morph); waitKey(0); } return 0; } | cs |
