#04-1. 카메라와 동영상 파일 처리

동영상의 처리

  • 동영상 : 일련의 정지 영상 압축 후 파일로 저장한 형태
  • Frame : 저장되어있는 일련의 정지 영상
  • 작업 순서 : Frame 추출 → 각 Frame에 영상 처리기법 적용
  • 컴퓨터에 연결된 카메라 장치로부터 정지 영상 Frame 받아와서 처리

VideoCapture 클래스

  • 동영상 파일 불러오기
  • VideoCapture::VideoCapture(const String& filename, int apiPreference = CAP_ANY)
    • filename : 동영상 파일 이름 전달 (.avi, .mpg, *.mp4)
    • filename : 카메라 장치의 경우 0보다 같거나 큰 정수 전달
  • isOpened() : 파일 잘 불러왔는지 확인
  • cap.get(…) : 열려있는 동영상 파일로부터 여러가지 정보 받아오기
    • CAP_PROP_FOURCC : codec(압축) 표현하는 정수값
    • CAP_PROP_FPS : 초당 프레임 수 → delay = round( 1000 / fps )
cap = cv.VideoCapture('jnary.mp4')
cap = cv.VideoCapture(0)   # 연결된 카메라 id >= 0
if not cap.isOpened():   # 제대로 파일 불러왔는지 확인
    print("Camera open failed!")
    exit()
print('width: ', int(cap.get(cv.CAP_PROP_FRAME_WIDTH)))
print('height: ', int(cap.get(cv.CAP_PROP_FRAME_HEIGHT)))
  • read() : 카메라 또는 동영상 파일로부터 다음 Frame 받아옴 → 받아왔으면 true, 없으면 false
while True:
    ret, frame = cap.read()
    if not ret:
        break
    inversed = ~frame   # 비트 단위 반전연산
    cv.imshow('frame', frame)
    cv.imshow('inversed', inversed)
    if cv.waitKey(10) == 27:
        break
cv.destroyAllWindows()
  • ret이 false가 되는 경우
    • 동영상을 끝까지 읽었을 때
  • 프레임 간격 계산
    fps = cap.get(cv.CAP_PROP_FPS)
    delay = round(1000 / fps)
    if cv.waitKey(delay) == 27:
    		break

동영상 파일 저장하기

  • VideoWriter 클래스
    • fourcc : codec (cv.VideoWriter_fourcc(*’DIVX’)
  • write() : 프레임 추가 가능 → 단, frame의 크기는 생성할 때 지정했던 크기와 일치해야함
outputVideo = cv.VideoWriter('output.avi', fourcc, fps, (w, h))
w = round(cap.get(cv.CAP_PROP_FRAME_WIDTH))
h = round(cap.get(cv.CAP_PROP_FRAME_HEIGHT))
fps = cap.get(cv.CAP_PROP_FPS)
delay = round(1000 / fps)
fourcc = cv.VideoWriter_fourcc(*'DIVX')
outputVideo = cv.VideoWriter('output.avi', fourcc, fps, (w, h))
if not outputVideo.isOpened():
    print('File open failed')
    exit()

while True:
    ret, frame = cap.read()
    ...
    outputVideo.write(inversed)

다양한 그리기 함수

→ 시험 X, 과제 O

직선 그리기

  • line()
    • void line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color, int thickness = 1, int lineType = LINE_8, int shift = 0);
    • pt1 : 시작점, pt2 : 끝점
  • arrowedLine()
  • drawMarker()

도형 그리기

  • rectangle()
  • circle()
  • ellipse() 타원
  • polylines()

문자열 출력

  • putText()
    • 대상 영상 및 출력할 문자열, 폰트 정보 설정 가능
    • 아직 한글은 출력 불가
  • getTextSize()
    • 문자열 출력을 위해 필요한 사각형 영역 크기 가늠
    • 문자열이 한쪽으로 치우치지 않도록 배치 가능

#04-2. 이벤트 처리

키보드 이벤트 처리

  • int waitKey(int delay = 0);
    • delay : 키 입력을 기다릴 시간(밀리초 단위)

    • delay ≤ 0 : 무한히 기다림

    • 눌러진 키 값 반환

      → 눌러진 키 없으면 -1 반환
      while(true) {
      		if (waitKey() == 27) break;
      		//esc키 == 27번
      }
  • ord(char)
    • 하나의 문자를 인자로 받아 unicode 정수값 반환

      cv.namedWindow('img')   # 이후 imshow에 대한 창 생성
      cv.imshow('img', img)
      while True:
          keycode = cv.waitKey()
          if keycode == ord('i'):
              img = ~img
              cv.imshow('img', img)
          elif keycode == 27 or keycode == ord('q'):
              break
      cv.destroyAllWindows()

마우스 이벤트 처리

  • OpenCV 마우스 이벤트 처리
    • 영상 출력 창에서 발생하는 마우스 이벤트 → 사용자에게 전달
    • 마우스 클릭, 마우스 드래그
    • 마우스 콜백 함수 등록하여 이벤트 처리
  • void setMouseCallBack (const String& winname, MouseCallback onMouse, void* userdata = 0);
    • winname : 마우스 이벤트 처리를 할 창의 이름
    • onMouse : 마우스 이벤트 처리를 위한 콜백 함수 이름 → 4개의 정수형과 하나의 void* 타입
    • userdata : 콜백 함수에 전달할 사용자 데이터의 포인터
  • typedef void (MouseCallback) (int event, int x, int y, int flags, void userdata);
    • event : MouseEventType으로 정의된 열거형 상수 중 하나
    • x, y : 마우스 이벤트가 발생한 위치의 x좌표, y좌표
    • flags : MouseEventFlags 열거형 상수의 논리합 조합 전달
  • ex. 왼쪽 버튼 누른 상태로 마우스 움직이면 궤적에 따라 노란색으로 표시
    def on_mouse(event, x, y, flags, param):
    		global oldx, oldy
    		if event == cv.EVENT_LBUTTONDOWN:
    				oldx, oldy = x, y
    				print('EVENT_LBUTTONDOWN: %d, %d' % (x, y))
    		elif event == cv.EVENT_LBUTTONUP:
    				print('EVENT_LBUTTONUP: %d, %d' % (x, y))
    		elif event == cv.EVENT_MOUSEMOVE:
    				if flags & cv.EVENT_FLAG_LBUTTON:
    						cv.line(img, (oldx, oldy), (x, y), (0, 255, 255), 2)
    						cv.imshow('img', img)
    						oldx, oldy = x, y   # 다음번 마우스 이동을 위한 갱신
    
    img = cv.imread('cat.bmp')
    if img is None:
    		exit()
    cv.namedWindow('jnary')
    # 마우스 이벤트 발생할 때마다 자동적으로 호출될 콜백함수
    cv.setMouseCallback('jnary', on_mouse)
    cv.imshow('jnary', img)
    cv.waitKey()
    cv.destroyAllWindows()

트랙바 사용하기

  • 트랙바 인터페이스

    • 영상 출력창에 부착되어 동작 중에 사용자가 지정된 범위 안의 값 선택 가능
    • Windows, Linux, Mac OS 운영체제에서 공통 사용 가능
    • 필요한 경우 창 하나에 여러 개 트랙바 생성 가능
    • 각각의 트랙바에 고유한 이름 지정 필요 → 왼쪽
    • 트랙바 현재 위치 : 트랙바 이름 옆에 표시
    • 최대 위치 : 트랙바 생성 시 지정 / 최소 위치 : 0 고정
  • int createTrackbar(const String& trackbarname, const String& winname, int value, int count, TrackbarCallback onChange = 0, void userdata = 0);

    • trackbarname : 트랙바 이름
    • winname : 트랙바를 생성할 창 이름
    • value : 트랙바 위치를 받을 정수형 변수의 주소
    • count : 트랙바 최대 위치
    • onChange : 트랙바 위치 변경될때마다 호출되게 만들 콜백함수명
    • userdata : 콜백 함수에 전달할 사용자 데이터의 포인터
    • 반환값 : 정상 동작 1, 실패 0
  • typedef void (TrackbarCallback) (int pos, void userdata);

    • pos : 현재 트랙바의 위치 정보 전달
    • 함수명 상관 X / 인자 목록, 반환형은 고정
    • 트랙바 위치 변경될 때마다 자동 호출
  • ex. 그레이스케일 레벨 16단계로 보여주기

    def on_level_change(pos):
        img[:] = saturated(pos * 16)
        cv.imshow('jnary', img)
    img = np.zeros((400, 400), np.uint8)
    cv.namedWindow('jnary')
    cv.createTrackbar('level', 'jnary', 0, 16, on_level_change)
    cv.imshow('jnary', img)
  • int getTrackbarPos(trackbarname, winname)

    • 트랙바의 현재 위치 알고 싶은 경우
    • 지정한 트랙바의 현재 위치 반환
  • void setTrackbarPos(trackbarname, winname, pos)

    • pos : 트랙바를 이동할 위치

#04-3. OpenCV 데이터 파일 입출력

데이터 파일 입출력

  • 영상데이터 : imwrite() 함수 이용 → BMP, JPG, PNG 등 영상 파일로 저장
  • uchar 자료형이 아닌 다른 자료형의 일반적인 행렬 : 영상 파일 형식으로 저장X → 범용적인 데이터 저장방식으로 저장 : XML, YAML, JSON 등

FileStorage 클래스

  • OpenCV에서 사용하는 데이터 파일 입출력 기능 캡슐화
  • FileStorage 객체 생성 → 데이터 저장 및 읽어오기
  • 생성자에서 두번째 인자 flags : 파일 열기모드 결정
    • FILE_STORAGE_READ
    • FILE_STORAGE_WRITE
    • FILE_STORAGE_APPEND
    • FILE_STORAGE_MEMORY : 자주 업데이트될 경우 메모리 버퍼 이용한 입출력 연산 수행
  • isOpened() : 파일 잘 열렸는지 확인
  • release() : 파일 닫고 메모리 버퍼 해제
  • ex. 데이터 파일 저장하기
    filename = 'mydata.json'
    name = 'Jnary'
    age = 22
    fs = cv.FileStorage(filename, cv.FILE_STORAGE_WRITE)
    if not fs.isOpened():    # 열렸는지 확인
        print('File open failed!')
        exit()
    fs.write('name', name)
    fs.write('age', age)
    fs.release()   # 필수, file write 수행 -> 메모리 버퍼 해제
  • JSON 파일로 저장
    {
    		"name": "Jnary",
    		"age": 21,
    		"point": {
    				"type_id": "opencv-matrix",
    				"rows": 2,
    				"cols": 1,
    				"dt": "d",
    				"data": [100.0, 200.0]
    		},
    		...
    }
  • XML 파일로 저장
    <?xml version="1.0"?>
    <opencv_storage>
    <name>Jnary</name>
    <age>21</age>
    <point type_id="opencv-matrix">
    	<rows>2</rows>
    	...
    </point>
  • ex. 데이터 파일 불러오기
    • XML/YAML/JSON 파일 읽기모드 → 파일 전체 분석 → 계층적 구조 갖는 node 집합 구성

    • Node : 이름, 값으로 구성되어있는 하나의 데이터

    • getNode() : 특정 이름으로 저장되어있는 Node에 접근

      fs = cv.FileStorage('mydata.json', cv.FILE_STORAGE_READ)
      name = fs.getNode('name').string()
      age = int(fs.getNode('age').real())   # real : 수치형 변환
      print('name: ', name)
      print('age: ', age)
      fs.release()

#04-4. 유용한 OpenCV 기능

마스크 연산

  • 임의의 모양을 갖는 ROI(Region-Of-Interest) 설정 목적

  • 입력 영상과 크기 동일, 깊이 = CV_8U인 마스크 영상을 인자로 전달

  • 마스크 영상의 픽셀값 = 0인 좌표에 대해서만 연산 수행

    → 보통 구분이 쉽도록 0~255로 구성된 흑백 영상 사용

  • ex. 일부 영역에 대해서만 픽셀값 노란색으로

    def mask_setTo():
    		src = cv2.imread('lenna.bmp', cv2.IMREAD_COLOR)
    		mask = cv2.imread('mask_smile.bmp', cv2.IMREAD_GRAYSCALE)
    		
    		if src is None or mask is None:
    				return
    
    		src[mask > 0] = (0, 255, 255)
    
    		cv2.imshow('src', src)
    		cv2.imshow('mask', mask)
    		cv2.waitKey()
    		cv2.destroyAllWindows()
  • ex. 마스크 영상에 의해 지정된 일부 영역만 복사하기

    dst = cv2.imread('field.bmp', cv2.IMREAD_COLOR)
    dst[mask > 0] = src[mask > 0]

연산 시간 측정

  • 대부분의 영상 처리 시스템 : 대용량 영상 데이터 → 복잡한 알고리즘 연산 수행
    • 각 단계에서 소요되는 연산 시간 측정 → 오래 걸리는 부분 개선하는 시스템 최적화 작업이 필수적
    • 특히 실시간 연산이 필요한 시스템(머신 비전) : 각 단계의 연산 시간 제대로 측정 → 분석하는 작업이 매우 중요
  • OpenCV : 정밀한 시간 측정 방법 제공
    • OS에 상관없이 통일된 인터페이스 함수 사용
    • TickMeter 클래스 활용 → 특정 연산의 수행 시간 측정
  • TickMeter 클래스
    • 초기화 후 start() 호출 → stop() 호출
      tm = cv.TickMeter()
      tm.start()
      # 측정하고자 하는 연산
      tm.stop()
      print(tm.getTimeMilli()) # getTimeMicro(), getTimeSec()
      tm.reset()   # 0부터 다시 시작할 때
    • getTimeMilli : ms 단위로 시간 측정 +) getTimeMicro(), getTimeSec()도 가능 → 반환형 : double
    • 0부터 다시 시작할 때
      tm.reset()
  • numpy 연산과 이중for문 영상 반전에 걸리는 시간 비교해보기

Numpy의 sum(), mean()

  • Scalar mean(InputArray src, InputArray mask = noArray());
    • mask : mask 부분의 특정 원소 평균 구하기

      sum_img = np.sum(img)
      mean_img = np.mean(img, dtype = np.int32)
      array = np.array([[1,2,3],[4,5,6],[7,8,9]])
      mask = array > 5
      mean_value = np.mean(array[mask])   # 마스크 연산 적용 가능

minMaxLoc() 함수

  • 주어진 행렬의 최솟값, 최댓값 찾는 함수
minVal, maxVak, minPos, maxPos = cv.minMaxLoc(img)

Normalization

  • L1 Norm
    • Manhattan Distance, Taxicab norm
    • |x| + |y|
    • 연산이 빠름, 정밀한 거리 X
  • L2 Norm
    • Euclidean norm
    • sqrt(x^2 + y^2)
    • 연산의 여유가 있을 경우 사용, 거리가 정밀
  • NORM_MINMAX
    • 최솟값 alpha, 최댓값 beta가 되도록 모든 원소값 크기 조절

    • 실수로 구성된 행렬 → 그레이스케일 영상 형태 변환할 때 유용

      src = np.array([[-1, -0.5, 0, 0.5, 1]], dtype=np.float32)
      dst = cv.normalize(src, None, 0, 255, cv.NORM_MINMAX, cv.CV_8U)
      # dst :  [[  0  64 128 191 255]]
profile
숭실대학교 컴퓨터학부 21

0개의 댓글