해당 포스팅은 Mastering OpenCV 4 with Python 원서를 바탕으로 작성했습니다. 원서를 옮기는 과정에서 부자연스러운 부분이 있을 수 있습니다. 잘못 작성되거나 어색한 부분에 대해서 알려주시면 감사하겠습니다!
이번 포스팅에서는 다음의 내용들 중 2개의 내용을 포함하고 있습니다. 아래의 2개에 대해서는 추가적으로 작성하여 올리도록 하겠습니다.
- 파일과 이미지를 다루기 위한 이론적 소개
- 이미지 불러오고 저장하기
- 카메라 프레임과 비디오 파일 불러오기
- 비디오 파일 저장하기
1. 파일과 이미지를 다루기 위한 이론적 소개
컴퓨터 비전 프로젝트에서는 위의 그림처럼 다양한 종류의 입력 파일들을 다뤄야 합니다. 또한 processing을 진행한 뒤에 다양한 파일들을 결과를 얻을 수 있습니다. 그래서 이번 절에서는 어떻게 이 과정이 진행되는지와 필수적인 요소들을 어떻게 다뤄야 하는지에 대해서 알아봅니다. 프로그램을 실행하기 위한 기본적이고 필요한 단계는 command-line arguments에 적절하게 다루는 것입니다. command-line arguments들은 매개변수화된 정보들을 포함하고 있는 스크립트나 프로그램에 주어집니다. 예를 들어, 숫자 2개를 더하는 스크립트를 쓴다고 한다면, 일반적으로 더하기를 수행하기 위해 필요한 숫자인 2개의 인수를 사용합니다. 컴퓨터 비전 프로젝트에서는 다양한 타입의 파일들과 이미지들이 명령어 인수로 스크립트에 전달됩니다.
1) sys.argv
command-line arguments들을 다루기 위해서 Python의 sys.argv를 사용할 수 있습니다. 프로그램을 실행할 때, 파이썬은 command-line으로부터 모든 변수들을 sys.argv 리스트에 저장합니다. sys.argv 리스트는 0번째 인덱스에는 스크립트의 전체 경로(또는 파일명)를 포함하고 있고, sys.argv의 1번째 인덱스에는 command-line에서 입력받은 param 1, 2번째 인덱스에는 command-line에서 입력받은 param 2를 저장합니다.
# 필요한 패키지들을 불러오기
import sys
# sys.argv[0]에 담겨있는 정보를 확인
print(f"The name of the script being processed is:{sys.argv[0]}")
# sys.argv에 포함된 내용의 갯수 확인
print(f"The number of arguments of the script is:{len(sys.argv)}")
# 리스트 내용 확인
print(f"The arugments of the script are: {sys.argv}")
먼저, 위에 작성한 스크립트를 sysargv_python.py 파일로 저장한 뒤에 cmd에서 해당 파일을 <python sysargv_python.py> 로 실행하게 되면, 아래와 같은 결과를 확인하실 수 있습니다.
그렇다면 이번에는 param값을 추가해서 실행을 해봅시다. param을 추가하기 위해서는 <python sysargv_python.py param1 param2> 로 실행하실 수 있습니다.
위에 나온 결과처럼 뒤에 param를 추가하니, sys.argv에 포함된 것을 확인할 수 있습니다. 그러면 이걸 어떻게 활용할 수 있을까요? 간단하게 두 개의 파라미터를 받아서 더하는 스크립트를 작성해보겠습니다.
# 필요한 패키지를 불러옵니다.
import sys
# sys.argv의 길이가 3이 아니면, 종료합니다.
if len(sys.argv) != 3:
print("You must input 2 param")
exit()
# 1번, 2번에 들어온 값을 int로 변환하고 안되면 예외처리로 반환
try:
int(sys.argv[1])
int(sys.argv[2])
result = int(sys.argv[1]) + int(sys.argv[2])
print(result)
except:
print("You must input 2 integers")
간단하게 sysargv_add.py파일로 작성해봤습니다. 이 파일을 활용해서 다양하게 시도해보면서 아래와 같은 결과를 얻었습니다.
위의 결과처럼 정확하게 2개의 정수값을 넣어줬을 때만 작동하도록 했습니다. 이렇게 했을 때, 재사용성이 높아진다는 장점이 있습니다. 하지만, 많은 변수가 입력되었을 때에는 리스트의 인덱스로 값을 접근하기 때문에 관리하기 어렵다는 단점이 존재합니다. 이를 해결하는 방법으로 argparse라는 라이브러리를 활용할 수 있습니다.
2) Argparse
argparse 라이브러리는 command-line으로 parameter를 주는 측면에서 sys.argv와 비슷합니다. 대신 변수명을 정할 수 있다는 점이 다릅니다. 추가적으로 도움말과 사용 메세지를 생성하고 잘못된 인수가 제공될 때 오류를 발생시키는 기능도 존재합니다. 간단한 예제를 통해서 사용방법에 대해서 알아보도록 하겠습니다.
아래의 코드를 포함하는 argparse_example.py를 만들어 보겠습니다. 전체코드를 접은글에 숨겨놓았으니, 돌려보시려면 전체코드를 확인해주세요. 본문 내용에서는 필요한 부분만 작성되어 있으므로, 그 부분만 사용해서는 작동하지 않을 수 있습니다.
# 필요한 라이브러리 argparse를 불러옵니다.
import argparse
# 먼저, ArgumentParser object를 생성합니다.
parser = argparse.ArgumentParser()
# argument 생성
parser.add_argument("first_argument", help="this is the string text in connection with first_argument")
# int형 argument 생성
parser.add_argument("first_number", help="first number to be added", type=int)
parser.add_argument("second_number", help="second number to be added", type=int)
# 프로그램 arguments들에 대한 정보는 parser에 저장하고, parse_args()로 불러올 수 있습니다.
args = parser.parse_args()
# parser의 argument들을 딕셔너리 형태로 변환
args_dict = vars(parser.parse_args())
# 첫번째 argument 프린트
print(args.first_argument)
# args를 확인
print(f"args: {args}")
# 합을 계산
print(f"The sum is: {args.first_number + args.second_number}")
# 딕셔너리 형태 확인
print(f"args_dict dictionary: {args_dict}")
# 필요한 라이브러리 argparse를 불러옵니다.
import argparse
# 먼저, ArgumentParser object를 생성합니다.
parser = argparse.ArgumentParser()
# 프로그램 arguments들에 대한 정보는 parser에 저장하고, parse_args()로 불러올 수 있습니다.
parser.parse_args()
이렇게 만들어진 코드는 아무런 argument를 보유하고 있지 않아 실행해도 아무것도 보여지지 않습니다. 그렇지만 --help나 -h를 뒤에 붙여서 실행하면 다음과 같은 결과를 얻을 수 있습니다.
이 기능은 지금은 별거 없을 수 있겠지만 다양한 argument들이 추가 되면 효과적입니다. 그러면 이제 argument를 추가해봅시다.
# argument 생성
parser.add_argument("first_argument", help="this is the string text in connection with first_argument")
# args에 parser가 가지고 있는 argument들을 넘겨줍니다.
args = parser.parse_args()
# 첫번째 argument 프린트
print(args.first_argument)
parser.add_argument라는 명령어를 통해서 원하는 접근하고 싶은 이름을 설정하고, 도움말을 작성할 수 있습니다. 그러면 이 명령어를 실행했을 때, 어떤 결과를 얻게 될까요?
위의 결과처럼 argparse_example.py OpenCV를 넣어주니 args.first_argument에 OpenCV가 들어가는 것을 확인할 수 있었습니다. 또한 -h를 붙여서 실행했더니 add_argument에 넣어준 변수명과 help 메세지가 추가된 것을 확인할 수 있습니다. 기본적으로 type을 지정해주지 않으면 string이라고 인식하기 때문에 인수의 형태를 지정하고 싶다면, type으로 지정해줄 수 있습니다.
# int형 argument 생성
parser.add_argument("first_number", help="first number to be added", type=int)
parser.add_argument("second_number", help="second number to be added", type=int)
# args에 parser가 가지고 있는 argument들을 넘겨줍니다.
args = parser.parse_args()
# args를 확인
print(f"args: {args}")
# 합을 계산
print(f"The sum is: {args.first_number + args.second_number}")
위의 코드를 실행해서 first_argument에는 OpenCV를 다음 숫자에는 3, 5를 입력한 결과입니다.
-h를 해보면, 위의 실행 결과처럼 first_number, second_number에 대한 변수명, 도움말이 추가된 것을 확인할 수 있습니다. 이번에는 vars()를 통해 dictionary형태로 변환하는 명령어를 추가해보겠습니다.
# args_dict 생성
args_dict = vars(parser.parse_args())
# args_dict 확인
print(f"args_dict dictionary: {args_dict}")
parser.parse_args()를 vars()안에 넣어주시거나, 위에서 args = parser.parse_args()를 하셨다면, args를 포함시켜도 됩니다.
vars() 함수 기능은 보시는 것처럼 add_argument를 실행할 때의 이름을 key값으로 하고, 우리가 입력해준 argument를 value값으로 하는 dictionary가 생성됩니다. 이렇게 vars()로 생성하게 되면, args.first_argument처럼 args_dict["first_argument"]로도 값에 접근할 수 있습니다.
2. 이미지를 불러오고 저장하기
1) 불러오기
위에서 배웠던 argparse를 활용해서 image를 command-line arguments를 통해 불러오고 저장해봅시다. 저는 argparse_load_image.py라는 이름으로 아래의 코드를 작성했습니다.
# import packages
import argparse
import cv2
# parser 객체 생성
parser = argparse.ArgumentParser()
# path_image argument 추가
parser.add_argument("path_image", help="path to input image to be displayed")
args = parser.parse_args()
# args.path_image로 접근해서 이미지 불러오기
image = cv2.imread(args.path_image)
image = cv2.resize(image, (600, 400))
# 딕셔너리 형태로 변환 후 args["path_image"]로 접근해서 이미지 불러오기
args = vars(parser.parse_args())
image2 = cv2.imread(args["path_image"])
image2 = cv2.resize(image2, (600, 400))
# 이미지 출력
cv2.imshow("loaded image", image)
cv2.imshow("loaded image2", image2)
cv2.waitKey(0)
cv2.destroyAllWindows()
동일한 이미지 경로를 받아서 객체에 접근하는 방식과 딕셔너리 형태로 접근하는 방법으로 이미지를 불러와서 출력하는 코드입니다. command line에 <python argparse_load_image.py 이미지경로>로 실행하시면 아래와 같이 동일한 이미지를 보실 수 있습니다. 참고로 제가 가지고 있는 이미지는 워낙 이미지 크기가 커서 resize를 진행했습니다. 가지고 계신 이미지가 크지 않다면, resize를 진행하지 않으셔도 괜찮습니다.
2) 저장하기
위에서 이미지를 불러오기를 해봤으니 비슷하게 저장하기도 할 수 있겠죠. 어떻게 할 수 있을까요? argument로 저장할 위치의 path를 지정하면 되겠죠?
import argparse
import cv2
parser = argparse.ArgumentParser()
parser.add_argument("path_image_input", help="path to input image to be displayed")
parser.add_argument("path_image_output", help="path of the processed image to be saved")
args = vars(parser.parse_args())
image_input = cv2.imread(args["path_image_input"])
image_input = cv2.resize(image_input, (600, 400))
gray_image = cv2.cvtColor(image_input, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray image", gray_image)
cv2.imwrite(args["path_image_output"], gray_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
위의 코드블럭처럼 새로운 argument output 경로를 추가해줬습니다. 저는 들어온 이미지를 Grayscale로 변경해서 gray.jpg로 저장하기로 했습니다. command line에서 <python argparse_save_image.py dog.jpg gray.jpg>로 실행하시면 아래의 그림이 뜨면서 py파일이 있는 폴더에 gary.jpg가 생성되는 것을 확인하실 수 있습니다.
3. 요약
- 이미지 처리할 때, 다양한 형태의 input과 output을 얻을 수 있습니다. 그 과정 속에서 command-line arguments들을 잘 다루는 것이 중요합니다.
- command-line arguments를 다루는 방법에는 sys.argv와 argparse가 존재합니다.
- sys.argv는 인덱스로 접근하기 때문에 코드를 봤을 때, 가독성이 떨어지는 단점이 존재합니다. 또한 많은 인수가 존재할 때 사용하기 어렵습니다.
- argparse는 우리가 지정한 이름으로 인수를 접근할 수 있기 때문에 sys.argv에 비해 편리한 라이브러리입니다.
- argparse는 딕셔너리 형태로 변환하는 vars()이라는 명령어도 존재하기에 객체에 접근하는 방식과 딕셔너리로 접근하는 방식 둘 다 사용이 가능합니다.
- argparse를 사용하면 코드 내용을 바꾸지 않고 command-line에서 내가 원하는 경로만 지정해서 불러오고 저장할 수 있어서 재사용성이 좋아집니다.