반응형

2020.04.20 - [영상처리] - Canny Edge Detection (Python)

 

Canny Edge Detection (Python)

import cv2 as cv import numpy as np import math import queue SIGMA = 1.0 LOW_T = 0.25 * 255 HIGH_T = 0.1 * 255 def gauss(y,x,sigma): value = math.exp(-(x**2+y**2)/(2*sigma**2)) return value/(2*math...

clazy-coder.tistory.com

예전에 개발한 Canny Edge Detection을 수정한 버전이다.

코드

 

GitHub - ClazyCoder/PyCV: Computer Vision Library

Computer Vision Library. Contribute to ClazyCoder/PyCV development by creating an account on GitHub.

github.com

수정된 코드는 Convolution 부분을 행렬연산으로 치환하여 더 빠른 속도를 보여준다.

이전의 코드는 Convolution시 영상 내부의 픽셀에 대해서만 연산을 수행했기 때문에, Kernel크기가 커지면 영상 테두리 쪽의 일부 픽셀은 무시되었다.

수정한 코드는 Zero-padding을 Kernel 크기 별로 원본 영상에 추가하여 연산을 수행하므로 모든 픽셀을 Convolution에 포함시킨다.

왼쪽: 원본 이미지, 오른쪽: 결과 이미지 (T_low=10, T_high=30)

반응형

'영상처리' 카테고리의 다른 글

Distance Transform  (0) 2021.04.09
K-MEANS Algorithm On Color Image  (0) 2021.04.09
Canny Edge Detection (Python)  (0) 2020.04.20
Image Rotation(Python)  (0) 2020.03.23
반응형

1. 복사 생성자

복사 생성자는 객체의 복사가 이루어질 때 호출되는 생성자이다.

#include<iostream>
#include<string>

class Person
{
	std::string name;
	int age;
public:
	Person(std::string name, int age)
	{
		this->name = name;
		this->age = age;
	}
	void ShowInfo()
	{
		std::cout << name << "(" << age << "세)" << std::endl;
	}
};

int main()
{
	Person p1("김철수", 123);
	Person p2 = p1;
	Person p3(p1);

	p1.ShowInfo();
	p2.ShowInfo();
	p3.ShowInfo();

	return 0;
}

그림 1. 실행결과.

p1의 값을 복사하여 p2 p3를 초기화하는 소스이다.

실행 결과를 보면 모든 클래스의 멤버들이 같은 값을 가지는 것을 확인할 수 있다.

 

또한 매개변수로 객체가 인자로 전달될 때, 호출된다.

 

그런데, 위 소스를 확인해보면 따로 복사 생성자를 정의해주지 않았음에도 정상적으로 복사가 된 것을 볼 수 있다.

이는 컴파일러가 기본적인 복사 생성자를 생성자처럼 만들어 주기 때문이다. 이렇게 자동적으로 생성된 복사 생성자를 디폴트 복사 생성자라고 한다.

#include<iostream>
#include<string>

class Person
{
	std::string name;
	int age;
public:
	Person(std::string name, int age)
	{
		this->name = name;
		this->age = age;
	}
	Person(const Person& src)
	{
		this->name = src.name;
		this->age = src.age;
	}
	void ShowInfo()
	{
		std::cout << name << "(" << age << "세)" << std::endl;
	}
};

int main()
{
	Person p1("김철수", 123);
	Person p2 = p1;
	Person p3(p1);

	p1.ShowInfo();
	p2.ShowInfo();
	p3.ShowInfo();

	return 0;
}

위의 소스는 복사 생성자를 보여준다.

디폴트 복사 생성자와 여기서 정의한 복사 생성자는 같은 역할을 수행한다.

단순히 각 멤버에 대해 대입 연산을 수행하여 복사를 진행한다.

const 키워드를 매개변수에 붙여준 것은 값을 변경하지 않겠다는 것을 명시적으로 나타낸 것이라 보면 된다.


2. 깊은 복사와 얕은 복사

2.1. 얕은 복사

1의 경우에는 우리가 따로 복사 생성자를 정의할 필요가 없다.

그럼에도 복사 생성자는 재정의 가능하게 되어있는데, 왜 그럴까?

그것은 클래스 내부에서 동적 할당된 공간을 사용할 경우,

복사 과정에서 고려해주어야 할 부분이 생기기 때문이다.

#include <iostream>

class IArray
{
	int size;
	int* arr;
public:
	IArray(int size)
	{
		this->size = size;
		this->arr = new int[size];
	}
	IArray(const IArray& src)
	{
		this->size = src.size;
		this->arr = src.arr;
	}
	~IArray()
	{
		delete[] arr;
	}
	void InitArray()
	{
		std::cout << "Input(" << size << ") : ";
		for (int i = 0; i < size; i++)
			std::cin >> arr[i];
	}
	void ShowArray()
	{
		std::cout << "Array(" << size << ") : ";
		for (int i = 0; i < size; i++)
			std::cout << arr[i] << " ";
		std::cout << "\n";
	}
};

int main()
{
	IArray arr1(5);
	arr1.InitArray();
	arr1.ShowArray();

	IArray arr2 = arr1; // Shallow Copy
	arr2.ShowArray();

	return 0;
}

그림 2. 에러.

위 소스를 실행시키면, 분명 메인 함수의 내용은 전부 실행되었다.

하지만 에러가 발생한다. 왜 갑자기 에러가 발생했을까?

그림 3. 포인터 내부 모습.

 

복사 생성자 호출 후, 단순 대입 연산을 수행하므로,

arr1의 포인터가 가리키고 있던 힙 공간의 주소를 그대로 arr2의 포인터로 전달한다.

 

메인 함수의 작업이 끝나고 나면 스택에 들어간 객체들의 순서상으로

arr2의 소멸자가 먼저 호출되게 되고 다음 그림과 같은 상황이 된다.

그림 4. arr2의 소멸자 호출후 포인터 상황.

이제 arr1의 소멸자가 호출될 시간이다.

그런데 이미 지워버린 공간에 대해 또 해제를 진행하려고 하니 문제가 발생한다.그래서 프로그램 실행 시 에러가 발생했던 것이다.

 

이를 어떻게 해결할 수 있을까?소멸자를 제거하는 것도 에러를 띄우지 않는 방법 중의 하나이긴 하나, 이는 근본적인 해결방법이 아니다.에러는 발생하지 않지만, 우리가 할당한 힙 공간의 해제를 진행하지 않았기 때문에 메모리 누수가 발생한다.

 

또, 위처럼 해제만 진행하는 것이 아닌, arr2의 내용을 수정하는 경우를 생각해보자.

#include <iostream>

class IArray
{
	int size;
	int* arr;
public:
	IArray(int size)
	{
		this->size = size;
		this->arr = new int[size];
	}
	IArray(const IArray& src)
	{
		this->size = src.size;
		this->arr = src.arr;
	}
	/*~IArray()
	{
		delete[] arr;
	}*/
	void InitArray()
	{
		std::cout << "Input(" << size << ") : ";
		for (int i = 0; i < size; i++)
			std::cin >> arr[i];
	}
	void ShowArray()
	{
		std::cout << "Array(" << size << ") : ";
		for (int i = 0; i < size; i++)
			std::cout << arr[i] << " ";
		std::cout << "\n";
	}
};

int main()
{
	IArray arr1(5);
	arr1.InitArray();
	arr1.ShowArray();

	IArray arr2 = arr1; // Shallow Copy
	arr2.ShowArray();
	arr2.InitArray();

	arr1.ShowArray();

	return 0;
}

그림 5. 실행결과.

소멸자를 제거해서 우선 에러를 발생하지 않게 했다고 해보자.

이제 메인 함수에서 arr2의 값을 수정하고 있다.

 

그런데 arr1의 내용을 출력해보면 arr2과 같은 내용으로 변한 것을 확인할 수 있다.

이런 문제점 때문에, 우리는 깊은 복사에 대해 이해할 필요가 있다.


2.2. 깊은 복사

깊은 복사라고 해서 어려운 내용은 없다.

기존 소스에서 복사 생성자 부분만 수정하면 된다.

#include <iostream>

class IArray
{
	int size;
	int* arr;
public:
	IArray(int size)
	{
		this->size = size;
		this->arr = new int[size];
	}
	IArray(const IArray& src)
	{
		this->size = src.size;
		this->arr = new int[size];
		for (int i = 0; i < size; i++)
		{
			this->arr[i] = src.arr[i];
		}
	}
	~IArray()
	{
		delete[] arr;
	}
	void InitArray()
	{
		std::cout << "Input(" << size << ") : ";
		for (int i = 0; i < size; i++)
			std::cin >> arr[i];
	}
	void ShowArray()
	{
		std::cout << "Array(" << size << ") : ";
		for (int i = 0; i < size; i++)
			std::cout << arr[i] << " ";
		std::cout << "\n";
	}
};

int main()
{
	IArray arr1(5);
	arr1.InitArray();
	arr1.ShowArray();

	IArray arr2 = arr1; // Deep Copy
	arr2.ShowArray();
	arr2.InitArray();

	arr1.ShowArray();

	return 0;
}

그림 6. 실행결과.

복사 생성자 부분만 살펴보자.

이번에는 단순 대입 연산을 수행하는 것이 아닌,

동적 할당으로 새로운 공간을 할당하고, 그 공간에 값만 복사시켜서 넣고 있는 것을 확인할 수 있다.

 

실행 결과를 보면, 우선 소멸자로 공간을 해제해도 아무런 문제가 없고,

또한 arr2의 값을 변경했을 때, arr1의 값을 출력시켜도 그대로 유지되는 것을 볼 수 있다.


반응형

'개념정리 > C++' 카테고리의 다른 글

6. Friend 키워드  (0) 2021.04.21
5. 참조자  (0) 2021.04.20
4. 동적할당과 소멸자  (0) 2021.04.19
3. 클래스  (0) 2021.04.18
2. Namespace  (0) 2021.04.17
반응형

1. Friend 키워드

Friend는 영어로 친구라는 의미를 가지듯, 클래스에서 사용할 수 있는 friend 키워드도 비슷한 의미를 지닌다.

#include<iostream>

class B;

class A
{
	int num;
	friend B;
public:
	A(int num)
	{
		this->num = num;
	}
	void Print()
	{
		std::cout << num << std::endl;
	}
};
class B
{
	int num;
public:
	B(int num)
	{
		this->num = num;
	}
	void Print(A& a)
	{
		std::cout << a.num + num << std::endl;
	}
};

int main()
{
	A a(100);
	B b(25);

	b.Print(a);
	return 0;
}

그림 1. 실행결과.

소소를 살펴보면 클래스 A 내에서 friend B라는 선언을 통해 B와 friend 관계에 있음을 명시했다.

이제 B 내부에서 A에 접근할 일이 생기면 A의 어떤 멤버든지 마음대로 접근이 가능해진다.

 

위에 class B를 먼저 선언해주고 나중에 구현하였는데,

이는 클래스 A가 정의된 시점에서 friend B라고 명시하여도 B에 대한 정보가 없어서 에러가 나기 때문이다.

 

friend 선언은 class내에 존재하는 어떤 접근제어 지시자 안에 위치하던 상관없다.


2. friend 함수

friend 키워드를 클래스 전체 이외에 특정 함수에만 한정 지을 수도 있다.

#include<iostream>
#include<string>

class Person;
void ChangeName(std::string, Person&);

class Person
{
	friend void ChangeName(std::string, Person&);
	std::string name;
	int age;
public:
	Person(std::string name, int age)
	{
		this->name = name;
		this->age = age;
	}
	void ShowInfo()
	{
		std::cout << name << "(" << age << "세" << ")" << std::endl;
	}
};

void ChangeName(std::string name, Person& p)
{
	p.name = name;
}

int main()
{
	Person p("김철수", 123);
	p.ShowInfo();

	ChangeName("홍길동", p);
	p.ShowInfo();

	return 0;
}

그림 2. 실행결과.

소스를 보면 ChangeName이라는 함수에 한정 지어 Person클래스 내에서 friend 선언을 한 것을 볼 수 있다.

ChangeName 함수 내에서는 Person클래스의 private 멤버인 name에 접근하고 있는 것을 볼 수 있다.

 

위의 예제는 전역 함수에 friend 키워드를 사용했지만, 다른 클래스의 메서드에도 적용 가능하다.

 

friend 키워드는 클래스의 멤버에 더 쉽게 접근하게 해 주지만,

너무 과도하게 사용하면 정보 은닉성을 해치게 되고, 결과적으로 캡슐화를 망칠 수도 있다.

따라서 너무 과도하게 사용하지 않도록 항상 주의하여야 한다.


 

반응형

'개념정리 > C++' 카테고리의 다른 글

7. 복사 생성자  (0) 2021.04.21
5. 참조자  (0) 2021.04.20
4. 동적할당과 소멸자  (0) 2021.04.19
3. 클래스  (0) 2021.04.18
2. Namespace  (0) 2021.04.17

+ Recent posts