Python(54)- 모폴로지 처리, 레이블링, 테서렉트
*이 글을 읽기전에 작성자 개인의견이 있으니, 다른 블로그와 교차로 읽는것을 권장합니다.*
모폴로지 처리:
영상의 밝은 영역이나 어두운 영역을 축소 또는 확대하는 기법
cv2.getStructuringElement(구조 요소의 모양, 사이즈)
- 구조 요소의 모양
1. 직사각형(cv2.MORPH_RECT)
- 가장 단순한 형태로, 모든 요소가 같은 값을 가지는 정사각형 또는 직사각형
- 팽창과 침식 연산에서 동일하게 작동
- 객체 가장자리를 따라 명확한 변화를 줄 때 유용
2. 타원형(cv2.MORPH_ELLIPSE)
- 가장자리 부분을 더 부드럽게 처리
- 객체의 둥근 모양을 유지하면서 노이즈를 제거할 때 유용
3. 십자형(cv2.MORPH_CROSS)
- 중심을 기준으로 수직 및 수평 방향으로 영향
- 얇은 라인 구조를 강화하거나 제거하는데 유용
침식(erosion) 연산
cv2.erode(영상, 구조요소, 출력영상, 고정점 위치)
- 이미지를 깎아내는 연산
- 객체 크기가 감소되고 배경은 확대
- 작은 크기의 객체(잡음)제거 효과가 있음
- 어두운 부분의 노이즈를 제거하는 효과가 있음
팽창(dilation) 연산
cv2.dilate(영상, 구조요소, 출력영상, 고정점 위치)
- 물체의 주변을 확장하는 연산
- 팽창 연산은 객체 외곽을 확대시키는 연산
- 객체 크기가 증가되고 배경은 감소
- 객체 내부의 홑이 있다면 홑을 채울 수도 있음
- 밝은 부분의 노이즈를 제거하는 효과
import cv2
import numpy as np
img = cv2.imread('./circuit.bmp', cv2.IMREAD_GRAYSCALE)
gse = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
gse = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
dst1 = cv2.dilate(img, gse)
dst2 = cv2.erode(img, gse)
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()
열림(Opening)
- 팽창 연산과 침식 연산의 조합
- 침식 연산을 적용한 다음, 팽창 연산을 적용
- 침식 연산으로 인해 밝은 영역이 줄어들고, 어두운 영역이 늘어남
- 객체 크기 감소를 원래대로 복구할 수 있음
- 작은 객체를 제거하고 구멍을 메우는데 사용
닫힘(closing)
- 팽창 연산과 침식 연산의 조합
- 팽창 연산을 적용한 다음, 침식 연산을 적용
- 어두운 영역이 줄어들고 밝은 영역이 늘어남
- 작은 구멍을 메우고 끊어진 객체를 연결하는 데 사용
그래디언트
- 영상에 팽창 연산을 적용한 결과에서 영상에 침식 연산을 적용한 결과를 뺌
- 경계가 강조된 영상을 생성
- 엣지 검출, 객체의 윤곽 추출, 영상 분할 등에 사용
레이블링
- 이진화, 모폴로지를 수행하면 객체와 배경 영역을 구분할 수 있게됨
- 객체 단위 분석을 통해 각 객체를 분할하여 특징을 분석하고 객체의 위치, 크기 정보, 모양 분석, ROI 추출 등이 가능함
- 서로 연결되어 있는 객체 픽셀에 고유번호를 할당하여 영역 기반 모양분석, 레이블맵, 바운딩 박스, 픽셀 갯수, 무게 중심, 좌표 등을 반환할 수 있게 함
cv2.connectedComponents(영상, 레이블맵)
레이블맵 : 픽셀 연결 관계(4방향 연결, 8방향 연결)
반환 : 객체 갯수, 레이블 맵 행렬
cv2.connectedComponentsWithStats(영상, 레이블맵)
반환 : 객체 갯수, 레이블 맵 행렬, (객체 위치, 가로세로길이, 면적 등 행렬), 무게 중심 정보
import cv2
img = cv2.imread('./keyboard.bmp', cv2.IMREAD_GRAYSCALE)
_, img_bin = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
dst = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cnt, labels, stats, centroids = cv2.connectedComponentsWithStats(img_bin)
print('cnt: ', cnt)
print('labels: ', labels)
print('stats: ', stats)
print('centroids: ', centroids)
for i in range(1, cnt):
(x, y, w, h, area) = stats[i]
if area < 30:
continue
cv2.rectangle(dst, (x, y, w, h), (0, 255, 255)) # garyscale에선 색상 못넣음 ->cvtColor사용 변환
cv2.imshow('img', img)
cv2.imshow('img_bin', img_bin)
cv2.imshow('dst', dst)
cv2.waitKey()
객체의 외곽선 검출
cv2.findContours(영상, 검출모드, 외곽선 좌표 근사화 방법)
검출모드
cv2.RETR_EXTERNAL: 외부 외곽선만 검출
cv2.RETR_LIST: 모든 외곽선을 검출하며, 계층 관계는 무시
cv2.RETR_CCOMP: 모든 외곽선을 검출하며, 계층 관계를 2단계로 구성
cv2.RETR_TREE: 모든 외곽선을 검출하며, 전체 계층 관계를 구성
첫번째 계층: 바깥쪽 윤곽선
두번째 계층: 내부 윤곽선
cv2.RETR_TREE: 모든 외곽선을 검출하며, 전체 계층 관계를 구성
import cv2
import random
img = cv2.imread('./contours.bmp', cv2.IMREAD_GRAYSCALE)
# hierarchy: 계층정보
contours, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
print(contours)
print(hierarchy)
dst = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
cv2.drawContours(dst, contours, -1, color, 3)
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey()
외곽선 좌표 근사화 방법
외곽선 점들의 저장 방식과 정확도를 정의
cv2.CHAIN_APPROX_NONE: 모든 외곽선의 점을 지정
cv2.CAHIN_APPROX_SIMPLE: 수평, 수직, 대각선 방향 점들은 그 점의 끝점만 저장하여 압축
외곽선 그리기
cv2.drawContours(영상, 외곽선 좌표 정보, 외곽선 인덱스, 색상, 두께)
외곽선 인덱스: -1을 지정하면 모든 외곽선을 그림
# milkdrop.bmp 영상을 이용하여 이진화를 시키고, 외곽선을 검출하여 외곽선을 랜덤한 색상으로 표기
import cv2
import numpy as np
import random
img = cv2.imread('./milkdrop.bmp', cv2.IMREAD_GRAYSCALE)
_, img_bin = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
contours, _ = cv2.findContours(img_bin, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
h, w = img.shape[:2]
dst = np.zeros((h, w, 3), np.uint8)
for i in range(len(contours)):
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
cv2.drawContours(dst, contours, i, color, 2)
cv2.imshow('img', img)
cv2.imshow('img_bin', img_bin)
cv2.imshow('dst', dst)
cv2.waitKey()
cv2.THRESH_BINARY_INV: 이진 반전, cv2.THRESH_OTSU: 이 옵션은 이미지의 히스토그램을 분석하여 최적의 임계값을 자동으로 계산, 첫 번째 값 _는 Otsu의 임계값(임계값 T). 코드에서 이 값을 무시하기 위해 _로 처리
cv2.RETR_EXTERNAL: 이미지에서 외부 윤곽선만을 검출, cv2.CHAIN_APPROX_NONE: 윤곽선의 모든 점을 저장, contours: 검출된 윤곽선의 리스트, _: 계층 정보가 반환. 여기서도 계층 정보는 필요하지 않으므로 _로 처리
외곽선의 길이 구하기
cv2.arcLength(외곽선 좌표, 폐곡선 여부)
면적 구하기
cv2.contourArea(외곽선 좌표, False)
바운딩 박스 구하기
cv2.boundingRect(외곽선 좌표)
외곽선 근사화
검출한 외곽선 정보를 분석하여 정점수가 적은 외곽선 또는 다각형으로 표현할 수 있게 만드는 것
cv2.approxPolyDP(외곽선 좌표, 근사화 정밀도 조절, 폐곡선 여부)
근사화 정밀도 조절: 입력 외곽선과 근사화된 외곽선 사이의 최대 길이. 값이 작을수록 다각형이 정확해지고, 꼭지점 수가 늘어남
블록 부분이 있는지 여부
cv2.isContourConvex()
블록 부분이 있으면 True, 없으면 False
블록 외피를 계산
cv2.convexHull()
주어진 점 집합을 둘러싸는 가장 작은 블록 다각형을 반환
import cv2
import math
def setLabel(img, pts, label):
(x, y, w, h) = cv2.boundingRect(pts)
pt1 = (x, y)
pt2 = (x + w, y + h)
cv2.rectangle(img, pt1, pt2, (0, 0, 255), 2)
cv2.putText(img, label, pt1, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255))
img = cv2.imread('./polygon.bmp')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, img_bin = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
contours, _ = cv2.findContours(img_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# print(contours)
for pts in contours:
if cv2.contourArea(pts) < 200:
continue
approx = cv2.approxPolyDP(pts, cv2.arcLength(pts, True) * 0.02, True)
# print(approx)
vtc = len(approx)
print(vtc)
if vtc == 3:
setLabel(img, pts, "TRI")
elif vtc == 4:
setLabel(img, pts, 'RECT')
else:
length = cv2.arcLength(pts, True)
area = cv2.contourArea(pts)
ratio = 4. * math.pi * area / (length * length) # 원인지 여부
if ratio > 0.70:
setLabel(img, pts, 'CIR')
else:
setLabel(img, pts, 'NONAME')
cv2.imshow('img', img)
cv2.imshow('gray', gray)
cv2.imshow('img_bin', img_bin)
cv2.waitKey()
OCR(Optical Character Recognition)
- 광학 문자 인식
- 영상이나 문서에서 텍스트를 자동으로 인식하고 컴퓨터가 이행할 수 있는 텍스트 데이터로 변환하는 프로세스
- Tesseract, EasyOCR, PaddleOCR, CLOVA OCR(네이버 API), Cloud Vision(구글 API) ..
테서렉트
- 오픈 소스 OCR 라이브러리로 구글에서 개발하고 현재는 여러 커뮤니티에 의해 유지보수
- https://github.com/UB-Mannheim/tesseract/wiki 를 다운로드
- tesseract-ocr-w64-setup-5.4.0.20240606.exe (64비트) 를 다운로드 -> Choose Components에서 Additional Script Data(download) 트리를 내림 -> Hangul Script와 hangul Vertical Script 체크, Additional Language Data(download) 트리를 내림 -> korean 체크
윈도우 환경설정
탐색기 -> '내 PC' 마우스 오른쪽 버튼 클릭 '속성' -> 창 최대화 후 우측 메뉴 '고급 시스템 설정' -> '환경 변수' 버튼 클릭 -> '시스템 변수'에서 'path'를 선택하고 '편집' 버튼 클릭 -> '새로 만들기' 버튼 클릭 -> 테서렉트 설치 경로 추가(C:\Program Files\Tesseract-OCR) -> 이후 파이참 재실행
팀과제
Tesseract, EasyOCR, PaddleOCR, CLOVA OCR(네이버 API), Cloud Vision(구글 API) ..
위 라이브러리 또는 API를 사용하여 OCR 프로젝트를 개발
(포스터, 간판, 주민등록증, 운전면허증, 차량번호인식 ..)
import cv2
import pytesseract
img = cv2.imread('./hello.png')
dst = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# lang = 'kor', lang='eng', lang='kor+eng'
text = pytesseract.image_to_string(dst, lang='kor+eng')
print(text)
import numpy as np
ndarr1 = np.array([1, 5, 1, 4, 4])
ndarr2 = np.array([9, 4, 0, 4, 0])
result = np.lexsort((ndarr2, ndarr1))
# ndarr2 배열을 기준으로 오름차순으로 정렬
# 0, 0, 4, 4, 9
# 동일한 값의 경우 ndarr2 배열의 기준으로 ndarr1 오름차순 한 인덱스를 반환
#
print(result)
surnames = ('Hertz', 'Galilei', 'Hertz')
first_names = ('Heinrich', 'Galileo', 'Gustav') # 1 2 0
ind = np.lexsort((first_names, surnames))
print(ind)
a = [1, 5, 1, 4, 3, 4, 4] # First sequence
b = [9, 4, 0, 4, 0, 2, 1] # Second sequence
ind = np.lexsort((b, a)) # Sort by `a`, then by `b`
print(ind)
x = [[1, 2, 3, 4],
[4, 3, 2, 1],
[2, 1, 4, 3]]
y = [[2, 2, 1, 1],
[1, 2, 1, 2],
[1, 1, 2, 1]]
print(np.lexsort((x, y), axis=1))
import cv2
import pytesseract
import numpy as np
def reorderPts(pts):
'''
[[903. 199.]
[179. 200.]
[159. 593.]
[938. 581.]]
[[179 200]]
[[159 593]]
[[938 581]]
[[903 199]]
'''
# print(pts)
# print(pts[:, 1])
# print(pts[:, 0])
idx = np.lexsort((pts[:, 1], pts[:, 0]))
# print(idx)
pts = pts[idx]
# print(pts)
if pts[0, 1] > pts[1, 1]:
pts[[0, 1]] = pts[[1, 0]]
if pts[2, 1] < pts[3, 1]:
pts[[2, 3]] = pts[[3, 2]]
# print(pts)
return pts
img = cv2.imread('./namecard.jpg')
dw, dh = 700, 400
srcQuad = np.array([[0, 0], [0, 0], [0, 0], [0, 0]], np.float32)
dstQuad = np.array([[0, 0], [0, dh], [dw, dh], [dw, 0]], np.float32)
dst = np.zeros((dh, dw), np.uint8)
src_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, src_bin = cv2.threshold(src_gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
contours, _ = cv2.findContours(src_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cpy = img.copy()
for pts in contours:
if cv2.contourArea(pts) < 1000:
continue
approx = cv2.approxPolyDP(pts, cv2.arcLength(pts, True) * 0.02, True)
cv2.polylines(cpy, [approx], True, (0, 255, 0), 2)
# print(approx)
srcQuad = reorderPts(approx.reshape(4, 2).astype(np.float32))
pers = cv2.getPerspectiveTransform(srcQuad, dstQuad)
dst = cv2.warpPerspective(img, pers, (dw, dh))
dst_gray = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)
print(pytesseract.image_to_string(dst_gray, lang='kor+eng'))
cv2.imshow('img', img)
cv2.imshow('cpy', cpy)
cv2.imshow('dst', dst)
cv2.waitKey()