[opencv] opencv에서 한글명 파일의 저장과 gif파일 저장 문제
한글명파일이나,gif이미지 파일을 읽어들여서 보여주기 까지는 해결이 되었는데, 저장하는 부분은 처리가 되지 않았다.
한글부분은 처리가 될 수 있겠는데, opencv에서 gif와 별로 사이가 좋지 않은 관계로 gif를 저장하는 방법을 검색을 통해 찾아봐야 했는데, 사실 내가하는 구현하는 방식은 일반적인 정지 사진이라고 할 수 있는것들이라 엄밀히 따지면, 사실 gif는 고려할 대상은 아니다. 그런데 이걸 좀 하다보니까, 이 부분도 좀 짚어야 되겠다 싶어서 살펴보다 보니 좀 깊게까지 들어온것같다.
opencv에서 read와 write기능중에서 한글파일명이나, gif등의 읽기 쓰기 등에 한계가 있다보니 아예 함수로 아예 구현을 해 놓은것인데, 이 부분을 약간 변형하였다. 아래는 원본 소스 출처
imread부분.. imread부분은 내경우 해결되어서 괜찮은데, 그냥 한번 둘러본다. |
# _filePath는 file이 존재하는 전체 경로..
# _flags는 , reading할때 들어가는 옵션 (보통 칼라 모드일 경우cv2.IMREAD_COLOR로.)
# _dtype=np.uint8 # dtype=np.uint8로 디폴트 매개변수 되있다.
def imread(_filePath, _flags=cv2.IMREAD_COLOR, _dtype=np.uint8):
try:
n = np.fromfile(_filePath, _dtype) # fromfile을통해 실제 파일을 읽어들인다.
decodedImg = cv2.imdecode(n, _flags) # 읽은 파일을 opencv형태로 디코딩한다. (한글파일명문제는 여기서 해결된다.)
if(str(_filePath).lower().endswith('.gif')): # 여기가 gif관련하여 읽어들이는 부분. (opencv에서gif읽을때 에러나니까)
gif = cv2.VideoCapture(_filePath)
ret,frame = gif.read()
if ret:
return frame
else:
return decodedImg
except Exception as e:
print(e)
return None
# return frame이나, return decodedImg나, 결국 return될때는 numpy배열형태를 한번 거치면서
# 다시 opencv형태로 읽어들이면서, 이 과정에서 한글파일명이나, gif문제를 해결 할 수 있다.
#함수 호출은 아래방식으로 보내면된다.
img = imread(filePath,cv2.IMREAD_COLOR,np.uint8)
#이때 img는 곧, opencv형식에서 다음과 동일하다.
# img = cv2.imread(img,cv2.IMREAD_COLOR) # 이렇게 가면되는데,
이렇게 놓고 쓰면, 읽기 문제에서 두 가지 문제가 그냥 한번에 해결이 된다. 이 부분은 내경우 해결된 상태라 넘어가도 되는데, gif문제를 추가하면서 맞는 형태로 바꿔 봤다. 내가 또 써먹을 수 있으니까..
cv2.imread로 바로 읽어들이면, opencv에서는 한글 파일명 에러가 발생되기때문에, 이 에러를 감수할 수 있는 능력이 되는 놈으로 먼저 열고 그 열고 나서 이 상태를 opencv가 소화할 수 있는 형태로 바꿔주는것이다. 그러니까 decoding의 의미는 곧, 하나의 읽어들인 데이터를 자기가 읽을 수 있는 형태로 바꿔주는 것이다. 처음에 읽어들일때만 이용하고 읽어들여서 메모리에 로딩이 되었을때는 이 로딩된 데이터를 decoding의 형태로 내 가 사용할수 있게(즉, opencv가 읽을 수 있는 형태)로 바꿔주는개념..
np.fromfile로 오픈함은 결국 np즉, numpy를 통해서 오픈한것인데, 이 numpy는 1차원 배열의 형태를 띄고 있다. 이 상태로는 opencv는 읽을 수 없기에 한글 파일명 관련하여 하드 어딘가에 저장되어있는 하나의 '파일(file)'을 불러서 읽어온것이고, 이제 읽어왔으니 이놈을 다시 바꿔서 이놈의 주인이 opencv가 다시 되게 만드는것..
요때 넘겨받을때 opencv는 파일을 직접 하드에 있는걸 연게 아니라, np.fromfile을 통해 오픈한 놈을 (즉, 메모리에 로딩된 놈을)이놈을 바꿔서 받은것이기 때문에 buffer로 받은것이 된다. 이렇게 받는과정을 할 수 있다면, opencv에서 이것말고도 '실제 존재하는 어떤 파일을 받는데 문제가 있다면' 계속 이런형태를 이용해서 받으면 해결할 수 있을 것이고, 그럼 충분히 함수로 만들어서 계속적으로 쓸 수 있을것이다. 내부적으로 그 부분만 추가적으로 더 구현해 주면 말이다. 내가 gif문제를 그 안에 집어 넣었듯이..
내가 깜박한것은.. 이게 저장할때를 생각 못했던것.. 읽기에서도 문제니, 저장역시도 당연히 하나의 파일로 저장이 되서 하드로 들어가는건데, 당연히 문제가 생길 수 밖에 없을것이다.
imwrite부분.. |
#_saveFilePath : 파일이 저장될 곳의 경로와 저장될 파일의 이름이다.
# _img : 지금읽어들인 opencv형태의 바로 그 data이다. (아직 파일은 아니다 저장은 않했으니)
# _params : 파일 저장옵션인데, 내경우 안에서 직접 구현하고 이 값은 none으로 했다.
def imwrite(_saveFilePath,_img,_params = None):
try:
ext = os.path.splitext(_saveFilePath)[1] # split text가 아니라, split ext이다. 곧 확장자 기준으로 split한다는 의미.
if (str(_saveFilePath).lower().endswith('.gif')): #첫번째 if문에서 gif문제를 해결하고
result,n = cv2.imencode('.jpg',_img,[cv2.IMWRITE_JPEG_QUALITY,100]) #이때 numpy형의 1차원 배열의 형태로 바뀐다.
if result:
with open(_saveFilePath,mode = 'w+b') as f: # with문 이용한 file오픈,
n.tofile(f) # 지금 encode했던 n이라는 buffer를 f에 할당하는 과정. 이순간 파일의 형태로 만들어지면서 저장이 된다.
print("GIF save success")
return True
elif (str(_saveFilePath).lower().endswith('.gif') == False): # gif파일이 아닌경우 이 elif문에서 한글 파일명 저장문제 해결
result,n = cv2.imencode(ext,_img,_params) #이때 numpy형의 1차원 배열의 형태로 바뀐다.
if result:
with open(_saveFilePath,mode = 'w+b') as f:
n.tofile(f)
print("save success")
return True
else:
return False
except Exception as e:
print(e)
return False
fileName = 'sample.gif' # 또는.. 한글명.gif 또는 한글명.jpg..
pathDir = os.getcwd()+'\\tempfolder\\saveImg\\'
saveFilePath = pathDir+fileName
imwrite(saveFilePath,img,None) # 저장은 여기서
# 이 부분에서는 실제 gif파일로 저장은 되었지만 속성 자첸 jpeg라는것을
# 확인 해 보기 위한 부분이다. imghdr.what()으로 보면, 이부분에서 'jpeg'로 출력됨을 확인 할 수 있다.
# print("saveFIlePath ; ",saveFilePath)
# print("imghdr.what(saveFilePath) : jpg? gif? ====> ",imghdr.what(saveFilePath)) #imghdr에서는 jpeg로 나온다.
저장부분은 이 부분인데, 마찬가지로 원래 cv2.imwrite()를 함수로 확장.. 여기서 gif문제를 추가 하였다.
ext = os.path.splitext(_saveFilePath)[1]
ext는 확장자의 이름일 것이고 split을 하는걸 보면 파일의 경로를 전달받아서 [1]번째 요소를 기준으로 split한다는 의미일것이다. 그래서 go to definition들어가보면,
def splitext(p):
p = os.fspath(p)
if isinstance(p, bytes):
sep = b'/'
extsep = b'.'
else:
sep = '/'
extsep = '.'
return genericpath._splitext(p, sep, None, extsep)
이렇게 되어있는데, 보니까 역슬래시(파일의 경로를 구분짓는), 그리고 점...point ('.')을 구분자로 써서 split한다는걸 알 수 있다. 그러니까 ext는 확장자를 담을거니까, 2번째 요소 즉 배열로는 ,[1]번째 요소가 될거고, isinstance부분은 만약 bytes타입이면 b를 붙여서 string형태로 넣겠다는 의미일테다..
ext는 여기서 .gif가 되는데.. 이 ext를 구한 이유가 edncoding을 하기 위한것인데.. 즉,' opencv가 이제 파일로 저장을 하기 위해서 어떠 어떠한 확장자의 이미지 파일인데, 어떠어떠한 형태로 인코딩을 해라.. ' 우리는 gif이기때문에 gif로 저장을 해야되는데, 여기서 에러가 나오는거다. "읽기"때 처럼..
result,n = cv2.imencode('.jpg',_img,[cv2.IMWRITE_JPEG_QUALITY,100]) #이부분..
여기서 '.jpg'가 아닌 ext가 들어가야 맞다.. .gif파일이니까.. 그런데, opencv에서는 gif는 encoding과정에서 지원을 해주는것이 없는것으로 알고있다. 그렇기때문에 확장자 아닌 세번째 옵션 즉 flag부분 " [cv2.IMRWRITE_JPEG_QUALITY,100] "이 부분을 대체 할것이 없는것이다. (지금은 내가 jpg로 바꾼경우고..) opencv 도큐멘터리를 가도 찾을 수 없다.
https://docs.opencv.org/4.x/d8/d6a/group__imgcodecs__flags.html
이쪽 목록에서 write부분을 보면 gif와 관련된 flag값을 찾을 수 없다. jpg, png등은 보인다..
default값인 none으로 해도 에러가 나오게 되서 그래서 gif 저장을 하지 않으려고 하다가, 사실 내 프로그램에서는(pixagoras) gif 파일은 필요없기때문에 생략하려고 하다가, 생각해보니까, encoding 자체를 그냥 jpg나 png의 옵션으로 인코딩하여 저장하게하면 될것같다. encoding이 그런거니까.. 어찌됐건 에러발생되지 않으면서 저장은 되고, 또 이미지도 형태 자체가 jpg로 바뀌었으니 보는데도 문제는 없을것이다..
그래서 위에 처럼. ext대신에, '.jpg'를 그냥 그자리에서 집어넣고 그에 해당되는 flag값을 줘 본것이다.
저장이 잘 됨을 확인 할 수 있고, 클릭해서 볼 수도있다.
* 다만 ,만약에 gif가 움직이는 영상같은 형태라면.. 이땐 그 프레임의 한장만 캡처한 상태로 jpg형태로 저장이 될것이다. 당연할테다.
저장 결과를 확인해 보면, 당연히 동일한 이름의 파일인 sample.gif로 저장이 되고 thumbnail도 보여진다. 그럼이게 진짜 gif인것인가..? 이걸 확인 해 보기 위해 마지막 부분에 주석처리를 해제하고 찍어보면.
# 이 부분에서는 실제 gif파일로 저장은 되었지만 속성 자첸 jpeg라는것을
# 확인 해 보기 위한 부분이다.
# imghdr.what()으로 보면, 이부분에서 'jpeg'로 출력됨을 확인 할 수 있다.
# 주석을 해제하고 출력하면..
print("saveFIlePath ; ",saveFilePath)
print("imghdr.what(saveFilePath) : jpg? gif? ====> ",imghdr.what(saveFilePath))
#imghdr에서는 jpeg로 나온다.
saveFilePath는 저장이 될 경로 + 파일의 이름이 포함된다. 여기서 파일의 이름은 원래 파일이름으로 설정을 했다.지금 이 경로를 imghdr.what()을통해 찍어본다.
sample.gif이지만 내부적으로는 encoding과정을 통해서 jpeg가 된것이다.
* 이걸 하고 나니, 현재 pixagoras내에 내 소스도 대폭 간소하게 수정할 수 있을것같다. 현재 ImageLoadFromFile()이란 함수로 한글문제와 gif문제를 안에 집어넣고 imread()형태처럼 만들어져 있는데, save쪽은 아직은 그렇게 되어있지 않고 gif를 만났을때 경고문을 넣고 패스하게 시켰고, 한글파일같은 경우는 s나 S를 클릭해서 save를 해도 그냥 넘어가게끔 설정이 되어있다. 지금 저 write함수자체를 이용하여 ImageSaveFromFile()이란 형태로 바꿔버리면 대폭 간소해 질것이다... 아.. 이래서 감히 내가 깃허브 같은데에 소스 공유를 못(never) 하는것이다. ㅎㅎ
문제를 바로잡고 정리한 소스는 여기에