|
|
- void split() : 다중 채널 배열을 여러 개의 단일채널 배열로 분리함.
- void mixChannals() : 명시된 채널의 순서쌍에 의해 입력 배열들(src)로부터 출력 배열들(dst)의 복사함.
2. Mat 클래스의 사칙 연산을 수행하는 함수와 연산의 수행 방법에 대해서 기술하시오.
- void add() : 두 개의 배열이나 배열과 스칼라의 각 원소 간 합을 계산함. 입력 인자 내 하나는 스칼라값일 수 있음.
- void subtract() : 두 개의 배열이나 배열과 스칼라의 각 원소 간 차분을 계산. add() 함수와 동일.
- void multiply() : 두 배열의 각 원소 간 곱을 계산함.
- void divide() : 두 배열의 각 원소 간 나눗셈을 수행함.
- void scaleAdd() : 스케일된 배열과 다른 배열의 합을 계산.
- void addWeighted() : 두 배열의 가중된 합을 계산.
4. Mat 클래스의 사칙 연산이나 논리 비트 연산에서 마스킹을 사용함. mask 행렬의 의미와 사용법에 대해서 설명하시오.
- mask 배열은 8비트의 단일 채널로서, 입력 배열의 원소 좌표 중에서 mask 배열의 원소가 0이 아닌 좌표만 연산 대상으로 함.
- 원하는 위치에 대해서 연산을 수행하고자 할 때, mask 배열의 원하는 위치만 0이 아닌 값으로 지정하면 됨.
5. cv:reduce() 함수에 대해서 설명하고, 특히 감축 시에 연산 옵션에 대해서 상세히 설명하시오.
- void reduce() : 행렬을 열방향 혹은 행방향으로 옵션상수의 연산을 수행하여 벡터로 감축함.
6. 다음 예시 코드는 컴파일 에러가 발생한다. 에러가 발생하는 부분을 수정하고 실행 결과를 적으시오.
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> // OpenCV 라이브러리의 기본 헤더 포함 #include <iostream> // 입출력을 위한 표준 라이브러리 포함 #include <conio.h> // 키보드 입력을 위한 라이브러리 포함 using namespace std; using namespace cv; int main() { // 2x3 행렬 m1 선언 및 초기화 Matx23f m1(1, 2, 3, 1, 2, 3); // 2x3 행렬 m2 선언 및 초기화 Matx23f m2(3, 3, 4, 2, 2, 3); // 결과를 저장할 Mat 타입의 행렬 m3 선언 Mat m3; // m1과 m2를 요소별로 더하여 m3에 저장 add(m1, m2, m3); // OpenCV의 add 함수는 요소별 덧셈을 수행 // 결과를 저장할 Mat 타입의 행렬 m4 선언 Mat m4; // m1과 m2를 요소별로 곱하여 m4에 저장 multiply(m1, m2, m4); // OpenCV의 multiply 함수는 요소별 곱셈을 수행 // 각 행렬의 값을 출력 cout << "[m1] = " << endl << m1 << endl; // m1 행렬 출력 cout << "[m2] = " << endl << m2 << endl; // m2 행렬 출력 cout << "[m3] = " << endl << m3 << endl; // m1 + m2 결과 출력 cout << "[m4] = " << endl << m4 << endl; // m1 * m2 결과 출력 return 0; // 0 반환 후 종료 } | cs |
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 | #include <opencv2/opencv.hpp> // OpenCV 라이브러리의 기본 헤더 포함 #include <iostream> // 입출력을 위한 표준 라이브러리 포함 using namespace std; using namespace cv; int main() { // 1차원 배열 data를 정의 (10개의 정수 요소) int data[] = { 1,2,3,4,5,6,7,8,9,10 }; // 2행 3열, 채널이 2개인 행렬 m1을 정의하고 data를 초기값으로 설정 // CV_8UC2: 8비트 무부호 정수, 2채널 Mat m1(2, 3, CV_8UC2, data); // 2개의 Mat 객체를 담는 배열 sp_mat 선언 // split 함수로 분리된 2개의 채널 데이터를 저장 Mat sp_mat[2]; // m1 행렬의 각 채널을 분리하여 sp_mat 배열에 저장 split(m1, sp_mat); // split 함수는 다채널 행렬을 채널별로 분리 // 첫 번째 채널 행렬 출력 cout << sp_mat[0] << endl; // 두 번째 채널 행렬 출력 cout << sp_mat[1] << endl; return 0; // 0 반환 후 종료 } | cs |
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 | #include <opencv2/opencv.hpp> // OpenCV 라이브러리의 기본 헤더 포함 using namespace std; using namespace cv; int main() { // 2x3 크기의 Mat_<uchar> 타입 행렬 m1, m2를 선언 // m3: m1과 m2의 요소별 뺄셈 결과를 저장 // m4: m1 행렬에서 열 기준 REDUCE_MAX 연산 결과를 저장 Mat_<uchar> m1(2, 3), m2(2, 3), m3, m4; // m1과 m2 행렬에 초기값 설정 m1 << 1, 1, 2, 2, 3, 3; // m1의 초기값 (2x3 행렬) m2 << 2, 1, 3, 1, 4, 1; // m2의 초기값 (2x3 행렬) // m1과 m2의 요소별 뺄셈 결과를 m3에 저장 subtract(m1, m2, m3); // subtract 함수는 요소별 연산 수행 // m1 행렬에서 REDUCE_MAX 연산 수행 (열 방향으로 최대값 찾기) // 0: 열 방향 축소 reduce(m1, m4, 0, REDUCE_MAX); // 각 행렬 출력 cout << "[m1] =" << endl << m1 << endl; // m1 행렬 출력 cout << "[m2] =" << endl << m2 << endl; // m2 행렬 출력 cout << "[m3] =" << endl << m3 << endl; // m1 - m2 결과 출력 cout << "[m4] =" << endl << m4 << endl; // REDUCE_MAX 결과 출력 // m1의 요소 합계 출력 cout << "sum(m1) =" << endl << sum(m1) << endl; // m2의 요소 평균 출력 cout << "mean(m2) =" << endl << mean(m2) << endl; return 0; // 0 반환 후 종료 } | cs |
7. 컬러 영상 파일을 입력받아 RGB의 3개 채널을 분리하고, 각 채널의 컬러 영상을 윈도우에 표시해 보자. 즉, Red 채널은 빨간색으로, Green 채널은 파란색으로, Blue 채널은 파란색으로 표현하도록 프로그램을 완성하시오.
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 | #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { // "fish.jpg" 이미지를 컬러(BGR)로 읽어오기 Mat image = imread("fish.jpg", IMREAD_COLOR), dst; // 이미지 데이터가 올바르게 로드되지 않았으면 프로그램 종료 CV_Assert(image.data); // 이진화(Threshold) 적용: 픽셀 값이 70 이상이면 255(흰색), 그렇지 않으면 0(검은색) threshold(image, dst, 70, 255, THRESH_BINARY); // BGR 채널을 저장할 배열 생성 Mat bgr[3]; // 검정색 단일 채널 이미지를 생성 (채널 분리 시 사용) Mat black(image.size(), CV_8UC1, Scalar(0)); // BGR 채널을 분리 split(image, bgr); // 각각의 채널을 병합하여 특정 색상만 강조된 이미지 생성 Mat bImage[] = { bgr[0], black, black }; // Blue 채널만 강조 Mat gImage[] = { black, bgr[1], black }; // Green 채널만 강조 Mat rImage[] = { black, black, bgr[2] }; // Red 채널만 강조 // 강조된 색상 채널을 병합 merge(bImage, 3, bgr[0]); // Blue 강조 이미지 생성 merge(gImage, 3, bgr[1]); // Green 강조 이미지 생성 merge(rImage, 3, bgr[2]); // Red 강조 이미지 생성 // 원본 이미지 출력 imshow("image", image); // 강조된 각 색상 채널 이미지 출력 imshow("Blue CH", bgr[0]); imshow("Green CH", bgr[1]); imshow("Red CH", bgr[2]); // 키 입력 대기 waitKey(); // 0 반환 후 종료 return 0; } | cs |
9. 3행, 6열의 행렬을 생성하고, 행렬의 원소를 초기화한 후에 cv::reduce() 함수를 이용해서 가로 방향과 세로 방향으로 감축하여 평균을 구한 결과를 출력하시오.
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 | #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { // 3행 6열의 행렬 생성 및 초기화 Mat matrix = (Mat_<float>(3, 6) << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 ); // 결과를 저장할 행렬 선언 Mat rdRow, rdCol; // 세로 방향(행 방향)으로 평균 계산: 행렬을 열 단위로 축소 reduce(matrix, rdRow, 0, REDUCE_AVG); // 가로 방향(열 방향)으로 평균 계산: 행렬을 행 단위로 축소 reduce(matrix, rdCol, 1, REDUCE_AVG); // 원본 행렬 출력 cout << "원본 행렬:" << endl; cout << matrix << endl; // 세로 방향 평균 결과 출력 cout << "행 방향(세로 방향) 평균 결과:" << endl; cout << rdRow << endl; // 가로 방향 평균 결과 출력 cout << "열 방향(가로 방향) 평균 결과:" << endl; cout << rdCol << endl; return 0; // 0 반환 후 종료 } | cs |
13. cv::sortIdx() 함수를 활용해서 다음의 조건에 부합하도록 벡터의 원소를 정렬하시오.
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 | #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { // Rect 객체를 저장할 벡터 r 선언 vector<Rect> r; // 각 영역의 면적과 인덱스를 저장할 행렬 a, i 선언 Mat_<int> a, i; // r 벡터에 다양한 Rect 객체 추가 (좌표와 크기 다르게 설정) r.push_back(Rect(5, 5, 15, 25)); // (x=5, y=5, width=15, height=25) r.push_back(Rect(20, 10, 60, 90)); // (x=20, y=10, width=60, height=90) r.push_back(Rect(30, 30, 20, 30)); // (x=30, y=30, width=20, height=30) r.push_back(Rect(100, 50, 40, 50)); // (x=100, y=50, width=40, height=50) r.push_back(Rect(200, 200, 150, 100)); // (x=200, y=200, width=150, height=100) // 각 Rect의 면적(area)을 계산하여 a에 저장 for (int j = 0; j < r.size(); j++) { a.push_back(r[j].area()); // Rect 객체의 면적을 a에 추가 } // 면적을 기준으로 인덱스를 정렬 (오름차순) sortIdx(a, i, SORT_EVERY_COLUMN); // 면적을 기준으로 정렬된 Rect들의 면적 출력 cout << "정렬된 면적:" << endl; for (int j = 0; j < r.size(); j++) { cout << "Rect " << j + 1 << " 면적: " << a(i(j)) << endl; // 정렬된 면적 출력 } return 0; // 0 반환 후 종료 } | cs |
14. 연립 방정식을 가우시안 소거법의 역함수를 계산해서 해를 구하는 프로그램을 작성하시오.
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 <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { // 3x3 크기의 행렬 A 정의 (행렬 원소는 주어진 값으로 초기화) Matx33f A(3, 6, 3, -5, 6, 1, 2, -3, 5); // 3x1 크기의 벡터 B 정의 Matx31f B(2, 10, 28); // 결과를 저장할 행렬들 정의 Mat A_inv, result1, result2; // A의 역행렬을 LU 분해 방법으로 계산하여 A_inv에 저장 invert(A, A_inv, DECOMP_LU); // A_inv와 B의 곱을 계산하여 result1에 저장 result1 = A_inv * (Mat)B; // A와 B에 대해 선형 방정식을 풀어 결과를 result2에 저장 solve(A, B, result2, DECOMP_LU); // A_inv 출력 (역행렬) cout << "[A_inv] = " << endl << A_inv << endl << endl; // result2 출력 (선형 방정식의 해) cout << "[result2] = " << endl << result2.t() << endl; return 0; // 0 반환 후 종료 } | cs |
15. 5.6절에서 예제_5.6.2의 사각형 회전하기 예제를 확장하여 그 사각형의 중심점을 기준으로 45도 회전시키는 프로그램을 완성하시오.
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 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { // 사각형의 네 점을 저장할 벡터 선언 vector<Point> p1, p2; // p1에 원래 사각형의 점 추가 (4개의 점) p1.push_back(Point(200, 50)); p1.push_back(Point(400, 50)); p1.push_back(Point(400, 250)); p1.push_back(Point(200, 250)); // 45도 회전을 위한 회전 행렬 생성 float a = 45 * CV_PI / 180; Matx22f m(cos(a), -sin(a), sin(a), cos(a)); // p1을 회전하여 p2에 저장 transform(p1, p2, m); // 회전 전후의 중심점을 계산하기 위한 변수 선언 Point c1(0, 0), c2(0, 0), offset(0, 0); // 각 사각형 점의 평균(중심) 계산 for (int i = 0; i < 4; i++) { c1.x += p1[i].x; c1.y += p1[i].y; c2.x += p2[i].x; c2.y += p2[i].y; } // 평균값으로 중심점 계산 c1.x /= 4; c1.y /= 4; c2.x /= 4; c2.y /= 4; // 두 중심점 간의 차이를 계산하여 offset 변수에 저장 if (c1.x - c2.x < 0) offset.x = -abs(c1.x - c2.x); else offset.x = abs(c1.x - c2.x); if (c1.y - c2.y < 0) offset.y = -abs(c1.y - c2.y); else offset.y = +abs(c1.y - c2.y); // 흰색 배경의 이미지를 생성 (400x500 크기) Mat img(400, 500, CV_8UC3, Scalar(255, 255, 255)); // 사각형의 각 점을 이미지에 그리기 for (int i = 0; i < 4; i++) { // 원본 사각형은 검은색 선으로 그림 line(img, p1[i], p1[(i + 1) % 4], Scalar(0), 1); // 회전된 사각형은 파란색 선으로 그림, 중심점 오프셋 적용 line(img, p2[i] + offset, p2[(i + 1) % 4] + offset, Scalar(255, 0, 0), 2); // 각 점의 좌표 출력 (디버깅용) cout << "p1[" + to_string(i) + "]=" << p1[i] << "\t"; cout << "p2[" + to_string(i) + "]=" << p2[i] << endl; } // 결과 이미지를 화면에 표시 imshow("image", img); waitKey(); return 0; // 0 반환 후 종료 } | cs |
chatper 06 # 화소 처리
<문제 풀이>
1. Mat::at() 함수의 이용한 행렬의 원소 접근 방법에 대해서 상세히 기술하시오.
- 행렬의 지정된 원소에 접근하는 템플릿 함수임.
- 인수 i, j, k는 각각 0-차원, 1-차원, 2차원 배열 인덱스이며, Point 객체와 배열로도 원소 접근이 가능함.
- 다채널 행렬에 대한 원소의 접근은 Vec 객체를 통해서 가능
- 주의점은 Point 객체로 접근할 경우, x 좌표가 먼저이기 때문에 Point(x, y) 형식이 되어야 함.
2. 그레이 스케일(gray-scale) 이미지가 의미하는 것이 무엇인지 설명하시오.
- 단일채널로 구성된 영상으로 검은색부터 흰색까지 0~255의 값을 가지는 화소들로 구성됨.
- 명암도 영상이라고도 함.
3. 화소의 밝기와 화소값에 대해서 설명하시오.
- 화소값은 영상의 밝기를 나타냄. 화소값을 변경하면 영상의 밝기를 바꿀 수 있음.
- 따라서 화소값과 화소의 밝기는 같다고 볼 수 있음.
4. 두 개의 영상을 합성하는 방법을 두 가지 이상 기술하시오.
- 비트연산으로 두 영상을 합성하는 방법, 합차연산(getWeight)을 통하여 합성하는 방법이 있음.
5. 영상에서 밝기 변경과 명암 대비 변경의 차이를 설명하시오.
- 밝기 변경은 화소값에 대하여 일괄적 혹은 부분적 영역에 대하여 값(화소) 자체를 증가시키는 것임. 그에 비해 명암 대비 변경은 값 화소에 대해 일정 값(1.0 이상 혹은 이하)을 곱해줌.
6. 영상 처리에서 히스토그램이란 무엇인가?
- 화소의 분포를 나타내는 지표.
- 이 분포를 이해하면 영상의 특성을 판단할 수 있는 도구가 됨.
8. 히스토그램 평활화 과정을 기술하시오.
- 영상의 히스토그램을 계산
- 히스토그램 빈도값에서 누적 빈도수(누적합)를 계산.
- 누적 빈도수를 정규화(정규화 누적합).
- 결과 화소값 = 정규화 누적합 * 최대 화소값
10. OpenCV함수 중에서 cv::addWeighted() 함수를 사용해서 두 영상을 합성하는 프로그램을 작성하시오. + 트랙바 추가해서 반영 비율을 조절할 수 있도록 수정하시오.
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 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; // 트랙바 콜백 함수 선언 void on_trackbar1(int pos, void* userdata); void on_trackbar2(int pos, void* userdata); // 이미지 파일 경로와 변수 설정 static const Mat img1 = imread("C:/Users/ms/source/repos/OpenCVvv/OpenCVvv/fish.jpg"); // 첫 번째 이미지 static const Mat img2 = imread("C:/Users/ms/source/repos/OpenCVvv/OpenCVvv/fish2.jpg"); // 두 번째 이미지 static int alpha = 50; // 첫 번째 이미지의 가중치 (트랙바 값) static int beta = 50; // 두 번째 이미지의 가중치 (트랙바 값) int main() { // 결과 이미지를 초기화 (img1과 img2의 크기에 맞는 빈 결과 이미지 생성) Mat result(Size(img1.size()), CV_8UC3); // 'img'라는 이름의 윈도우 생성 namedWindow("img"); resizeWindow("img", Size(img1.size())); // 이미지 크기에 맞게 윈도우 크기 설정 // 트랙바 생성: 트랙바를 이용하여 alpha와 beta 값을 조절할 수 있게 설정 createTrackbar("trackbar1", "img", &alpha, 100, on_trackbar1); // 첫 번째 트랙바 (alpha 값) createTrackbar("trackbar2", "img", &beta, 100, on_trackbar2); // 두 번째 트랙바 (beta 값) // 초기 상태에서 두 이미지의 가중치를 합성하여 결과 이미지 생성 addWeighted(img1, (double)alpha / 100, img2, (double)beta / 100, 0, result); imshow("img", result); // 결과 이미지 표시 // 사용자가 키를 누를 때까지 대기 waitKey(); return 0; // 0 반환 후 종료 } // 첫 번째 트랙바에서 값이 변경될 때 호출되는 콜백 함수 void on_trackbar1(int pos, void* userdata) { Mat result; // 결과 이미지를 저장할 변수 alpha = pos; // 트랙바에서 선택된 값을 alpha에 저장 // 두 이미지의 가중치를 변경하여 합성 addWeighted(img1, (double)alpha / 100, img2, (double)beta / 100, 0, result); imshow("img", result); // 결과 이미지 업데이트 } // 두 번째 트랙바에서 값이 변경될 때 호출되는 콜백 함수 void on_trackbar2(int pos, void* userdata) { Mat result; // 결과 이미지를 저장할 변수 beta = pos; // 트랙바에서 선택된 값을 beta에 저장 // 두 이미지의 가중치를 변경하여 합성 addWeighted(img1, (double)alpha / 100, img2, (double)beta / 100, 0, result); imshow("img", result); // 결과 이미지 업데이트 } | 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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; // 마우스 이벤트 처리 함수 선언 void on_mouse(int event, int x, int y, int flags, void* userdata); int main() { // 'fish.jpg' 이미지를 읽어 Mat 객체로 저장 Mat fish = imread("fish.jpg"); // 원본 이미지를 복사하여 수정 가능한 이미지로 저장 Mat re_fish = fish.clone(); // 'fish_re'라는 이름의 윈도우 생성 (이미지를 표시할 윈도우) namedWindow("fish_re"); // 'fish_re' 윈도우에 마우스 클릭 이벤트가 발생하면 on_mouse 함수를 호출 setMouseCallback("fish_re", on_mouse, &re_fish); // 원본 이미지를 'fish'라는 이름의 윈도우에 표시 imshow("fish", fish); // 수정된 이미지를 'fish_re' 윈도우에 표시 imshow("fish_re", re_fish); // 키 입력을 기다림 (사용자가 키를 누를 때까지 대기) waitKey(); return 0; // 0 반환 후 종료 } // 마우스 이벤트에 따른 동작을 처리하는 콜백 함수 void on_mouse(int event, int x, int y, int flags, void* userdata) { // 전달된 Mat 객체를 참조 (이미지 수정) Mat img = *(Mat*)userdata; // 흰색 색상 정의 (흰색을 반전하여 이미지 수정) Scalar white(255, 255, 255); // 마우스 클릭 시 시작 좌표를 저장할 변수 static Point pt1; switch (event) { case EVENT_LBUTTONDOWN: // 마우스 왼쪽 버튼을 눌렀을 때 시작 좌표 저장 pt1 = Point(x, y); break; case EVENT_LBUTTONUP: // 마우스 왼쪽 버튼을 떼었을 때 끝 좌표 저장 Point pt2(x, y); // 두 점을 이용하여 직사각형 영역을 생성 Rect array(pt1, pt2); // 생성된 직사각형 영역을 흰색으로 반전시킴 img(array) = white - img(array); break; } // 수정된 이미지를 'fish_re' 윈도우에 업데이트하여 표시 imshow("fish_re", img); } | cs |
chapter 07 # 영역 처리
<문제 풀이>
1. 입력 영상의 화소와 마스크 행렬로 회선 알고리즘을 상세히 설명하시오.
- 입력 영상의 각 화소에 마스크를 적용하여 새로운 값을 계산하는 과정. 마스크는 영상에서 특정 필터링 효과를 위해 가중치를 제공하며, 가중합을 통해 이미지의 변형을 수행함. 이 방법은 엣지 검출, 블러링, 샤프닝 등 다양한 이미지 처리에 활용함.
2. 블러링과 샤프닝을 비교 설명하시오.
- 블러링이 이웃 화소의 차이를 감소시켜서 부드럽게 만다는 것이라면, 샤프닝은 출력 화소에서 이웃 화소끼리 차이를 크게 해서 날카로운 느낌이 나게 만드는 것임.
- 샤프닝은 영상의 세세한 부분을 강조할 수 있으며, 경계 부분에서 명암대비가 증가되는 효과를 낼 수 있음. 그에 비해 블러링은 영상이 전체적으로 부드러운 느낌이 나게 만드는 것임.
3. 에지 검출 방법의 종류를 아는 데로 적으시오.
- 차분 연산, 1차 미분 마스크, 2차 미분 마스크, 캐니
4. 대표적인 1차 미분 마스크의 종류를 적고, 그 장단점을 기술하시오.
- 소벨(Sobel) 마스크가 대표적인 1차 미분 마스크임.
- 수직, 수평 방향의 에지를 잘 추출하며, 특히 중심화소의 차분 비중을 높였기 때문에 대각선 방향의 에지도 잘 검출함. (장점)
- 1차 미분 마스크이므로 밝기가 급격하게 변화하는 영역뿐 아니라 점진적으로 변화하는 부분까지 민감하게 에지를 검출하여 너무 많은 에지가 나타날 수 있음. (단점)
5. 2차 미분 마스크의 종류와 특징에 대해서 설명하시오.
- 1) 라플라시안 에지 검출
# 중심화소와 4방향 혹은 8방향의 주변화소와 차분을 합하여 에지를 검출하기 때문에 주변 화소에 잡음 성분이 있으면 잡음 성분에 매우 민감하여 실제보다 더 많은 에지를 검출함
- 2) LoG와 DoG
# 잡음을 먼저 제거하는 작업이 있음 -> 라플라시안 에지 검출 보완
# 잡음 제거 필터링을 수행하고, 다시 라플라시안을 수행하는 방식, 속도에서 문제가 있음
- 3) 캐니 에지 검출
# 블러링을 통한 노이즈 제거(가우시안 블러링)
# 화소 기울기(gradiant)의 강도와 방향 검출 (소벨 마스크)
# 비최대치 억제(non_maximum supperssion)
# 이력 임계값(hysteresis threshold)으로 에지 결정
6. 캐니 에지 알고리즘의 과정을 설명하시오.
- 블러링을 통한 노이즈 제거(가우시안 블러링)
- 화소 기울기(gradiant)의 강도와 방향 검출 (소벨 마스크)
- 비최대치 억제(non_maximum supperssion)
- 이력 임계값(hysteresis threshold)으로 에지 결정
7. 최댓값/최솟값 필터링에 대해서 설명하시오.
- 입력 영상의 해당 화소(중심화소)에서 마스크로 씌워진 영역의 입력 화소들을 가져와서 그 중에 최댓값 혹은 최솟값을 출력화소로 결정하는 방법. 즉, 최댓값 필터링은 마스크 계수 중에서 최대값을 통과시켜 출력화소가 되고, 최솟값 필터링은 최솟값을 통과시켜 출력 화소가 되는 것임.
8. 평균값 필터링과 미디언 필터링을 비교설명하시오.
- 평균값 필터링은 마스크 영역 내의 배열에 대한 평균을 화소값에 부여하는 것이고, 마스크 영역 내의 배열을 정렬해서 중간값에 위치하는 화소값을 부여하는 것은 미디언 필터링임.
9. 모폴로지 연산 종류에 대해서 적고, 각각에 대해서 설명하시오.
- 침식 연산 # 객체를 침식시키는 연산. 객체의 크기는 축소시키고 배경은 확장됨. 잡음들 제거를 함.
- 팽창 연산 # 객체를 팽창시키는 연산. 객체의 최외각 화소를 확장시키는 기능을 하기 때문에 객체의 크기는 확대되고 배경은 축소됨. 또한 객체의 팽창으로 인해서 객체 내부에 있는 빈 공간도 메워짐.
- 열림 연산과 닫힘 연산 # 침식 연산과 팽창 연산의 순서를 조합하여 수행하는 연산임. 열림은 침식->팽창 순, 닫힘은 그 반대의 순서로 수행함.
10. 각기 다른 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 25 26 27 28 29 30 31 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { // 이미지를 그레이스케일로 읽어들임 Mat img = imread("fish.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; // 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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { // 이미지를 읽어옴 Mat img = imread("fish.jpg"); CV_Assert(img.data); // 이미지 파일이 제대로 로드됐는지 확인 // 원본 이미지 출력 imshow("img_origin", img); // BGR 채널로 나누어 저장할 배열 Mat arr[3]; split(img, arr); // BGR 채널로 분리하여 배열에 저장 // 샤프닝 마스크 정의 float k[] = { -1, -1, -1, -1, 9, -1, -1, -1, -1 }; Mat mask(3, 3, CV_32F, k); // 3x3 샤프닝 커널 생성 // 각 채널에 필터(샤프닝) 적용 filter2D(arr[0], arr[0], -1, mask); // Blue 채널에 필터 적용 filter2D(arr[1], arr[1], -1, mask); // Green 채널에 필터 적용 filter2D(arr[2], arr[2], -1, mask); // Red 채널에 필터 적용 // 수정된 채널을 합쳐서 최종 이미지 생성 merge(arr, 3, img); // 배열에 있는 채널을 합쳐 최종 이미지 생성 // 필터링된 이미지 출력 imshow("img", img); // 키 입력 대기 waitKey(); return 0; // 0 반환 후 종료 } | 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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; void med_filter(Mat src, Mat& dst, Size sz); int main() { // 그레이스케일로 이미지 읽기 Mat img = imread("fish.jpg", IMREAD_GRAYSCALE); CV_Assert(img.data); // 이미지 파일이 제대로 로드됐는지 확인 // 이미지에 테두리 추가 (반사형 테두리) copyMakeBorder(img, img, 5, 5, 5, 5, BORDER_REFLECT); Mat out; // 출력 이미지 변수 med_filter(img, out, Size(3, 3)); // 3x3 미디언 필터 적용 // 결과 이미지 출력 imshow("dst", out); imshow("img", img); // 키 입력 대기 waitKey(); return 0; // 0 반환 후 종료 } // 미디언 필터링 함수 void med_filter(Mat src, Mat& dst, Size sz) { // 출력 이미지 초기화 (src와 같은 크기, 단일 채널) dst = Mat(src.size(), CV_8U, Scalar(0)); // 필터 크기 중앙 위치 계산 Point pt(sz / 2); // 이미지의 각 픽셀에 대해 필터 적용 for (int i = pt.y; i < src.rows - pt.y; i++) { for (int j = pt.x; j < src.cols - pt.x; j++) { // 필터 적용할 영역(ROI) 계산 Point start = Point(j, i) - pt; Rect roi(start, sz); Mat mask = src(roi); // ROI 영역 추출 vector<uchar> v; // ROI 내의 픽셀 값 저장용 벡터 // 3x3 마스크 내의 픽셀 값 모두 벡터에 추가 for (int x = 0; x < 3; x++) { for (int y = 0; y < 3; y++) { v.push_back(mask.at<uchar>(x, y)); } } // 벡터 정렬 후 중앙값(median) 계산 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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #include <opencv2/opencv.hpp> using namespace std; using namespace cv; void min_tk(int pos, void* userdata); void max_tk(int pos, void* userdata); int main() { // 이미지 로드 Mat img = imread("fish.jpg"); CV_Assert(img.data); // 이미지 확인 // 트랙바 변수 초기화 int min = 50, max = 100; // 트랙바와 윈도우 생성 namedWindow("canny"); createTrackbar("min", "canny", &min, 100, min_tk); // 최소값 트랙바 createTrackbar("max", "canny", &max, 200, max_tk); // 최대값 트랙바 Mat cany; // 엣지 검출 이미지 while (true) { // Canny 엣지 검출 Canny(img, cany, min, max); // 결과 이미지 출력 imshow("canny", cany); // 'q' 또는 'Q'를 누르면 종료 char c = waitKey(1); if (c == 'q' || c == 'Q') break; } return 0; // 0 반환 후 종료 } // 최소값 트랙바 콜백 함수 void min_tk(int pos, void* userdata) { setTrackbarPos("min", "canny", pos); // 트랙바 값 설정 } // 최대값 트랙바 콜백 함수 void max_tk(int pos, void* userdata) { // 최대값이 최소값보다 작으면 100으로 설정 if (pos < 100) pos = 100; setTrackbarPos("max", "canny", pos); // 트랙바 값 설정 } | cs |