정규화란? 

관계형 데이터베이스의 설계에서 중복을 최소화하게 데이터를 구조화하는 프로세스


* 정규화의 목적 

- 중복성 및 종속성 제거 : 유연성 향상, 데이터 보호, 테이블 규칙성, 테이블 간의 관계 설정 

- 데이터베이스 변경시 이상(anomaly)현상 제거 


1) 삽입 이상

삽입 이상


위와 같은 정규화 되지않은 데이터베이스에서 새로운 교수인 C를 데이터베이스에 삽입하려 할 때 그가 아직 맡은 강의가 없기 때문에 강의 코드를 null로 하지 않는 이상 테이블에 추가 할 수 없음 


2) 삭제 이상

삭제 이상


위와 같은 정규화 되지않은 데이터베이스에서 C 교수의 운영체제 과목이 종료되어서 삭제하려고 할 때 그의 강의가 기록된 레코드를 삭제해야 하는데, 교수 레코드 자체가 사라지게 된다.


3) 갱신 이상

갱신 이상


위와 같은 정규화 되지않은 데이터베이스에서 B 교수의 전화번호가 변경되어 레코드를 수정하려 할 때 성공적인 갱신이 이루어지지 않을 수 있다.



☞ 이러한 문제들을 해결하기 위해 테이블의 구성을 논리적으로 변경하는 것을 정규화라고 한다.


* 정규화 과정

비공식적으로 관계형 데이터베이스 테이블이 제 3 정규화가 되었으면 '정규화 되었다' 라고 한다.


제 1 정규화(1NF; First Normal Form) - 중복되는 그룹 제거


정규화 되지 않은 테이블


위와 같은 테이블은 강의명 칼럼에 두개의 값을 가진다. 위 테이블을 제 1 정규화하면 다음과 같다.


제 1 정규화 된 테이블


제 2 정규화(2NF; Second Normal Form) - 부분 함수 종속성 제거

모든 기본 키의 특정 칼럼에 종속된 칼럼을 제거한다. 수강 과목은 이름 또는 학교에 종속되어 있으므로 이를 제거한다.(새로운 테이블 생성)

다음과 같은 테이블있다고 가정한다.


제 1 정규화 된 테이블


위 테이블은 제 1 정규화가 되었지만 여전히 갱신 이상의 위험이 존재한다.(중복성)

위 테이블의 후보 키는 { 이름 } 이 될 수있다. { 학교 }는 오로지 이름에만 종속된다.

이를 제거하면 다음과 같이 테이블을 분리할 수 있다.


제 2 정규화 된 테이블


제 3 정규화(3NF; Third Normal Form) - 이행 함수 족속성 제거

제 2 정규화로 갱신 이상을 제거했지만 그렇지 않은 테이블도 존재한다.

아래 테이블은 학번은 이름에 종속, 성적은 학번과 이름에 종속, 등급은 성적에 종속됨

이를 이행함수 종속성이라 한다.

제 2 정규화 된 테이블


위의 테이블은 중복되는 테이블이 없고, 함수 족속성도없는 제1, 2 정규화가 된 테이블이다.

하지만 여전히 이상현상이 존재한다. 

만약 100점이 A라는 등급을 주고싶지만 학번과 이름이 기본키가 되는 테이블에서 null값을 삽입 할 수 없다. (삽입 이상) 그리고 B등급의 점수를 70점으로 하향하고 싶다면 해당 성적의 학생을 모두 변경해야함.(갱신 이상) 마지막으로 B 학생의 테이블을 삭제한다면? A등급에 해당하는 점수가 모두 삭제된다.(삭제 이상) 이를 해결하는 방법은 아래와 같다.


제 3 정규화 된 테이블


다음과 같이 테이블을 분리하면 간단하게 해결된다.

이외에도 BCNF, 4NF, 5NF, 6NF 등이 있다.(이는 대부분 3NF이다)


오타 및 질문사항은 댓글로 남겨주세요!





안녕하세요 열코입니다!

OpenCV로 영상처리 및 패턴인식을 공부하는 동안 가장 기본적인 자동차 번호판 인식 프로그램을 

간단하게 제작해 보았습니다.


* 개발 환경

개발 툴 : Visual Studio 2017

개발 언어 : C#


* 기능

- IplImage Load(불러오기) 및 Save(저장)

- Web Image Load 및 Save

- Image GrayScale(흑백)

- Image Binary(이진화)

- Image CannyEdge(에지 검출)

- Templet Match 및 ROI(관심 영역 추출)

- Mouse Drag Event로 ROI

- Tesseract-OCR(API)

- OpenALPR(API)


* 소스 코드

전체 소스코드는 300줄가량 되며, 부분 소스코드만 공개하고 설명합니다.


- Binary 함수


1
2
3
4
5
6
7
8
9
IplImage gray = new IplImage(src.Size, BitDepth.U8, 1); // GrayScale 수행
 
Cv.CvtColor(src, gray, ColorConversion.BgrToGray);
 
IplImage bina = new IplImage(src.Size, BitDepth.U8, 1);
 
int temp = trackBar1.Value * 20// track bar 값을 받아와 적절한 수치로 변환
 
Binarizer.SauvolaFast(gray, bina, temp, 0.264); // Sauvola 방법으로 이진화 수행
cs



- Tesseract 함수


1
2
3
4
5
6
7
8
9
Bitmap img = new Bitmap(pictureBoxIpl2.Image);  
 
var ocr = new TesseractEngine("./tessdata""kor", EngineMode.Default);  
 
// ./tessdata : tesseract 문자 인식 훈련 데이터, kor : 한글 or eng : 영문
 
var texts = ocr.Process(img); 
 
textBox1.AppendText(texts.GetText());
cs




- OpenALPR


1
2
3
4
5
6
7
8
9
10
11
12
13
Task<string> recognizeTask = Task.Run(() => ProcessImage(savePath));
 
recognizeTask.Wait();
 
string task_result = recognizeTask.Result;
 
string result = task_result.Split(':')[11].Split(',')[0];
 
result = result.Replace("\"""");
 
result = result.Trim();  // 결과 값을 적절히 파싱
 
textBox1.AppendText(result);
cs





* 실행 화면

프로그램을 실행하면 아래 사진과 같은 화면이 실행됩니다.



Load Image를 눌러 로컬 컴퓨터 내 이미지를 불러오거나 Capture를 눌러 연결된 카메라로 사진을 불러올 수 있습니다. 먼저 Capture를 눌러 사진을 찍어보겠습니다.




제 책상 위의 아이폰을 한번 찍어봤습니다...

캡처된 이미지를 불러오기 성공했다는 로그가 뜨고 화면에 이미지를 불러옵니다.

이 사진은 딱히 패턴인식할 사진이 아니므로 Load Image로 자동차 사진을 불러오겠습니다.



자동차 이미지를 불러왔습니다.

자동차 번호판을 인식해야 하는데 먼저 전처리 과정을 거쳐야 인식률이 많이 높아집니다.

(사진 선명도 및 노이즈를 제거하기 위해 처리하는 과정입니다.)



- GrayScale은 RGB 컬러사진은 흑백 사진으로 변환해주는 버튼입니다.

- CannyEdge는 사진의 Edge(모서리) 영역만 감지하여 나타내주는 버튼입니다.

- Init은 작업한 사진을 초기화하는 버튼입니다.

- Binary Degree는 Track Bar로 구현했습니다. 수치의 degree( 정도) 값에 의해 Binary(이진화) 처리를 해줍니다.



120 degree로 Binary 처리한 결과입니다.

번호판 부분이 선명하게 처리되었습니다.



마우스 이벤트를 이용하여 마우스 드래그를 하여 마우스 X, Y좌표를 받아온 후 좌표 크기만큼 잘라서 따로 표시해줍니다. (패턴 인식 알고리즘 속도 향상을 위해)



패턴인식 알고리즘은 2가지를 사용했습니다.

Logic 1은 Open Library인 Tesseract 알고리즘을 사용했고,

Logic 2는 OpenALPR API를 사용했습니다.

(OpenALPR은 유료 라이브러리로 2000회 무료로 사용 가능합니다.) 

두 라이브러리 모두 인식률은 상용적으로 사용할 정도로 높진 않았습니다.



하지만 전처리 과정과 알고리즘 수정을 통해 어느 정도 인식률을 높이는 것이 가능합니다.

Logic 1 버튼을 눌러 결과를 확인하니 번호판을 정확히 인식하는 모습입니다.

실행 시간은 두 로직 모두 1~3초 정도 소요합니다. (더 많은 최소화 작업이 필요)  

Templet Match는 미리 저장해 둔 Templet 사진을 통해 기존 사진과 비교하여 번호판을 찾아내는 방법입니다. 번호판 모양이 제각각이기 때문에 인식률이 많이 낮았습니다.



Logic 2(OpenALPR) 실행 결과입니다.

실행 시간은 Logic 1(Tesseract) 조금(0.5초~1초) 더 걸리는 정도였습니다.

OpenALPR 홈페이지에서 데모 프로그램 실행 시 한글 인식이 잘 되는 걸로 확인했는데

소스코드로 구현하니 한글 인식이 안되는 걸로 나옵니다... (아시는 분 댓글로 부탁드립니다)


* 수정

- OpenALPR 한글지원 가능합니다.

PostAsync에서 API주소에 country를 us에서 kr로 변경하면 한글도 인식됩니다.

한글을 unicode로 처리하기 때문에 한글이 \ud638 이런식으로 출력됩니다.

유니코드를 한글로 변경하는 알고리즘을 따로 구현하여 처리하였습니다.


유니코드 한글로 변환하기


또 다른 차량 번호판 인식 결과입니다.



다른 차량 역시 번호판을 잘 인식하는 모습입니다.

위 사진처럼 깨끗하고 깔끔하게 번호판이 나온 경우 따로 전처리 작업을 해주지 않아도 인식하는 모습입니다.


알고리즘 및 전처리 과정을 수정하여 실시간 CCTV에서 번호판 인식이 가능하고, 자율 주행 자동차에서 도로 인식 및 표지판, 신호등을 인식하는 알고리즘을 만들어 볼 수 있겠네요




질문 또는 오타나 잘못된 정보가 있는 경우 댓글로 달아주세요!

공감♡ 버튼을 눌러주시면 더욱 유용하고 좋은 포스팅으로 찾아 뵙겠습니다.







1. 개요

아두이노와 각종 센서를 통해 책상에 오래 앉아 있는 직장인 또는 학생의 편의와 건강을 위한 스마트 책상 만들기 프로젝트입니다.


2. 회로도



회로도가 조금 복잡합니다... USB 모듈에는 USB 선풍기를 연결했습니다.


3. 사용 부품

아두이노 우노 R3

I2C LCD 모듈

적외선 근접센서

USB 인터페이스 변환 모듈

USB 선풍기

3색 LED * 2

온습도 센서

조도 센서

MF, FF 점퍼 선 * 다수


4. 소스코드

#include <LiquidCrystal_I2C.h>

#include <dht11.h> 

#define DHT11PIN 10


LiquidCrystal_I2C lcd(0x27,20,4); // LCD 모듈 구조체 선언

dht11 DHT11; // 온, 습도 모듈 구조체 선언

unsigned long readTime=0; // 착석 시 타이머 작동을 위한 변수

unsigned long readCheckTime=0; // 미착석 여부를 위한 변수

long previousMillis = 0; long interval = 1000; 


void setup() {

    lcd.init(); // LCD 초기화

    lcd.backlight(); // LCD 백라이트 출력

    // 출력 핀모드 설정

    pinMode(5, OUTPUT); pinMode(7, INPUT); // 13 : USB, 7 : 근접센서

    pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); 

    // LED = 2: RED, 3: GREEN, 4 : BLUE

    pinMode(13, OUTPUT); pinMode(6, OUTPUT); pinMode(9, OUTPUT); // 백색 LED

    Serial.begin(9600); // 시리얼 통신 시작(BAUD = 9600)

}

void loop() {

    int chk = DHT11.read(DHT11PIN); // DHT11 라이브러리를 통해 온,습도 데이터를 받아옴

    unsigned long currentMillis = millis(); // 1/1000초 단위로 시간 입력(별개의 루프 사용위함)

    int brightness = analogRead(A0); // 조도 센서 데이터를 아날로그 값으로 입력받음

    int checkP = digitalRead(7); // 근접 선서 데이터를 디지털 값으로 입력받음(0 or 1) 


    // 착석했지만 근접 센서에 체크가 되지 않는경우(잠깐 자리를 고쳐앉는 경우)에 계속 착석 중으로 체크하기 위해

    if(checkP) 

        readCheckTime++; // 근접 센서에 근접한 사물이 없는 경우

    else

        readCheckTime = 0; // 근접 센서에 근접한 사물이 있는 경우


    if(currentMillis - previousMillis > interval) { // interval(1000ms, 1초) 단위로 실행되는 루프(메인 루프와 별개로 작동)

        previousMillis = currentMillis; // 이전시간을 현재시간으로 설정해줌으로써 다음 interval 단위에 체크하기 위함

        if(readCheckTime<50) 

            readTime++; 

        else 

            readTime = 0; // 착석 시 1초 단위로 타이머 변수에 저장

        lcd.clear(); // LCD 화면을 지움(초기화)

    }

    lcd.setCursor(0,0); // 0,0(첫째줄 왼쪽 첫번째) 자리에 출력하기위해 커서를 set

    lcd.print("BG:"); lcd.print(brightness/10); lcd.print("%, "); // 밝기 출력

    lcd.print("TMP:"); lcd.print(DHT11.temperature); lcd.print("'C"); // 온도 출력

    lcd.setCursor(0,1); // 0,1(둘째줄 왼쪽 첫번째) 자리에 출력하기위해 커서를 set

    lcd.print("ST:"); // 착석 여부 확인

    if(readCheckTime<50) 

        lcd.print("O"); 

    else 

        lcd.print("X"); 

    lcd.print(" LED:"); // 착석 여부와 주변 밝기에 따라 LED 작동 여부 확인

    if(readCheckTime<50 && brightness/10 < 40) { // RGB모두 켬(백색)

        lcd.print("O"); 

        digitalWrite(13, LOW); digitalWrite(6, LOW); digitalWrite(9, LOW); 

    }

    else { // 애노드 타입이므로 5,6,9번 핀을 GND로 연결(LOW=HIGH, HIGH=LOW)

        lcd.print("X"); 

        digitalWrite(13, HIGH); digitalWrite(6, HIGH); digitalWrite(9, HIGH); 

    }

    lcd.print(" FAN:"); // FAN 작동 여부

    if(readCheckTime<50 && DHT11.temperature >= 25) { 

        lcd.print("O"); analogWrite(5, 255); 

    } 

    else { 

        lcd.print("X"); digitalWrite(5, LOW); 

    }

    if(readTime<1800) { 

        digitalWrite(3, LOW); digitalWrite(2, HIGH); digitalWrite(4, HIGH); 

    } // 30분이하 : GREEN

    else if(readTime<3600) { 

        digitalWrite(3, HIGH); digitalWrite(2, HIGH); digitalWrite(4, LOW); 

    } // 30분~1시간 : BLUE

    else { 

        digitalWrite(3, HIGH); digitalWrite(2, LOW); digitalWrite(4, HIGH); 

    } // 1시간이상 : RED

}


소스코드 주석에 설명이 있기 때문에 작동 순서만 설명드리겠습니다.

1. 책상 또는 의자 부근에 설치한 근접센서에 사람이 확인되면 작동 시작

2. 온도와 조도를 측정하여 선풍기 및 조명(LED) 작동 여부 확인

3. 근접센서에 일정시간 이상 감지 되지않으면 사람이 없다고 간주 각종 센서 전원 OFF

4. 사람이 착석한 이후 일정시간 마다 초록, 파랑, 빨강 LED 점등


질문 사항은 댓글로 남겨주세요!



to Top