딥러닝을 활용하여 자동차 번호판을 추출하고 인식(OCR)을 테스트 해 보았을 때 

추출은 잘 되지만 인식은 잘 되지 않는 경우가 많았다. 

그래서 어떻게 하면 더 간단하게 이미지를 변형할 수 있을까 생각을 하며 테스트를 해보았다

    

1. 추출된 번호판에 대해서 진행한다 

2. 번호판 사이 간격이 존재한다는 가정 

3. 번호판을 하나의 단어만을 유추 할 수 있도록 쪼개보자 

 

단순 테스트용으로 내용은 부실합니다 .ㅎㅎ 

 

 순서 

1. 이미지를 gray scale로 변환 

2. Blur를 적용하여 불필요한 엣지를 줄임 

3. 엣지를 찾고 

4. 외곽선을 검출하고 

5. 외곽선 결과를 4개의 점으로 받아 넓이가 큰 순서로 정렬

6. 가장큰 외곽선을 엣지추출한 이미지에서 추출

7. Blur와 closing을 통해   이미지에 노이즈를 줄임 

8. x, y 축 각각 좌표별 원소의 합을 구해 정보가 적은 영역을 찾기 

9. 정보가 적은 영역 제거

10. 다시 x,y축 각각 좌표별 원소의 합을 구해서 plot 해보기 

 

사용한 차량 번호는 없는 번호입니다 ~ 

 

1 ~ 2 - 이미지 전처리 

img = cv2.imread("plate1.jpg")
img2 = img.copy()
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.GaussianBlur(img, (5,5),0)

 

3. edge 추출

edge = cv2.Canny(img, 100,100)

4 ~ 5 외곽선 검출 하고 넓이로 정렬

contours, _ = cv2.findContours(edge.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

sorted_contours = sorted(contours, key=cv2.contourArea, reverse=True)

6. 가장 큰 외곽선 선택 하여 해당 영역만 추출

contour = sorted_contours[0]
xmin = contour[:][:,:,0].min()
ymin = contour[:][:,:,1].min()
xmax = contour[:][:,:,0].max()
ymax = contour[:][:,:,1].max()

plate = edge[ymin:ymax, xmin:xmax]

7. 노이즈 제거를 위해 blur 적용 후 closing

plate = cv2.GaussianBlur(plate, (5,5),0)
kernel =  cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
plate = cv2.morphologyEx(plate, cv2.MORPH_CLOSE, kernel)

8. x축, y축 원소의 개수 확인  

h,w = plate.shape
# x축, y축 각각 원소의 개수 카운팅
x_result = np.sum(plate,axis=0) // 255 
x_result = x_result[5:-5]
y_result = np.sum(plate,axis=1) // 255
y_result = y_result[5:-5]

plt.plot(x_result[5:-5] )
plt.plot(y_result[5:-5] )
plt.xlim([0,w])

노란선이 y축 파란선이 x 축

plot을 보면 이미지에서 아무 정보가 없는 부분의 원소의 합이 0에 가깝게 나온다는 것을 확인 할 수 있다. 

그렇다면 처음 ~ 0까지 정보와 맨뒤에서 0이 아닌 원소에 대해서찾는 다면 원하는 번호만 존재하는 영역을 뽑을 수 있겠다 

 

9. 정보가 적은 영역 제거 후 다시 원소의 개수 확인 

# y축에 대해서 값이 너무 작은 영역 제외
se_list = []
condition = y_result < (y_result.max() // 2)
for i, l in enumerate(condition):
    if l == False :
        se_list.append(i)
        break
for i, l in enumerate(condition[::-1]):
    if l == False :
        se_list.append(h - i)
        break

       
# 다시 x축 y축 각각 원소의 개수 확인
plate = plate[se_list[0] : se_list[1]]
x_result = np.sum(plate,axis=0) // 255
y_result = np.sum(plate,axis=1) // 255
plt.plot(x_result[5:-5] )
plt.plot(y_result[5:-5] )
plt.xlim([0,w])
cv2.imshow("qwe", plate)
plt.show()    

그래프에서 노란선은 y축에 대한 원소의 합 파란선은 x축에 대한 원소의 합이다. 

먼저 x 축에 대하여 보면

3의 정보가 있는 영역은 x축 0 ~ 50까지라고 볼 수 있다

8의 정보가 있는 영역은 x축 50 ~ 120까지라고 볼 수 있다 

이런식으로 각 7자리 영역에 대해서 유추를 해볼 수 있다. 

 

워낙 테스트용으로 만들어본 내용이라 실제 사용은 불가능 하다 

1. 외곽선 검출에서 가장 넓은 영역이 번호판이 아닐 수 있다.

2. 차량 번호판은 다양하기 때문에 각 작업에 다른 처리가 들어가야한다. 

3. 번호판이 기울어져 있다면 x 축에대해서 원소의 개수가없는 영역이 줄어들 것이다. 

 

 

(내용 추가)  

floodFill을 활용한 정보 부곽   

h,w = plate.shape
mask = np.zeros((h+2,w+2), np.uint8)
cv2.floodFill(plate, mask, (0,0), 255, 254)
plate = cv2.bitwise_not(plate)

번호판에서 0,0 위치부터 0에 해당하는 값을255로 변경하고 bitwise not으로 색상을 반전시켰다 

 

1. Image 결합 

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <array>

using namespace std;

int main() {
	// cv namespace는 설명을 위해 사용하지 않음 
	cv::Mat img = cv::imread("img1.png");
	cv::Mat gray_img;
	cv::cvtColor(img, gray_img, cv::COLOR_BGR2GRAY); // gray.로 변환 

	cout << gray_img.size() << " channel : " << gray_img.channels() << endl; // 225x225x1
	cout << img.size() << " channel : " << img.channels() << endl; // 225x225x3

	array<cv::Mat, 3> arr = { gray_img, gray_img, gray_img };

	cv::Mat merge_img; 
	cv::merge(arr, merge_img); // ArrayOfArray를 합침 
	
	cout << merge_img.size() << " channel : " << merge_img.channels() << endl; //225x225x3

	cv::Mat concat_img;
	cv::hconcat(merge_img, img, concat_img); // 수평으로 결합 - 차원이 같아야함 
	//cv::vconcat(merge_img, img, concat_img); // 수직으로 결합 - 차원이 같아야함 

	cout << concat_img.size() << " channel : " << concat_img.channels() << endl; // 450x225x3

	cv::imshow("qwe", concat_img); 
	cv::waitKey(0);

	return 0;
}

  • Opencv에서 제공하는 concat 함수를 사용하여 이미지를 수직 또는 수평으로 결합할 수 있다 
    - vconcat, hconcat 
  • 흑백이미지와 color 이미지를 결합하기 위해서는 흑백이미지의 경우 채널이 1이기 때문에 먼저 3채널로 만든 뒤 결합하여야 한다. 그때 cv::merge 함수를 사용하여 생성하였다  
  • merge를 사용할 때 흑백 이미지에 대해서 array를 생성한 후 merge 하였는데, vertor를 사용하지 않고 array를 사용한 이유는 우리가 원하는 차원의 개수(3)를 알고있기 때문에 원소의 개수를 지정할 수 있는 array를 사용했다 

2. 외곽선 검출 (https://docs.opencv.org/3.4/df/d0d/tutorial_find_contours.html

#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <vector>
#include <array>
#include <iostream>

using namespace std; 

cv::Mat gray_img;
const int max_thresh = 255; // threshold 최댓값 
int thresh = 100;
cv::RNG rng(54321); // Random Number Generator - 랜덤값 생성

void thresh_callback(int, void*);

int main() {
	const char* img_path = "milk.jpg";
	cv::Mat img = cv::imread(img_path); // 이미지 읽어오기

	cv::cvtColor(img, gray_img, cv::COLOR_BGR2GRAY); // gray scale로 변환 

	cv::blur(gray_img, gray_img, cv::Size(3, 3)); // 이미지에 블러 적용 3x3 커널사용

	const char* window_name = "origin"; // 보여줄 윈도우 이름 
	cv::namedWindow(window_name);  // 이미지를 보여줄 윈도우 생성 
	cv::imshow(window_name, img); // 이미지 표현 

	cv::createTrackbar("Canny thresh:", window_name, &thresh, max_thresh, thresh_callback); // 이미지에 수평으로 컨트롤 할 수 있는 바 생성 ( 마지막 파라미터는 onChange 함수로 파라미터를 (int, void*) 형태로 가지고 있어야함 
	thresh_callback(0,0); // trackbar에 따라 외곽선 검출 결과를 보여주는 함수 
	cv::waitKey(0);

	return 0;
}

void thresh_callback(int, void*)
{
	cv::Mat canny_output;
	cv::Canny(gray_img, canny_output, thresh, thresh * 2); // Canny 알고리즘을 이용해서 물체의 엣지(edge)를 검출
	vector<vector<cv::Point> > contours; // Point = {int, int} = vector int가 2개 있는 
	vector<cv::Vec4i> hierarchy; // Vec4i = {int, int, int, int} = vector int가 4개있는 

	cv::findContours(canny_output, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE); // binary 이미지에서 외곽선을 찾는다 

	cv::Mat drawing = cv::Mat::zeros(canny_output.size(), CV_8UC3);

	for (size_t i = 0; i < contours.size(); i++)
	{
		cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
		cv::drawContours(drawing, contours, (int)i, color, 2, cv::LINE_8, hierarchy, 0);
	}
	imshow("Contours", drawing);
}
  • opencv docs에서 새로운 함수를 알게되었다 
     createTrackbar - 설정한 윈도우에 설정한 변수의 값을 변경할 수 있는 바가 생성된다 
  • Edge 추출시 blur를 활용하여 불필요한 edge가 덜 나오게 할 수 있다. 
  • cv::findContours - 외곽선을 검출한다 
    • (input image, output image, contours, hierarchy, int mode, int method, cv::Point offset=cv::Points())
    • mode 의 종류 
      cv2::RETR_EXTERNAL : 외곽선중 가장 바깥쪽 선만 리턴한다 
      cv2::RETR_LIST : 모든 외곽선을 찾지만 계층관계는 구성하지 않는다 
      cv2::RETR_CCOMP : 모든 외곽선을 찾으며 2단계의 계층관계를 구성한다 
      cv2::RETR_TREE : 모든 외곽선을 찾으며 모든 계층관계를 구성한다 
      여기서 계층이란 검출된 외곽선 안에 있는 또다른 외곽선을 뜻한다 
    • method 의 종류
      cv2::CHAIN_APPROX_NONE : 모든 point를 저장
      cv2::CHAIN_APPROX_SIMPLE : 4개의 point만을 저장하여 메모리를 절약 
      cv2::CHAIN_APPROX_TC89_L1 : Teh_Chin 연결 근사 알고리즘 L1버전을 적용하여 point 개수를 줄임 
      cv2::CHAIN_APPROX_TC89_KCOS : Teh_Chin 연결 근사 알고리즘 KCOS 버전을 적용하여 point 개수를 줄임 
      return 값으로 오는 contours의 shape를 찍어보면 각각 method마다 shape이 다른것을 볼 수 있다.
  • cv::Scalar 를 이용하여 외곽선을 그릴 때마다 다른 색상을 적용 
  • cv::drawContours
  • (InputOutputArray image,
    InputArrayOfArrays contours,
    int contourIdx,
    const cv:Scalar &color,
    int thickness=1, int lineType=8,
    cv::InputArray hierarchy=noArray(),
    int maxLevel = 2147483657,
    cv::Point offset = cv::Point())

python.을 활용하여 개발을 진행하던 중 Edge device를 사용하면서 문제가 발생했다 

속도가 너무 느리고 메모리가 부족하다는 점이였다. 

그래서 C++을 활용하여 개발을 진행하고자 이전에 python으로 진행했던 opencv 예제를 C++로 변환하여 다시 진행하려한다. 

2021.06.23 - [Machine Learning/Computer Vision] - (1) OpenCV python - 기초 ( imread, imshow, imwrite, cvtColor, resize, waitkey, destroyAllWindows)

 

(1) OpenCV python - 기초 ( imread, imshow, imwrite, cvtColor, resize, waitkey, destroyAllWindows)

CNN 모델에 학습을 시키기 전 하고자 하는 목표에 맞게 이미지를 전처리하여 학습을 시키면 더 좋은 효과를 볼 수 있다. 이미지에서 필요없는 부분이 있는지, 특정 영역만을 추출해도 되는지 개

house-of-e.tistory.com

 

 

1. cv::imread 

string img_path = "asd";
Mat img = imread(img_path, IMREAD_COLOR);
  • 지정된 경로에 이미지를 읽어오는 함수이다 
  • 파라미터 - (const cv::String &filename, int flags =1 ) 
  • 첫번째 파라미터는 파일 이름으로 String형태의 문자열을 입력받는다 
  • 두번째 파라미터는 load할 color의 종류로 cv:: 에 여러가지 종류의 color type이 enum으로 정의되어있다
  • enum  ImreadModes {
    IMREAD_UNCHANGED=-1, 
    IMREAD_GRAYSCALE = 0, 
    IMREAD_COLOR = 1 // 기본값 
    ....
    }

2. cv::imshow

imshow("window_name", img);
  • 설정한 Window name을 가지는 창을 띄운다 
  • 파라미터 - (const cv::String &winname, cv::InputArray mat) 
  • 첫번째 파라미터는 생성할 윈도우의 이름으로 String형태로 문자열을 입력받는다 
  • 두번째 파라미터는 윈도우에 표시할 이미지 데이터로cv::InputArray형태로 입력받는다 

3. cv::waitKey

waitKey(0)
  • 사용자의 키 입력을 설정한 시간만큼 대기한다. 만약 0이면 무한대로 대기한다 
  • 파라미터 - (int delay=0) 
  • 첫번째 파라미터는 대기할 시간을 의미한다 단위는 milliseconds 이다 

4. cv::cvtColor : header -> (opencv2/opencv.hpp)

Mat grayImage;
cvtColor(img, grayImage, COLOR_BGR2GRAY);
Mat HSVImage; 
cvtColor(img, grayImage, COLOR_BGR2HSV);

  • 파라미터 - (cv::InputArray src, cv::OutputArray dst, int code, int dstCn = 0 ) 
  • 첫번째 파라미터는 변경을 하려는 이미지 
  • 두번째 파라미터는 변경한 결과를 저장할 변수 
  • 세번째 파라미터는  변경을 할 색상이다. enum으로 cv::ColorConversionCodes에 작성되어있다 
  • 네번째 파라미터는 결과물의 차원 수이다. 기본값은 0으로 0일시 원본이미지와 코드에서 자동적으로 차원이결정된다  

5. cv::resize 

Mat resizedImg; 
resize(img, resizedImg, Size(640, 640));
  • 파라미터 - (cv::InputArray src, cv::OutputArray dst, cv::Size dsize, double fx =(0.0), double fy = (0.0), int interpolation = 1 ) 
  • 세번째 파라미터는 변경하고자 하는 크기이다. cv::Size형태로 전달해야한다 
  • 네번째 파라미터는 수평(x축)에 적용되는 배율의 값이다 
  • 다섯번째 파라미터는 수직(y축)에 적용되는 배율의 값이다
  • 여섯번째 파라미터는 사용할 보간법의 종류를 설정할 수 있다. enum으로 InterpolationFlags에 정의되어있다

 

C++에서 파일을 다루는 방법에 대해서 작성한다 

  • 파일을 생성하는 방법 - ofstream  /  이미 있으면 삭제 후 다시만듬
  • 파일이 있는지 확인하는 방법 - ifstream.is_open()
#include <fstream>
#include <iostream>

int main() {
	using namespace std; 

	char automobile[50];
	int year; 
	double a_price;
	double b_price;

	ofstream outFile; // 출력을 위한 파일 객체 생성 
	outFile.open("carinfo.txt"); // 파일에 연결 - 이미 파일이 있으면 기존 파일 내용이 사라진다 
	if (outFile.is_open()) {
		cout << "파일 잘 열렸고 ";
	}
	else {
		cout << "파일 잘 안열렸고";
	}
	cout << "자동차 메이커와 차종을 입력하세요 : ";
	cin.getline(automobile, 50); // 파라미터 :  (char *_Str, stream size _Count)
	cout << "연식을 입력하세요 : ";
	cin >> year; 
	cout << "구입 가격을 입력하세요 : "; 
	cin >> a_price; 
	
	b_price = a_price * 0.913; 

	// cout 을 통해서 보기 
	cout << fixed; // std:fixed - 표현 소수점을 고정 소수점으로 변경 
	cout.precision(2); // 고정소수점으로 변경했기 때문에 소수점을 2개까지만 표현 
	cout.setf(ios_base::showpoint); // 끝에 소수점이후 값이 없어도 precision까지 표현 
	cout << "메이커와 차종 : " << automobile << endl;
	cout << "연식 : " << year << endl;
	cout << "구입 가격 : " << a_price << endl;
	cout << "현재 가격 : " << b_price << endl;

	// outFile을 통해 저장 
	outFile << fixed; 
	outFile.precision(2);
	outFile.setf(ios_base::showpoint);
	outFile << "메이커와 차종 : " << automobile << endl;
	outFile << "연식 : " << year << endl;
	outFile << "구입 가격 : " << a_price << endl;
	outFile << "현재 가격 : " << b_price << endl;

	outFile.close();
	return 0;
}
  • 이미 있는 파일을 읽어오는 코드. 
  • getline이라는 string 해더에 있는 맴버함수를 사용하였다. 
  • 파일에서 라인을 읽어올 때 개행문자만을 인식하여 읽어오고 싶었다 
  • ifstream >> buffer의 경우 띄어쓰기를 기준으로도 읽어와서 불편함이 있어 사용하지 않았다 
  • getline 함수는 string, cstring, std 등에 존재하여 수만은 오버로딩이 존재하기 떄문에 찾는데 시간이 걸렸다
#include <fstream>
#include <iostream>
//#include <cstring>
#include <string>
int main() {
	using namespace std; 
	ifstream inFile; 

	inFile.open("carinfo.txt");
	if (!inFile.is_open()) { // 파일을 여는데 실패했다면
		cout << "파일을 열 수 없습니다." << endl;;
	}
	else {
		cout << "파일을 열었습니다." << endl;;
	}
	string buffer; 

	while (inFile.good()) { // 파일의 입력이 양호하고 EOF가 아닌 동안 
		getline(inFile, buffer); // string의 getline 
		cout << buffer << endl;
	}

	return 0;
}

 

문자열을 비교하는 방식은 여러가지가 있다 

 

#include <cctype>
#include <iostream>


int main() {
	using namespace std;
	char ch;
	cin >> ch; 
	// 문자가 알파벳인지를 검증할 대 
	if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
		cout << "ch is alphabet";
	}
	else {
		cout << "ch is not alphabet";
	}
	// cctype 을 이용 
	if (isalpha(ch)) {
		cout << "isalpha? : ch is alphabet";
	}
	else {
		cout << "ch is not alphabet";
	}
	// 다양한 cctype 함수 
	// isalnum() : 알파벳 또는 숫자인지 
	// isalpha() : 알파벳인지 
	// isblank() : 빈칸 또는 수평탭 인지 
	// iscntrl() : 제어문자인지 
	// isdigit() : 십진숫자인지 (0 ~ 9) 
	// isxdigit() : 16진수 숫자인지 (0~9, a~f, A~F)
	// isgraph() : 빈칸이 아닌 인쇄할 수 있는 문자인지 
	// isprint() : 빈칸을 포함하여 인쇄할 수 있는 문자인지
	// islower() : 소문자인지 
	// isupper() : 대문자인지
	// ispunct() : , . ! ? ' " 등의 구두점 문자인지 
	// isspace() : 표준 화이트스페이스 ( 빈칸, 개행, 탭 등) 인지
	// tolower() : 대문자를 소문자로 
	// toupper() : 소문자를 대문자로 
}

 

for 문

	for (int i = 0; i < 5; i++) {
		cout << "for i : " << i << endl;
		for (int j = 0; j < 5; j++)
			cout << "for j : " << j << endl;
	}
	string word = "Hellow ! my name is C++";
	
	for (int i = word.size() - 1; i >= 0; i--) {
		cout << word[i] ;
	}
	int a1[4] = { 1,2,3,4 }; 
	int* pt = a1;
	for (int i = 0; i < 4; i++) {
		cout << a1[i] << endl;
	}
	*++pt; // 포인터를 증가 
	++*pt; // 지시되는 값을 증가 
	(*pt)++; // 지시되는 값 증가 
	*pt++; // 원래 주소를 참고하고 나서 포인터 증가
// 컴마 연산자를 이용하여 하나의 포문에 여러개의 증감, 변수선언을 할 수 있다 
	int i, j; // for문 내부에서 선언하지 않는것을 추천 
	for (i = 0, j = 5; j > i; i++, j--) {
	//for (i = 0, j = 5; j > i; ++i, --j) {
		cout << "i : " << i << " j : " << j << endl; 
	}
// range 기반 for 루프 - built-in array 
	int prices[5] = { 1,2,3,4,5 };
	int i = 0; 
	for (int x : prices) {
		cout << "x는 prices의 " << i << "번째 요소 : " << x << endl;
		i++;
	}
 // range 기반 for 루프- array 
    int i = 0;
	array<int, 5> arr = { 1,2,3,4,5 };
	for (int x : arr) {
		cout << "x는 arr의 " << i << "번째 요소 : " << x << endl;
		i++;
	}

for (;;) 는 무한루프이다

While 문 

// while문 사용 
	int i = 0; 
	const char name[20] = "Hong gil dong";
	while (name[i] != '\0') { // 하나의 문자를 나타낼 때는 작은 따움표, 문자열은 큰따움표
		cout << name[i] << "는 ASCII로 : " << int(name[i]) << endl;
		i++;
	}

if 문

	char t1[5] = "mate";
	if (t1 == "mate") {
		cout << "같다" << endl;
	}
	else {
		cout << "다르다" << endl;
	}
	if (strcmp(t1, "mate") == 0) { // 같으면 0을 반환
		cout << "같다" << endl;
	}
	else {
		cout << "다르다 : " << strcmp(t1, "mate") << endl;
	}
	string t2 = "mate"; // string 클래스는 간단하게 비교가능하다 
	if (t2 == "mate") {
		cout << "같다" << endl; 
	}
	else {
		cout << "다르다" << endl; 
	}

do while 

// do while 사용 
	int i = 0; 
	do {
		cout <<  i << endl;;
		i++;
		
	} while (i < 5);
	return 0;

 

if else문의 경우 ?: 연산자를 사용할 수 있다 

#include <iostream>

int main() {
	using namespace std; 
	int a = 6; 
	int b = 7; 
	int c = a > b ? a : b;
	cout << c; // b가 크기때문에 7 출력 
}

 

Switch

  • 조건결과가 정수일 경우 if else 문과 switch 둘다 사용할 수 있다  
  • 책에서는switch의경우선택사항의 개수가 3개 이상일 경우에 사용하는 것이 좋다고 작성되어 있다 
#include <iostream>

int main() {
	using namespace std; 

	// int user_input;
	char user_input;
	cout << "정수를 입력하세요 : ";
	cin >> user_input;

	switch (user_input) {
	case 1: 
		cout << "1을 입력하셨습니다." << endl;
		break; 
	case 2 : 
		cout << "2를 입력하셨습니다" << endl;
		break;
	case 3 : 
		cout << "3를 입력하셨습니다" << endl;
	case 4 : 
		cout << "4를 입력하셨을수도 있고 3을 입력하셨을 수도 있어요" << endl;
		break;
	case 'a':
		cout << "문자를 입력하셨습니다" << endl;
		break;
	default : 
		cout << "입력한 값이 없습니다." << endl;
			break;
	}

	return 0;
}

 

Vector Template Class

  • Vector template class는 동적 배열에 속하는 string 클래스와 유사하다 
  • 프로그램이 실행되는동안 vector 객체의 크기를 세팅할 수 있고
  • 새로운 데이터를 마지막에 추가하거나 중간에 데이터를 삽입할 수 있다
  • 실제로 vector 클래스는 메모리를 관리하기 위하여 new와 delete를 자동으로 진행한다 
  •  vector 클레스는 <vector> 헤더파일에 있다 
  • vector<typeName> vt(n_elements); 

 

Array Template Class

  • vector는 다소 비효율적인 면이있다 
  • 만약 사용자가 고정된 크기의 배열만 필요하다면 array를 사용하는 것이 더 좋을것이다 
  • 그러나 안전성과 편리성은 다소 줄어들 수 있다 
  • Array객체는 free store 대신에 고정된 크기의 고정 메모리 대입을 사용하여 built-In array와 동일한 수준의 효율성을 지닌다 
  •  array 클래스는 <array> 헤더파일에 있다 
  • array<typeName, n_elements> arr;  n_elements개의 typeName 데이터타입을 가진  array 객체를 생성해라 
#include <iostream>
#include <vector> // STL C++98
#include <array> // C++11


int main() {
	using namespace std; 
	// C, origin C++ 
	int a1[4] = { 1,2,3,4 };
	// C++ 98 STL 
	vector<int> a2(4); 
	a2[0] = 1;
	a2[1] = 2;
	a2[2] = 3;
	a2[3] = 4;

	// C++ 11 
	array<int, 4> a3 = { 1,2,3,4 }; 
	array<int, 4> a4 = a3; 

	cout << "a1[2] : " << a1[2] << " at " << &a1[2] << endl;
	cout << "a2[2] : " << a2[2] << " at " << &a2[2] << endl;
	cout << "a3[2] : " << a3[2] << " at " << &a3[2] << endl;
	cout << "a4[2] : " << a4[2] << " at " << &a4[2] << endl;

	return 0;
}

 

프로그램이 단지 하나의 값만 요구한다면 단순한 변수를 선언하는 것이 간단하다. 

하지만 배열이나 구조체, 문자열 같이 큰 데이터를 다룰 때에는 new를 사용하는 것이 더 효율적이다. 

프로그램이 컴파일시에 메모리를 차지하는 방식을 static binding이라고 하고 new 를 사용하여 프로그램 실행시에 메모리를 할당하는 방식을 dynamic binding이라 한다. 

 

Dynamic array

 

int main() {
	using namespace std;
	// 동적 배열의 선언 (dynamic array)
	// Darray의 값(주소)에는 배열의 첫번째 주소가 들어가있다
	int* Darray = new int[10]; 
	
	Darray[0] = 3;
	Darray[1] = 4;
	Darray[2] = 5;
	Darray[3] = 6;
	Darray[4] = 7;

	cout << "배열의 1번 값은 : " << Darray[1] << endl; 
	cout << "배열의 주소는 : " << Darray << endl; // 00000283BBECA4F0
	// 포인터 변수에 1을 더하면 그 포인터가 지시하는 데이터형의 바이트 수만큼 증가한다 
	Darray = Darray + 1; // 배열 포인터에서 +1 은 다음 원소를 가르키게 된다 
	cout << "이제는 배열의 0 번 값은 : " << Darray[0]; 
	cout << " 배열의 1번 값은 : " << Darray[1] << endl;
	cout << "배열의 주소는 : " << Darray << endl; // 00000283BBECA4F4  int형 배열이기 때문에 4가 증가됐다 

	Darray = Darray - 1; // 다시 시작 위치를 지시한다 
	cout << "배열의 주소는 : " << Darray << endl; // 00000283BBECA4F0

	// 동적 배열의 해제 
	delete [] Darray; // 동적 배열의 경우 반드시 []를 써줘야 배열 전체가 해제된다 


	return 0;
}

 

Dynamic Structure 

 

구조체의 경우 선언된 포인터 변수를 통해 맴버 변수에 접근 할 수 없다. 

포인터 변수는 해당 구조체의 주소값을 의미하기 때문이다 . 

그래서 c++에서는 포인터 변수에서 구조체의 맴버에 접근하기 위해 -> 기호를 사용한다

(*value).member 으로도 접근 가능하다 

int main() {

	struct Mystruct{
		string name;
		int price;
		bool type;
	};

	Mystruct* ps = new Mystruct; 

	ps->price = 1000; 
	ps->name = "12345678901234567890123";
	(*ps).name = "apple";
	ps->type = true; 

	cout << ps->price << endl;
	cout << ps->name<< endl;
	cout << ps->type << endl;

	cout << "*ps의 사이즈 : " << sizeof(*ps);
	cout << " price의 사이즈 : " << sizeof(ps->price);
	cout << " name의 사이즈 : " << sizeof(ps->name);
	cout << " type의 사이즈 : " << sizeof(ps->type);
	delete ps; 

	return 0;
}

 

new를 활용하여 메모리를 절약하는 방법 

new를 사용하지 않으면 선언되는 변수마다 80Byte의 공간이할당되고 프로그램의 종료까지 유지되지만 

다음과 같이 입력받은 값의 크기만큼의 공간을 새로 할당하고 해당 변수의 사용이 끝나면 delete로 반환하는 방식으로 메모리를 절약할 수 있다 

char* getname(void); 

int main() {
	char* name; 
	name = getname(); // name에는 새로운 길이의 배열의 시작 주소가 있음 

	cout << "name의 주소값 : " << (int*)name << endl;
	cout << "name의 값 : " << name << endl;
	delete name; // 메모리 해제 

	name = getname(); // 해제한 메모리를 다시 사용

	cout << "name의 주소값 : " << (int*)name << endl;
	cout << "name의 값 : " << name << endl;
	delete name; // 메모리 해제 

	return 0;
}

char* getname() { // 새 문자열을 가리키는 포인터  
	char temp[80]; // 임시 배열 - 해당 함수가 return하면 해당변수의 메모리는 반환된다 (automatic variable)
	cout << "이름을 입력하세요 : ";
	cin >> temp;
	char* pn = new char[strlen(temp) + 1]; // 배열을 입력한 크기만큼의 새로운 주소의 배열을 생성
	// strcpy 는 secure에 문제가 있어 사용 권장하지 않아서 strcpy_s를 사용
	// strcpy_s 는 두번째 인자로 변경할 사이즈를 정해줘야함 
	strcpy_s(pn, sizeof(*pn)*strlen(temp) + 1, temp);// 새로운 크기의 배열에 기존 temp의 값을 넣음 
	return pn; // 새로운 배열의 주소를 리턴
}

 

automatic storage, static storage, dynamic storage

  • C++에서는 데이터를 저장해 두기 위한 메모리를, 대입하는 방법에 따라  자동 공간(automatic storage), 정적 공간(static storage), 동적 공간(dynamic storage)로 구분한다
  • 정적 공간은 static 변수를 사용하거나 전역변수로 정의할 수 있다. 
  • 함수의 호출에 따라 사라지고 생기는 변수들은 자동공간(stack의 형태로)에 저장된다.
  • 동적 공간은 힙(heap)이라고도 한다
  • 동적 공간은 free store(자유 공간)이라 부르는 메모리 풀(memory pool)을 관리한다. 
  • 메모리 풀은 자동 변수와 정적변수가 사용하는 메모리와 분리되어 있다. 
  • stack의 경우  메모리상에서 데이터들이인접하여 존재하게 된다.
  • new와 delete를 사용하여 dynamic storage에 저장된 데이터를 관리를 해줘야한다 new로 생성한 메모리 주소에 값을 대입하고 delete를 하지않고 새롭게 new로 값을 대입하면 이전에 값은 메모리 어딘가에 떠돌게 되고 프로그램이 종료되지 않는이상 다시는 접근하기 어렵다. 이런것을 memory leak (메모리 누수)라고 한다   

 

+ Recent posts