[25일차] OpenCV2

btga·2024년 1월 23일

SKT FLY AI 4기

목록 보기
26/31

1. 화소 처리

1. 화소 점 처리

  • 원 화소의 값, 위치 바탕으로 단일 화소값을 변경하는 기술
  • 포인트 처리(Point Processing)이라고도 함
    -> 다른 화소의 영향을 받지 않고 단순히 화소 점의 값만 변경하기 때문

2. 영상 화소의 접근

  • 영상 처리: 2차원 데이터에 대한 행렬 연산
  • 행렬 원소 접근 방법 코드
import numpy as np

def mat_access1(mat):
    for i in range(mat.shape[0]):
        for j in range(mat.shape[1]):
            k = mat[i, j]
            mat[i,j] = k * 2  
            
def mat_access2(mat):
    for i in range(mat.shape[0]):
        for j in range(mat.shape[1]):
            k = mat.item(i,j)
            mat.itemset((i,j), k*2)
    
    
mat1 = np.arange(10).reshape(2, 5)
mat2 = np.arange(10).reshape(2, 5)

print("원소 처리 전: \n%s\n" % mat1)
mat_access1(mat1)
print("원소 처리 후: \n%s\n" % mat1)

print("원소 처리 전: \n%s\n" % mat2)
mat_access2(mat2)
print("원소 처리 후: \n%s" % mat2)

  • 화소의 밝기 값: 화소를 표현하는 양자화 비트 수가 밝기의 단계 수 결정
  • 명암 대비: 영상 품질 결정하는 중요한 요소
    • 높은 대비: 시각적으로 더 명확히 보임
    • 낮은 대비: 밝기의 차이가 크지 않아, 시각적으로 명확 X

3. 디지털 영상의 산술 연산

  • 덧셈 연산: 화소의 밝기 값에 특정한 상수 값을 더해 화소의 밝기 값 증가시킴

    -> 최댓값인 255를 넘는 값들은 모두 255로 처리
  • 영상 화소 표현 코드
import cv2

image = cv2.imread("C:/Open CV/chap06/images/bright.jpg", cv2.IMREAD_GRAYSCALE)    # 영상 읽기
if image is None: raise Exception("영상 파일 읽기 오류")

# OpenCV 함수 이용
dst1 = cv2.add(image, 100) # 밝게
dst2 = cv2.subtract(image, 100) # 어둡게

# numpy.ndarray 이용
dst3 = image + 100 # 밝게
dst4 = image - 10 # 어둡게


cv2.imshow("original image", image)
cv2.imshow("dst1- bright: OpenCV", dst1)
cv2.imshow("dst2- dark: OpenCV", dst2)
cv2.imshow("dst3- bright: numpy", dst3)
cv2.imshow("dst4- dark: numpy", dst4);
cv2.waitKey(0)
  • 곱셈 연산: 화소의 밝기 값에 특정 상수 값을 곱하여 전체적으로 화소의 밝기 값이 증가하여 더 밝아짐
# 행렬 덧셈, 곱셈 이용한 영상 합성
import numpy as np, cv2

image1 = cv2.imread("C:/Open CV/chap06/images/add1.jpg", cv2.IMREAD_GRAYSCALE)   # 영상 읽기
image2 = cv2.imread("C:/Open CV/chap06/images/add2.jpg", cv2.IMREAD_GRAYSCALE)
if image1 is None or image2 is None: raise Exception("영상 파일 읽기 오류 발생")

# 영상 합성
alpha, beta = 0.6, 0.7 # 곱셈 비율
add_img1 = cv2.add(image1, image2) # 단순 더하기
add_img2 = cv2.add(image1*alpha, image2*beta) # 비율에 따른 더하기
add_img2 = np.clip(add_img2, 0, 255).astype('uint8') # saturation 처리
add_img3 = cv2.addWeighted(image1, alpha, image2, beta, 0) # 두 영상 비율에 따른 더하기

titles = ['image1','image2','add_img1','add_img2','add_img3']
for t in titles: cv2.imshow(t, eval(t)) # 영상 표시
cv2.waitKey(0)

4. 명암 대비

- 대비: 같은 색도 인접한 색의 밝기에 따라 다르게 보임
  • 영상 대비 변경 코드
import numpy as np, cv2

image = cv2.imread("C:/Open CV/chap06/images/contrast.jpg", cv2.IMREAD_GRAYSCALE)  # 영상 읽기
if image is None: raise Exception("영상 파일 읽기 오류 발생")

noimage = np.zeros(image.shape[:2], image.dtype)   # 더미 영상
avg = cv2.mean(image)[0]/2.0

dst1 = cv2.scaleAdd(image, 0.5, noimage) # 명암 대비 감소
dst2 = cv2.scaleAdd(image, 2.0, noimage) # 명암 대비 증가
dst3 = cv2.addWeighted(image, 0.5, noimage, 0, avg) # 명암 대비 감소
dst4 = cv2.addWeighted(image, 2.0, noimage, 0, -avg) # 명암 대비 증가

# 영상 띄우기
cv2.imshow("image", image)
cv2.imshow("dst1 - decrease contrast", dst1)
cv2.imshow("dst2 - increase contrast", dst2)
cv2.imshow("dst3 - decrease contrast using average", dst3)
cv2.imshow("dst4 - increase contrast using average", dst4)

cv2.imwrite("dst.jpg",dst1)
cv2.waitKey(0)

5. 연습문제1. OpenCV함수 중에서 cv2.addWeighted() 함수를 사용해서 두 영상을 합성하는 프로그램을 작성하시오

  • alpha, beta 값을 입력으로 받아야 함
import numpy as np, cv2

image1 = cv2.imread("C:/Open CV/chap06/images/add1.jpg", cv2.IMREAD_GRAYSCALE)   # 영상 읽기
image2 = cv2.imread("C:/Open CV/chap06/images/add2.jpg", cv2.IMREAD_GRAYSCALE)
if image1 is None or image2 is None: raise Exception("영상 파일 읽기 오류 발생")

# 영상 합성
#alpha, beta = 0.6, 0.7
alpha = float(input())
beta = float(input())
add_img1 = cv2.addWeighted(image1, alpha, image2, beta, 0)


cv2.imshow("dst", add_img1)
cv2.waitKey(0)

2. 히스토그램

1. 디지털 영상의 히스토그램

  • 관찰한 데이터의 특징을 한 눈에 알아볼 수 있도록 데이터를 막대그래프 모양으로 나타낸 것
  • 디지털 영상에 대한 많은 정보 제공

2. 산술 연산을 이용한 히스토그램 이동

  • 덧셈 연산: 명도 값 증가 -> 밝게 -> 히스토그램 기둥이 오른쪽으로
  • 뺼셈 연산: 명도 값 감소 -> 어둡게 -> 히스토그램 기둥이 왼쪽으로

3. 히스토그램 스트레칭

  • 명암 대비 향상시키는 연산(= 명암 대비 스트레칭)
  • 히스토그램이 모든 범위의 화소 값을 포함하도록 분포 넓힘
  • 기본 명암 대비 스트레칭: 명암 대비가 낮은 디지털 영상의 품질 향상시키는 기술

    - low: 히스토그램의 최저 명도 값
    - high: 히스토그램의 최고 명도 값

3. 컬러 공간 변환

  • 컬러 공간: 색 표시계의 모든 색들을 색 공간에서 3차원 좌표로 표현한 것
    -> 컬러들과의 관계를 표현하는 논리적인 방법 제공

  • 영상 처리에서의 활용

    • 색상이 다른 객체 분리
    • 전체 영상을 컬러 영역별 분리
    • 전선의 연결 오류 검사 목적
    • 색상 정보를 이용한 특정 물체 검색
  • RGB 컬러 공간: 빛의 삼원색(빨, 초, 파) 사용

  • CMY(K) 컬러 공간: RGB 컬러 공간과 보색 관계, 색의 삼원색(청록, 자홍, 노랑) 사용

  • CMYK 컬러 공간: 순수한 검정색 사용 -> 뛰어난 대비 제공

# CMY(K) 컬러 공간 변환
import numpy as np, cv2

BGR_img = cv2.imread("C:/Open CV/chap06/images/color_model.jpg", cv2.IMREAD_COLOR) # 컬러 영상 읽기
if BGR_img is None: raise Exception("영상 파일 읽기 오류")

white = np.array([255,255,255], np.uint8)
CMY_img = white - BGR_img
Yellow, Magenta, Cyan = cv2.split(CMY_img)


titles = ['BGR_img','CMY_img','Cyan','Magenta','Yellow']
[cv2.imshow(t, eval(t)) for t in titles]
cv2.waitKey(0)

연습문제2. 지난 문제에서 두 개의 트랙바를 추가해서 각 영상의 반영 비율을 조절할 수 있도록 수정하시오.

import numpy as np
import cv2

def on_trackbar_change(val):
    global alpha, beta
    alpha = cv2.getTrackbarPos("Alpha", "dst") / 100
    beta = cv2.getTrackbarPos("Beta", "dst") / 100
    update_blend()

def update_blend():
    global alpha, beta, image1, image2
    add_img = cv2.addWeighted(image1, alpha, image2, beta, 0)
    cv2.imshow("dst", add_img)

# Read images
image1 = cv2.imread("C:/Open CV/chap06/images/add1.jpg", cv2.IMREAD_GRAYSCALE)
image2 = cv2.imread("C:/Open CV/chap06/images/add2.jpg", cv2.IMREAD_GRAYSCALE)
if image1 is None or image2 is None:
    raise Exception("영상 파일 읽기 오류 발생")

# Initial alpha and beta values
alpha = 0.5
beta = 0.5

# Create a window and add trackbars
cv2.namedWindow("dst")

cv2.createTrackbar("Alpha", "dst", int(alpha * 100), 100, on_trackbar_change)
cv2.createTrackbar("Beta", "dst", int(beta * 100), 100, on_trackbar_change)

# Display initial result
update_blend()

# Wait for a key event
cv2.waitKey(0)

<코드 설명>

  • cv2.createTrackbar 함수를 사용하여 "Alpha"와 "Beta"라는 두 개의 트랙바를 생성
  • on_trackbar_change 함수: 트랙바 값이 변경될 때 호출됨
    -> 현재 트랙바 위치를 읽어와서 알파와 베타 값을 업데이트하고, update_blend 함수를 호출하여 이미지를 다시 합성하고 결과 보여줌
  • update_blend 함수: 현재 알파와 베타 값을 사용하여 두 이미지를 가중합성하고, 그 결과를 "dst" 창에 표시

4. 프로젝트: Image To Text(문자 인식)

1. Tesseract 설치

: https://github.com/UB-Mannheim/tesseract/wiki

  • 환경 변수 편집

2. 코드 실습

import cv2
import numpy as np
import pytesseract


TESSERACT_PATH = "C:/Program Files/Tesseract-OCR"  # 테서렉트 설치 경로
imgpath = 'images/2.JPG'  # 이미지 파일 경로
win_name = "Image To Text"  # OpenCV 창 이름
img = cv2.imread(imgpath)  # 이미지 읽어오기

img = cv2.resize(img, dsize=(0,0), fx=0.7, fy=0.7, interpolation=cv2.INTER_AREA) # 크기 조절! 안해주면 밑에 잘림
image = img.copy()
rows, cols = img.shape[:2]
count = 0
rect = np.zeros((4,2), dtype=np.float32)


cv2.imshow(win_name, img)

#마우스 이벤트 처리 함수
def onMouse(event, x, y, flags, param):
    global win_name, image, count, img
    if event == cv2.EVENT_LBUTTONUP:
        if count < 4:
            cv2.circle(image, (x,y), 10, (100,255,0), -1) 
            cv2.imshow(win_name, image)

            rect[count] = [x,y]           
            count+=1
        if count == 4:                      
            sm = rect.sum(axis=1)                 
            diff = np.diff(rect, axis = 1)       

            topLeft = rect[np.argmin(sm)]        
            bottomRight = rect[np.argmax(sm)]     
            topRight = rect[np.argmin(diff)]     
            bottomLeft = rect[np.argmax(diff)]  

            pts1 = np.float32([topLeft, topRight, bottomRight , bottomLeft])

            w1 = abs(bottomRight[0] - bottomLeft[0])    
            w2 = abs(topRight[0] - topLeft[0])          
            h1 = abs(topRight[1] - bottomRight[1])      
            h2 = abs(topLeft[1] - bottomLeft[1])        
            width = max([w1, w2])                      
            height = max([h1, h2])                      
            
            pts2 = np.float32([[0,0], [width-1,0], 
                                [width-1,height-1], [0,height-1]])

            mtrx = cv2.getPerspectiveTransform(pts1, pts2)
            result = cv2.warpPerspective(img, mtrx, (int(width), int(height)))

            image = result
            print(GetOCR())
            count=0
            image = img.copy()
    return 0



#OCR 함수
def GetOCR():
    #이미지 불러오기
    global image
    image = cv2.threshold(cv2.cvtColor(image, cv2.COLOR_BGR2GRAY), 130, 255, cv2.THRESH_BINARY_INV)[1]

    #OCR모델 불러오기
    pytesseract.pytesseract.tesseract_cmd = TESSERACT_PATH


    #OCR모델로 글자 추출
    text = pytesseract.image_to_string(image, lang='kor+eng')
    cv2.imshow("OCR", image)    
    return text


cv2.setMouseCallback(win_name, onMouse)

#cv2.imshow(win_name, img)   #이미지 출력
cv2.waitKey(0)              #입력 대기

<코드 설명>

  • onMouse 함수: 마우스 클릭 이벤트를 처리하는 함수로, 사용자가 이미지에서 4개의 점을 선택할 수 있게 함. 이 점들들 바탕으로 perspective 변환을 수행.
  • Perspective 변환: 사용자가 선택한 4개의 점을 이용하여 perspective 변환 행렬을 계산하고, cv2.warpPerspective 함수를 사용하여 이미지를 변환
  • GetOCR 함수: Tesseract OCR을 사용하여 이미지에서 텍스트를 추출. 이미지를 그레이스케일로 변환하고 이진화 처리를 수행한 후, Tesseract를 이용하여 텍스트를 추출.
  • 마우스 이벤트 및 OCR 결과 출력: cv2.setMouseCallback 함수를 통해 마우스 이벤트를 처리하고, 추출된 텍스트는 GetOCR 함수를 통해 얻어지며, 결과는 콘솔에 출력됨.

5. 회선

1. 화소 영역 처리

  • 해당 입력 화소 뿐만 아니라 그 주위의 화소 값도 함께 고려하는 공간 영역 연
  • 회선 기법(Convolution Technique) 수행
  • 원시 화소와 이웃한 각 화소에 가중치를 곱한 합을 출력화소로 생성

2. 블러링

  • 고주파 성분을 제거하여 영상을 흐리게 하거나 부드럽게 하는 기술
  • 사용하는 가중치의 회선 마스크: 저역 통과 필터(Loss Pass Filter)

3. 샤프닝

  • 블러링과 반대로 디지털 영상에서 상세한 부분을 더 강조하여 표현
  • 저주파 성분 제거하면 샤프닝 효과 얻을 수 있음
  • 사용하는 가중치의 회선 마스크: 고역 통과 필터(High Pass Filter)

4. 공간 영역의 개념과 회선

  • 화소 기반 처리: 화소값 각각에 대해 연산 수행
  • 역 기반 처리: mask라 불리는 규정된 영역을 기반으로 연산 수행

5. 회선 처리의 원리

  • 화소의 영역 처리
    - 디지털 영상 처리 시스템: 선형 시불변 시스템

    • 회선 기법으로 새로운 화소값 생성
    • 선형 시불변 시스템: 선형성을 만족하면서 시간에 따라 변하지 않는 시스템
  • 회선 수행 방법
    - 가중치를 포함한 회선 마스크가 이동(왼쪽 위 -> 오른쪽)하며 수행

  • 회선 마스크 특징
    - 크기: 행, 열 모두 홀수 크기 사용

    • 영상의 평균 밝기는 원 영상과 같게 유지 -> 회선 마스크의 계수 합이 1이 되도록!
    • 경계선 검출에서 사용되는 일부 회선 마스크: 음수의 계수 포함 -> 계수 합이 0이 되도록 설계

6. 블러링

영상에서 화소값이 급격하게 변하는 부분들을 감소시켜 점진적으로 변하게
함으로써 영상이 전체적으로 부드러운 느낌이 나게 하는 기술

import numpy as np, cv2, time

# 회선 수행 함수 - 행렬 처리 방식(속도 면에서 유리)
def filter(image, mask):
    rows, cols = image.shape[:2]
    dst = np.zeros((rows, cols), np.float32)
    xcenter, ycenter = mask.shape[1]//2, mask.shape[0]//2

    for i in range(ycenter, rows - ycenter):
        for j in range(xcenter, cols - xcenter):
            y1, y2 = i - ycenter, i + ycenter + 1
            x1, x2 = i - xcenter, i + xcenter + 1
            roi = image[y1:y2, x1:x2].astype('float32')
            tmp = cv2.multiply(roi, mask)
            dst[i,j] = cv2.sumElems(tmp)[0]

    return dst                                                  # 자료형 변환하여 반환

# 회선 수행 함수 - 화소 직접 근접
def filter2(image, mask):
    rows, cols = image.shape[:2]
    dst = np.zeros((rows, cols), np.float32)
    xcenter, ycenter = mask.shape[1]//2, mask.shape[0]//2

    for i in range(ycenter, rows - ycenter):
        for j in range(xcenter, cols - xcenter):
            sum = 0.0
            for u in range(mask.shape[0]):
                for v in range(mask.shape[1]):
                    y, x = i + u - ycenter, j + v - xcenter
                    sum += image[y,x] * mask[u, v]
                dst[i, j] = sum

    return dst

image = cv2.imread("C:/Open CV/chap07/images/filter_blur.jpg", cv2.IMREAD_GRAYSCALE)  # 영상 읽기
if image is None: raise Exception("영상파일 읽기 오류")

# 블러링 마스크 원소 지정     
data = [ 1/9, 1/9, 1/9,
         1/9, 1/9, 1/9, 
         1/9, 1/9, 1/9 ]


mask = np.array(data, np.float32).reshape(3, 3)
blur1 = filter(image, mask)                                    # 회선 수행 - 화소 직접 접근
blur2 = filter2(image, mask)                                   # 회선 수행

cv2.imshow("image", image)
cv2.imshow("blur1", blur1.astype("uint8"))
cv2.imshow("blur2", cv2.convertScaleAbs(blur2))
cv2.waitKey(0)


7. 샤프닝

  • 출력 화소에서 이웃 화소끼리 차이를 크게 하여 날카로운 느낌이 나도록 만드는 것
  • 영상의 세세한 부분 강조 가능
  • 샤프닝 마스크: 마스크 원소들의 값 차이가 커지도록 구성 & 원소 전체 합이 1이 되도록

  • 블러링과 마스크만 다름!

import numpy as np, cv2
from Common.filters import filter

image = cv2.imread("images/filter_sharpen.jpg", cv2.IMREAD_GRAYSCALE) # 영상 읽기
if image is None: raise Exception("영상파일 읽기 오류")

# 샤프닝 마스크 원소 지정 
data1 = [ 0, -1, 0,
         -1, 5, -1,
          0, -1, 0]
data2 = [[-1, -1, -1],
         [-1, 9, -1],
         [-1, -1, -1]]

mask1 = np.array(data1, np.float32).reshape(3, 3)
mask2 = np.array(data2, np.float32)

sharpen1 = filter(image, mask1)
sharpen2 = filter(image, mask2)
sharpen1 = cv2.convertScaleAbs(sharpen1)
sharpen2 = cv2.convertScaleAbs(sharpen2)

cv2.imshow("image", image)
cv2.imshow("sharpen1", cv2.convertScaleAbs(sharpen1))  # 윈도우 표시 위한 형변환
cv2.imshow("sharpen2", cv2.convertScaleAbs(sharpen2))
cv2.waitKey(0)


8. 에지 검출

1. 에지(edge)

  • 디지털 영상의 밝기가 낮은 값 -> 높은 값 or 높은 값 -> 낮은 값으로 변하는 지점
  • 디지털 영상을 구성하는 객체 간의 경계

2. 미분 이용한 에지 검출

  • 에지가 화소의 밝기 변화율에 관여

  • 1차 미분 이용 or 2차 미분 이용

  • 예시: 자동차 번호판 검색, 도로 차선 검출

3. 1차 미분 마스크

  • 로버츠(Roberts) 마스크: 대각선 방향으로 1, -1 배치

    장점: 크기가 작아서 매우 빠른 속도로 동작
    단점: 돌출된 값 평균 어렵 + 잡음에 민감

import numpy as np, cv2
from  Common.filters import filter

def differential(image, data1, data2):
    mask1 = np.array(data1, np.float32).reshape(3,3)
    mask2 = np.array(data2, np.float32).reshape(3,3)

    dst1 = filter(image, mask1)
    dst2 = filter(image, mask2)
    dst1, dst2 = np.abs(dst1), np.abs(dst2)
    dst = cv2.magnitude(dst1, dst2)

    dst = np.clip(dst, 0, 255).astype('uint8')
    dst1 = np.clip(dst1, 0, 255).astype('uint8')
    dst2= np.clip(dst2, 0, 255).astype('uint8')

    
    return dst, dst1, dst2

image = cv2.imread("images/edge.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")
    
data1 = [-1, 0, 0,
          0, 1, 0,
          0, 0, 0]
data2 = [ 0, 0, -1,
          0, 1, 0,
          0, 0, 0]
dst, dst1, dst2 = differential(image, data1, data2)  		# 회선 수행 및 두 방향의 크기 계산

cv2.imshow("image", image)
cv2.imshow("roberts edge", dst)
cv2.imshow("dst1", dst1)
cv2.imshow("dst2", dst2)
cv2.waitKey(0)
  • 프리윗(Prewitt) 마스크

    • 로버츠 마스크의 단점 보완 위해 고안
    • 수직 마스크: 원소의 배치가 수직 방향으로 구성, 에지의 방향도 수직
    • 수평 마스크: 원소의 배치가 수평 방향으로 구성, 에지의 방향도 수평
    • 장점: 돌출된 값을 비교적 잘 평균화
    • 단점: 대각선보다 수평, 수직에 놓인 에지에 더 민감하게 반응
  • 소벨(Sobel) 마스크

    • 프리윗 마스크와 유사
    • 중심 화소의 처분에 대한 비중을 2배 키움 -> 대각선 방향 에지 검출 가능
    • 장점: 돌출된 값을 비교적 잘 평균화
    • 단점: 대각선 방향에 놓인 에지에 더 민감하게 반응
import numpy as np, cv2

image = cv2.imread("images/edge.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")
    
# OpenCV 제공 소벨 에지 계산
# x축방향 미분 - 수직 마스크, ksize: 커널 크기
dst1 = cv2.Sobel(np.float32(image), cv2.CV_32F, 1, 0, ksize=3)
# y방향 미분 - 수평 마스크

dst2 = cv2.Sobel(np.float32(image), cv2.CV_32F, 0, 1, 3)

dst1 = cv2.convertScaleAbs(dst1)
dst2 = cv2.convertScaleAbs(dst2)


cv2.imshow("edge- sobel edge", image)
cv2.imshow("dst1- vertical_OpenCV", dst1)
cv2.imshow("dst2- horizontal_OpenCV", dst2)
cv2.waitKey(0)

4. 2차 미분 마스크

  • 에지 부분에서 부호가 바뀌는 영교차(Zero Crossing)의 특성 가짐
  • 라플라시안 에지 검출
    • 라플라시안 필터를 적용한 이미지에서 0을 기준으로 양수 -> 음수 or 음수 -> 양수 픽셀 찾아냄
    • cv2.Laplacians(src, ddepth, scale, delta~)
import numpy as np, cv2

image = cv2.imread("images\laplacian.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")

data1 = [[0, 1, 0],
         [1, -4, 1],
         [0, 1, 0]]
data2 = [[-1, -1, -1],
         [-1 ,8, -1],
         [-1, -1, -1]]

mask4 = np.array(data1, np.int16)
mask8 = np.array(data2, np.int16)
# OpenCV 함수 cv2.filter2D() 통한 라플라시안 수행

dst1 = cv2.filter2D(image, cv2.CV_16S, mask4)
dst2 = cv2.filter2D(image, cv2.CV_16S, mask8)
dst3 = cv2.Laplacian(image, cv2.CV_16S, 1)

cv2.imshow("image", image)
cv2.imshow("filter2D 4-direction", cv2.convertScaleAbs(dst1))
cv2.imshow("filter2D 8-direction", cv2.convertScaleAbs(dst2))
cv2.imshow("Laplacian_OpenCV", cv2.convertScaleAbs(dst3))
cv2.waitKey(0)

  • 캐니 에지 검출
    • 잡음은 다른 부분과 경계를 이루는 경우가 많아서 대부분의 에지 검출 방법이 이 잡음들을 에지로 검출
    • 위의 문제를 보안한 방법이 캐니 에지 검출 기법
import numpy as np, cv2

image = cv2.imread("images\canny.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상 파일 읽기 오류")


canny2 = cv2.Canny(image, 100, 150)            # OpenCV 캐니 에지

cv2.imshow("image", image)
cv2.imshow("OpenCV_Canny", canny2)           # OpenCV 캐니 에지
cv2.waitKey(0)


9. 기타 필터링

1. 최댓값 / 최솟값 필터링

  • 입력 영상의 중심 화소에서 마스크로 씌워진 영역의 입력 화소들을 가져와서 최댓값, 최솟값을 출력 화소로 결정하는 방법

  • 최댓값 필터링: 가장 큰 값인 밝은 색들로 출력 화소가 구성
    -> 돌출되는 어두운 값이 제거됨 + 전체적으로 밝은 영상

  • 최솟값 필터링: 가장 작은 값들인 어두운 색들로 출력화소가 구성
    -> 돌출되는 밝은 값들이 제거 + 전체적으로 어두운 여상

import numpy as np, cv2

def minmax_filter(image, ksize, mode):
    rows, cols = image.shape[:2]
    dst = np.zeros((rows,cols), np.uint8)
    center = ksize // 2

    for i in range(center, rows - center):
        for j in range(center, cols - center):
            # 마스크 영역 행렬 처리 방식
            y1, y2 = i - center, i + center + 1
            x1, x2 = j - center, j + center + 1
            mask = image[y1:y2, x1:x2]
            dst[i, j] = cv2.minMaxLoc(mask)[mode]
   
    return dst

image = cv2.imread("images/min_max.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")
    
minfilter_img = minmax_filter(image, 3, 0)
maxfilter_img = minmax_filter(image, 3, 1)

cv2.imshow("image", image)
cv2.imshow("minfilter_img", minfilter_img)
cv2.imshow("maxfilter_img", maxfilter_img)
cv2.waitKey(0)

2. 평균값 필터링

  • 마스크 영역 입력 화소들의 평균을 구하여 출력 화소로 지정하는 방법
  • cv2.blur(src, ksize, anchor, borderType) 이용
  • cv2.boxFilter(src, ddepth, ksize, dst, anchor, normalize, borderType) 이용
import numpy as np, cv2

image = cv2.imread("images/filter_avg.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")

blur_img = cv2.blur(image, (5,5), borderType=cv2.BORDER_CONSTANT)
box_img = cv2.boxFilter(image, ddepth=-1, ksize=(5,5))



cv2.imshow("image", image),
cv2.imshow("blur_img", blur_img)
cv2.imshow("box_img", box_img)
cv2.waitKey(0)

3. 미디언 필터링

  • 마스크 범위 원소 중 중간값을 취하여 출력 화소로 결정하는 방식
  • 마스크 범위 내의 화소값 정렬 필요
import numpy as np, cv2


def salt_pepper_noise(img, n):
    h, w = img.shape[:2]
    x, y = np.random.randint(0, w, n), np.random.randint(0, h, n)
    noise = img.copy()
    for (x,y) in zip(x,y):
        noise[y,x] = 0 if np.random.rand() < 0.5 else 255
   
    return noise

image = cv2.imread("images/median2.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")

noise = salt_pepper_noise(image, 500)
med_img2 = cv2.medianBlur(noise, 3)

cv2.imwrite('chap07/images/noise.jpg', noise)

cv2.imshow("image", image),
cv2.imshow("noise", noise),
cv2.imshow("median - OpenCV", med_img2)
cv2.waitKey(0)

4. 연습 문제. 다음의 그림과 같이 캐니 에지 알고리즘에서 이중 임계값을 트랙바로 만들어서 두 개의 임계값을 조절하여 에지를 검출하도록 프로그램을 작성하시오

import numpy as np
import cv2

def on_trackbar1(pos):
    global low_threshold
    low_threshold = pos
    update_canny()

def on_trackbar2(pos):
    global high_threshold
    high_threshold = pos
    update_canny()

def update_canny():
    canny_result = cv2.Canny(image, low_threshold, high_threshold)
    cv2.imshow("OpenCV_Canny", canny_result)

image = cv2.imread("C:/Open CV/chap07/images/cannay_tset.jpg", cv2.IMREAD_GRAYSCALE)
if image is None:
    raise Exception("영상 파일 읽기 오류")

low_threshold = 100
high_threshold = 150

cv2.namedWindow("image")
cv2.imshow("image", image)

cv2.createTrackbar("Low Threshold", "image", low_threshold, 255, on_trackbar1)
cv2.createTrackbar("High Threshold", "image", high_threshold, 255, on_trackbar2)

update_canny()

cv2.waitKey(0)
cv2.destroyAllWindows()
  • on_trackbar1: 낮은 임계값 업데이트
  • on_trackbar2 함수: 높은 임계값을 업데이트
  • update_canny 함수: 현재의 임계값으로 캐니 에지를 다시 계산하여 업데이트

10. 모폴로지(morphology)

1. 영상 처리에서 형태학

  • 영상의 객체들의 형태를 분석, 처리하는 기법
  • 영상의 경계, 골격, 블록 등의 형태를 표현하는데 필요한 요소 추출
  • 불필요한 잡음 제거 or 객체 뚜렷하게

2. 침식 연산

  • 객체 크기 축소 및 배경 확장
  • 영상 내에 존재하는 잡음 같은 작은 크기의 객체 제거 가능
  • 소금-후추 잡음과 같은 임펄스 잡음 제거
import numpy as np, cv2

def erode(img, mask=None):
    dst = np.zeros(img.shape, np.uint8)
    if mask is None: mask = np.ones((3, 3), np.uint8)
    ycenter, xcenter = np.divmod(mask.shape[:2], 2)[0]

    mcnt = cv2.countNonZero(mask) #0이 아닌 요소의 개수를 반환
    for i in range(ycenter, img.shape[0] - ycenter):           # 입력 행렬 반복 순회
        for j in range(xcenter, img.shape[1] - xcenter):
            y1, y2 = i - ycenter, i + ycenter + 1              # 마스크 높이 범위
            x1, x2 = j - xcenter, j + xcenter + 1              # 마스크 너비 범위
            roi = img[y1:y2, x1:x2]                            # 마스크 영역
            temp = cv2.bitwise_and(roi, mask)
            cnt  =  cv2.countNonZero(temp)                     # 일치한 화소수 계산
            dst[i, j] = 255 if (cnt == mcnt) else 0            # 출력 화소에 저장
    return dst

image = cv2.imread("C:/Open CV/chap07/images/morph.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")

data = [0, 1, 0,                                               # 마스크 선언 및 초기화
        1, 1, 1,
        0, 1, 0]
mask = np.array(data, np.uint8).reshape(3, 3)
th_img = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY)[1]  # 영상 이진화

dst1 = erode(th_img, mask)                                     # 사용자 정의 침식 함수
dst2 = cv2.erode(th_img, mask)
# dst2 = cv2.morphologyEx(th_img, cv2.MORPH_ERODE, mask)         # OpenCV의 침식 함수

cv2.imshow("image", image)
cv2.imshow("binary image", th_img)
cv2.imshow("User erode", dst1)
cv2.imshow("OpenCV erode", dst2)
cv2.waitKey(0)

3. 팽창 연산

  • 객체의 최외곽 화소를 확장시키는 기능 -> 객체 크기 확대 + 배경 축소
  • 객체 내부의 빈 공간도 메워짐 -> 객체 내부 잡음 제거
import numpy as np, cv2

def dilate(img, mask):
    dst = np.zeros(img.shape, np.uint8)
    if mask is None: mask = np.ones((3, 3), np.uint8)
    ycenter, xcenter = np.divmod(mask.shape[:2], 2)[0]

    for i in range(ycenter, img.shape[0] - ycenter):    # 입력 행렬 반복 순회
        for j in range(xcenter, img.shape[1] - xcenter):
            y1, y2 = i - ycenter, i + ycenter + 1       # 마스크 높이 범위
            x1, x2 = j - xcenter, j + xcenter + 1       # 마스크 너비 범위
            roi = img[y1:y2, x1:x2]                     # 마스크 영역
            temp = cv2.bitwise_and(roi, mask)
            cnt  = cv2.countNonZero(temp)
            dst[i, j] = 0 if (cnt == 0) else 255  # 출력 화소에 저장
    return dst

image = cv2.imread("C:/Open CV/chap07/images/morph.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")

mask = np.array([[0, 1, 0],                         # 마스크 초기화
                 [1, 1, 1],
                 [0, 1, 0]]).astype("uint8")
th_img = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY)[1]  # 영상 이진화
dst1 = dilate(th_img, mask)                              # 사용자 정의 팽창 함수
dst2 = cv2.morphologyEx(th_img, cv2.MORPH_DILATE, mask)  # OpenCV의 팽창 함수
# dst2 = cv2.dilate(th_img, mask)

cv2.imshow("User dilate", dst1)
cv2.imshow("OpenCV dilate", dst2)
cv2.waitKey(0)

4. 연습 문제. median2.jpg를 이용하여 소금-후추 노이즈를 만들고, 침식과 팽창 연산을 적용해 보라.

import numpy as np
import cv2

def salt_pepper_noise(img, n):
    h, w = img.shape[:2]
    x, y = np.random.randint(0, w, n), np.random.randint(0, h, n)
    noise = img.copy()
    for (x, y) in zip(x, y):
        noise[y, x] = 0 if np.random.rand() < 0.5 else 255
    return noise

image = cv2.imread("C:/Open CV/chap07/images/median2.jpg", cv2.IMREAD_GRAYSCALE)
if image is None:
    raise Exception("영상파일 읽기 오류")

# 소금-후추 노이즈 추가
noise = salt_pepper_noise(image, 500)

# 중간값 필터 적용
med_img2 = cv2.medianBlur(noise, 3)

# 침식 연산 적용
kernel = np.ones((3, 3), np.uint8)
erosion_result = cv2.erode(noise, kernel, iterations=1)

# 팽창 연산 적용
dilation_result = cv2.dilate(noise, kernel, iterations=1)

# 결과 이미지 저장
cv2.imwrite('images/noise.jpg', noise)
cv2.imwrite('images/erosion.jpg', erosion_result)
cv2.imwrite('images/dilation.jpg', dilation_result)

# 이미지 출력
cv2.imshow("Original Image", image)
cv2.imshow("Salt-and-Pepper Noise", noise)
cv2.imshow("Median Filter - OpenCV", med_img2)
cv2.imshow("Erosion", erosion_result)
cv2.imshow("Dilation", dilation_result)
cv2.waitKey(0)
cv2.destroyAllWindows()

5. 열림 연산과 닫힘 연산

  • 열림 연산: 침식 연산 -> 팽창 연산
    - 침식 연산으로 객체 축소 + 배경 부분의 미세한 잡음 제거
    • 팽창 연산으로 축소되었던 객체들이 다시 원래 크기로
  • 닫힘 연산: 팽창 연산 -> 침식 연산
    - 팽창 연산으로 객체가 확장되어서 객체 내부의 빈 공간이 메워짐
    • 침식 연산으로 확장되었던 객체 크기가 원래대로 축소
import numpy as np, cv2
from Common.filters import erode, dilate

def opening(img, mask):                     # 열림 연산
    tmp = erode(img, mask)                  # 침식
    dst = dilate(tmp, mask)                 # 팽창
    return dst

def closing(img, mask):                     # 닫힘 연산
    tmp = dilate(img, mask)
    dst = erode(tmp, mask)
    return dst

image = cv2.imread("C:/Open CV/chap07/images/morph.jpg", cv2.IMREAD_GRAYSCALE)
if image is None: raise Exception("영상파일 읽기 오류")

mask = np.array([[0, 1, 0],                 # 마스크 초기화
                 [1, 1, 1], 
                 [0, 1, 0]]).astype("uint8")
th_img = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY)[1]   # 영상 이진화

dst1 = opening(th_img, mask)                            # 사용자 정의 열림 함수 호출
dst2 = closing(th_img, mask)                            # 사용자 정의 닫힘 함수 호출
dst3 = cv2.morphologyEx(th_img, cv2.MORPH_OPEN, mask)   # OpenCV의 열림 함수
dst4 = cv2.morphologyEx(th_img, cv2.MORPH_CLOSE, mask, iterations = 1)  # OpenCV의 닫힘 함수

cv2.imshow("User opening", dst1);       cv2.imshow("User closing", dst2)
cv2.imshow("OpenCV opening", dst3);     cv2.imshow("OpenCV closing", dst4)
cv2.waitKey(0)

0개의 댓글