ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Day9
    교육/서울 ICT 이노베이션 고오급 시각과정 2021. 6. 16. 14:46
    728x90

    에지의 검출과 응용

    1. 에지 검출

    2. 마스크 기반 에지 검출

    #OpenCV는 소벨 마스크를 이용하여 영상을 미분하는 Sobel() 함수 제공
    #3*3 소벨 마스크 또는 확장된 형태의 큰 마스크를 이용하여 영상 미분
    
    void Sobel(src, dst, ddepth, dx, dy, ksize, scale, delta, borderType);
    /*
    src : 입력 영상
    dst : 출력 영상
    ddepth : 영상 타입
    dx : x방향 미분 차수, 주로 1을 많이 사용
    dy : y방향 미분 차수, 주로 1을 많이 사용
    ksize : 소벨 커널의 크기
    scale : 필터링 연산 후 추가적으로 곱할 값, 에지가 너무 강하거나 약한 거 같으면 이걸 변경하면 됨
    delta : 필터링 연산 후 추가적으로 더할 값, 에지가 너무 강하거나 약한 거 같으면 이걸 변경하면 됨
    borderType : 가장자리 픽셀 확장 방식
    */
    int brightness=0;
    void callback(Mat src){
        //1 컬러영상->흑백영상
        Mat gray;
        cvtColor(src, gray, COLOR_BGR2GRAY);
        Mat dst = src + Scalar(brightness, brightness, brightness);
        
        Mat edge;
        Sobel(gray, edge, CV_32F, 1, 1);
        //2 에지 강도 감소 
        Sobel(gray, edge, CV_32F, 1, 1, 3, 0.1);
        
        //3 plus minus 에지가 다 나오게 됨
        Mat a=abs(edge);
        
        imshow("edge", edge);
        imshow("dst", dst);
        imshow("a",a);
        char key = waitKey(33);
        if (key=='w'){
        	brightness +=1;
        }
        else if (key == 'q'){
        	brightness -=1;
        }
        printf("%d\n",brightness);
    }

    3. 캐니 에지 검출기

    좋은 에지 검출기의 조건 : 1.정확한 검출 2. 정확한 위치 3. 단일 위치

    소벨 마스크로 검출

     

    캐니 에지로 검출

     

    캐니 에지 검출은 소벨을 먼저하고 후처리(0아니면 1로 이진화)를 하는 방식

    min, max 2개의 임계값을 설정하고 소벨처리한 값을 살펴봐서 min보다 작으면 배경/max보다 크면 에지로 처리 min~max사이에 있는 값은 주변 픽셀을 살펴서 에지가 있으면 연결시킴

    void Canny(image, edges, threshold1, threshold2, apertureSize, L2gradient);
    /*
    apertureSize : 소벨 마스크 크기
    L2gradient : L2노름을 사용하려면 true를 지정
    */

    3. 허프 변환 직선 검출

    허프변환은 직선을 찾아내기 위한 알고리즘

    허프변환하기 이전에 전처리로 주로 캐니를 많이 사용

    허프변환 알고리즘

    //xcosθ+ysinθ=ρ을 잘 풀어서 그려주는 함수
    void draw_line(Mat dst, Vec2f l, Scalar color){
      float rho = l[0], theta=l[1];
      Point pt1, pt2;
      double a=cos(theta), b=sin(theta);
      double x0=a*rho, y0=b*rho;
      pt1.x=cvRound(x0+1000*(-b));
      pt1.y=cvRound(y0+1000*(a));
      pt2.x=cvRound(x0+1000*(-b));
      pt2.y=cvRound(y0+1000*(a));
      line(dst, pt1, pt2, color, 2, LINE_AA);
      //line()함수는 화면 밖의 점을 찍어도 알아서 선을 그어줌
    }
    
    void main(){
      //원본 영상을 흑백으로 불러옴
      Mat src=imread("images/sudoku.png", IMREAD_GRAYSCALE);
    
      //max임계값을 10씩 증가->최종적으로는 굉장히 강한 에지만 남을 것
      //10부터 시작 min이 50인데 max가 10이면??min도 10, max도 10으로 적용됨!
      for (int i=10;i<255;i+=10){
        Mat dst;
        Canny(src, dst, 50, i);
        imshow("dst",dst);
        waitKey(0);
      }
    
      //위에서 max값을 10씩 증가시켜봤을때 max임계값이 200정도면 적절한 것 같다고 판단
      Mat canny;
      Canny(src, canny, 50, 200);
      vector<Vec2f> lines;
      //<cv::Vec2f> Vec2f는 opencv에 들어있는 데이터타입 2f : 2차원상의 한 점을 float으로 표현
      HoughLines(canny, lines, 1, CV_PI/180, 150); 
      //150 : 임계점, (a,b)를 150번 이상 지나야 직선이라고 판별
      //CV_PI/180이 뭔지 설명하려면 허프라인을 표기하는 함수를 알아야하는데 이건 아래 그래프에서 설명
      //허프변환의 결과로 허프라인이 출력됨->lines
      
      //lines를 출력해주는 코드
      Mat dst;
      cvtColor(src, dst, COLOR_GRAY2BGR);
      for (cons auto& l : lines){
      	draw_line(dst, l , Scalar(0,0,255));
        //l[0] : ρ값, l[1] : θ값
      }
      imshow("canny",canny);
      imshow("dst",dst);
      waitKey(0);
    }
    

    허프라인에서 직선을 표기하는 함수:xcosθ+ysinθ=ρ 
    ->θ,ρ가 정해졌을 때, 방정식을 만족하는 x,y를 구하면 직선의 모양을 갖추게된다라는 의미

    허프변환 알고리즘 설명하는 그림에서는 y=ax+b로 쓴댔는데 왜 갑자기 xcosθ+ysinθ=ρ 이렇게 표현하는 것??

    고거슨 위의 그림에서 ex1,2를 보면 알 수 있다

    ex1을 보게 되면 a가 0이 돼서 y=b가 됨

    ex2를 보게 되면 a가 무한대가 되기때문에 표현할 수 없음 + 허프공간에서 a축이 무한대까지 가야함

    즉, y=ax+b는 직선을 나타내는 방정식을 표현하기에 적절하지 못함!

    HoughLines(canny, lines, 1, CV_PI/180, 120);
    //1 : ρ에 대한 해상도, 1은 1픽셀을 의미
    //CV_PI / 180 : θ에 대한 해상도, CV_PI/180 = 1도를 의미(CV_PI=π)

     

    허프 변환 직선 검출할 때 많이 사용하는 기능들

    1. Non Maximum supression

    //HoughLines(edge, lines, 1, CV_PI/180, 150,0,0);
    //위의 함수에서 lines는 강도 순으로 정렬되어 있음,여기서 강도란 허프공간으로 변환했을때 점에서 겹치는 횟수
    
    //각도의 차이를 확인하는 함수
    float diff_angle(float t1, float t2)
    {
    	float ty1 = cos(t1);
    	float tx1 = sin(t1);
    	float ty2 = -cos(t2);
    	float tx2 = -sin(t2);
    
    	return pow(tx2 - tx1, 2) + pow(ty2 - ty1, 2);
    }
    
    //Non Maximum Supression : lines의 정렬된 순으로 보면서 겹치는지 확인하면서 겹치면 뒤에 들어온 라인을 버림
    vector<Vec2f> selected;	//겹치는 게 없는 직선들
    for (const auto& line : lines)
    {
    //printf("%lf, %lf\n", line[0], line[1]);
    //선택된 라인과 겹치는지 찾는다.
    	bool found = false;
    	for (const auto& s : selected)
    	{
        	//직선이 겹치는 지 확인하는 코드
            //직선의 간격 차이(rho)가 10보다 작고 각도의 차이(theta)가 3.99보다 작으면 겹친다고 가정
    		if (abs(s[0] - line[0]) < 10 && diff_angle(s[0], line[0]) < 3.99)
    		{
    			found = true;
    			break;
    		}
    	}
    	//직선이 겹치면 continue해서 selected에 push X
    	if (found)
    	{
    		continue;
    	}
    	selected.push_back(line);
    }

    왼쪽이 houghline으로 검출된 직선들/오른쪽이 겹친다는 가정을 임의로 설정하여 걸러내고 남은 직선들

    2. cross point검출

    //r = x * cos(theta) + y * sin(theta)
    //두 직선의 방정식을 연립방정식으로 풀어서 겹치는 지점의 x,y좌표를 구하는 함수
    Point2f crossPoint(Vec2f l1, Vec2f l2)
    {
    	//연립방정식을 위한 행렬만들기
    	float t1 = l1[1];
    	float t2 = l2[1];
    	float ty1 = cos(t1);
    	float tx1 = sin(t1);
    	float ty2 = cos(t2);
    	float tx2 = sin(t2);
    	float a[] = { ty1, tx1, ty2, tx2 };
    	float b[] = { l1[0], l2[0] };
    	Mat ma = Mat(2, 2, CV_32F, a);
    	Mat mb = Mat(2, 1, CV_32F, b);
    	Mat mx;
    	//연립방정식 풀기
    	cv::solve(ma, mb, mx);
    	Point2f res;
    	res.x = mx.at<float>(0, 0);
    	res.y = mx.at<float>(1, 0);
    	//=memcpy((void*)&res, mx.data, sizeof(float) * 2);
    	return res;
    }
    
    //가로 세로 나누기
    vector<Vec2f> hor;	//세로
    vector<Vec2f> ver;	//가로
    for (const auto& line : selected)
    {
    	//그 외는 세로선
    	if (line[1] < CV_PI * 0.25 || line[1] > CV_PI * 0.75)
    	{
    		ver.push_back(line);
    	}
    	//theta가 45도~135도면 가로선
    	else
    	{
    		hor.push_back(line);
    	}
    }
    //겹치는 부분 찾아서 circle그려주기
    cvtColor(src, dst, COLOR_GRAY2BGR);
    drawLines(dst, hor, Scalar(0, 0, 255));
    drawLines(dst, ver, Scalar(0, 255, 0));
    //모든 세로선을 하나씩 꺼내고
    for (const auto& lh : hor)
    {	//모든 가로선을 하나씩 꺼내서
    	for (const auto& lv : ver)
    	{
    		Point2f p = crossPoint(lh, lv);
    		circle(dst, p, 3, Scalar(255, 0, 0), FILLED);
    	}
    }
    imshow("cross", dst);
    waitKey(0);

    crossPoint에서 사용하는 방정식

    4. 허프 변환 원 검출

    HoughCircles(blurred, circles, HOUGH_GRADIENT, 1, 50, 150, 30);

    허프 변환 직선 검출 함수에서는 캐니를 적용하고 캐니를 넣어줬었음

    허프 변환 원 검출 함수에는 내부적으로 에지를 찾는 게 들어있음 그래서 에지영상(위에서 말한 캐니)을 넣는 게 아니라 노이즈에 민감하지 말라고 블러영상을 넣음

    컬러영상처리

    1. 컬러 영상 다루기

    1. 컬러 영상 다루기

    //컬러영상 불러오기(BGR)
    Mat img = imread("img.jpg",IMREAD_COLOR);
    
    //컬러영상 픽셀 값 참조
    Vec3b& pixel=img.at<Vec3b>(0,0);

    컬러 영상 한 픽셀을 정확하게 표현하려면 Vec3b를 사용

    2. 색 공간 변환

    색을 표현하는 방법엔 HSV라는 방법도 있음

    H : 컬러값, 360도의 각도를 갖고 있음

    S : 채도, 0: 무채색->파스텔->원색->형광색 : 높음

    V : 명도

    HSV를 사용하는 이유? H를 이용하여 특정 색만 추출하기 위해

     

    하지만 H의 단위가 각도라서 생기는 문제도 있음

    1. 빨간색이 각도가 0인데 x<30, x>310으로 해야함 중간에 끊김

    2. RGB는 평균, 차, 합 등을 구할 때 그냥 계산하면 되는데 HSV는 애매

    추가적인 문제로 S(채도)가 0이면 H를 정의할 수 없음

    이런 부분들때문에 주의해서 사용해야함

    #include <opencv2/opencv.hpp>
    
    using namespace cv;
    using namespace std;
    
    int lower_hue = 40, upper_hue = 80;
    Mat src, src_hsv, mask;
    
    //모든 픽셀을 돌면서 해당 픽셀이 설정해둔 범위에 속하는지 0/1로 이진화
    void on_hue_changed(int, void*)
    {
    	//마지막 파라미터를 0으로 해뒀지만 30~50정도로 해놓는 게 좋음
    	//사람 눈에는 명도가 0인건 까매서 구분 못하기 때문
    	Scalar lowerb(lower_hue, 100, 0);
    	Scalar upperb(upper_hue, 255, 255);
    	inRange(src_hsv, lowerb, upperb, mask);
    	imshow("mask", mask);
    }
    
    Mat hsv;
    int hue_changer = 0;
    Mat h, s, v;
    
    void my_add(Mat src, Mat dst, int value)
    {
    	unsigned char* datSrc = src.data;
    	const unsigned char* datSrcEnd = src.dataend;
    	unsigned char* datDst = dst.data;
    
    	for (; datSrc < datSrcEnd; datSrc++, datDst++)
    	{
    		int val = *datSrc + value;
    		if (val >= 180) val -= 180;
    		*datDst = val;
    	}
    }
    
    void on_color(int, void*)
    {
    	//Mat nu_h = (h + hue_changer); 
    	//hue에 일정한 값을 더하면 0+a~180+a로 범위가 변하므로 180을 초과하는 건 앞으로 끌어와야함
    	Mat nu_h = Mat(h.rows, h.cols, CV_8U);
    	//nu_h=hue+일정한 값
    	my_add(h, nu_h, hue_changer);
    	Mat nu_hsv, nu_bgr;
    	//nu_h, s, v
    	merge(vector<Mat> {nu_h, s, v}, nu_hsv);
    	//nu_h, s, v로 만든 hsv이미지를 bgr이미지로 변환
    	cvtColor(nu_hsv, nu_bgr, COLOR_HSV2BGR);
    	imshow("new bgr", nu_bgr);
    }
    
    void ch10_1()
    {
    	src = imread("images/mnm.jpg");
    	/*Mat src32;
    	src.convertTo(src32, )*/
    	cvtColor(src, src_hsv, COLOR_BGR2HSV);
    	vector<Mat> hsvList;
    	split(src_hsv, hsvList);
    	h = hsvList[0];
    	s = hsvList[1];
    	v = hsvList[2];
    	imshow("src", src);
    
    	namedWindow("mask");
    	resizeWindow("mask", src.cols, src.rows);
    	//createTrackbar() : 슬라이드바 생성
    	//Hue의 범위를 설정해서 원하는 색깔을 골라서 볼 수 있는 함수
    	createTrackbar("Lower Hue", "mask", &lower_hue, 179, on_hue_changed);
    	createTrackbar("Upper Hue", "mask", &upper_hue, 179, on_hue_changed);
    	//Hue를 조정하는 함수(컬러를 조정) Hue에 일정한 값을 더하는 것
    	createTrackbar("Hue Changer", "src", &hue_changer, 179, on_color);
    	on_hue_changed(0, 0);
    
    	waitKey(0);
    }
    //위 함수를 사용해서 색을 변환시키면 어색한 부분이 있음 개선한 버전이 10_2()함수
    
    Mat l, a, b;
    int ang = 0;
    
    void on_lab_color(int, void*)
    {
    	Mat nu_a = Mat::zeros(a.size(), CV_8U);
    	Mat nu_b = Mat::zeros(a.size(), CV_8U);
    	unsigned char* datSrcA = a.data;
    	unsigned char* datSrcB = b.data;
    
    	unsigned char* datDstA = nu_a.data;
    	unsigned char* datDstB = nu_b.data;
    
    	const unsigned char* datSrcAEnd = a.dataend;
    
    	for (; datSrcA < datSrcAEnd; datSrcA++, datSrcB++, datDstA++, datDstB++)
    	{
    		float va = *datSrcA - 128;
    		float vb = *datSrcB - 128;
    		float theta = atan2(vb, va);
    		float rho = sqrt(va * va + vb * vb);
    		theta += (float)ang * CV_PI / 180;
    		*datDstA = (rho * cos(theta) + 128);
    		*datDstB = (rho * sin(theta) + 128);
    	}
    
    	Mat nu_lab, nu_bgr;
    	merge(vector<Mat>{l, nu_a, nu_b}, nu_lab);
    	cvtColor(nu_lab, nu_bgr, COLOR_Lab2BGR);
    	imshow("nu_bgr", nu_bgr);
    }
    
    //컬러맵(Lab모델, 사람의 시각이 느끼는 밝기값을 반영)을 사용하여 개선시킨 함수
    void ch10_2()
    {
    	src = imread("images/mnm.jpg");
    	Mat lab;
    	cvtColor(src, lab, COLOR_BGR2Lab);
    	vector<Mat> labList;
    	split(lab, labList);
    	l = labList[0];
    	a = labList[1];
    	b = labList[2];
    	imshow("src", src);
    	createTrackbar("Hue Changer", "src", &ang, 360, on_lab_color);
    	waitKey(0);
    }
    
    void main()
    {
    	//ch10_1();
    	//ch10_2();
    }

    '교육 > 서울 ICT 이노베이션 고오급 시각과정' 카테고리의 다른 글

    Day11  (0) 2021.06.30
    Day10  (0) 2021.06.17
    Day8  (0) 2021.05.14
    Day7  (0) 2021.05.14
    Day6  (0) 2021.05.13

    댓글

Designed by Tistory.