123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- import os
- from typing import Tuple
- import cv2
- from PIL import Image
- from pycs.database.File import File
- DEFAULT_JPEG_QUALITY = 80
- def file_info(data_folder: str, file_name: str, file_ext: str):
- """
- Receive file type, frame count and frames per second.
- The last two are always None for images.
- :param data_folder: path to data folder
- :param file_name: file name
- :param file_ext: file extension
- :return: file type, frame count, frames per second
- """
- # determine file type
- if file_ext.lower() in ['.jpg', '.png']:
- file_type = 'image'
- elif file_ext.lower() in ['.mp4']:
- file_type = 'video'
- else:
- raise ValueError(f"Unsupported file extension: {file_ext}!")
- # determine frames and fps for video files
- if file_type == 'image':
- frames = None
- fps = None
- else:
- file_path = os.path.join(data_folder, file_name + file_ext)
- video = cv2.VideoCapture(file_path)
- frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
- fps = video.get(cv2.CAP_PROP_FPS)
- video.release()
- # return values
- return file_type, frames, fps
- def resize_file(file: File, project_root: str, max_width: int, max_height: int) -> Tuple[str, str]:
- """
- If file type equals video this function extracts a thumbnail first. It calls resize_image
- to resize and returns the resized files directory and name.
- :param file: file object
- :param project_root: project root folder path
- :param max_width: maximum image or thumbnail width
- :param max_height: maximum image or thumbnail height
- :return: resized file directory, resized file name
- """
- abs_file_path = file.absolute_path
- # extract video thumbnail
- if file.type == 'video':
- abs_target_path = os.path.join(os.getcwd(), project_root, 'temp', f'{file.uuid}.jpg')
- create_thumbnail(abs_file_path, abs_target_path)
- abs_file_path = abs_target_path
- # resize image file
- abs_target_path = os.path.join(os.getcwd(),
- project_root,
- 'temp',
- f'{file.uuid}_{max_width}_{max_height}.jpg')
- result = resize_image(abs_file_path, abs_target_path, max_width, max_height)
- # return path
- if result is not None:
- return os.path.split(abs_target_path)
- return os.path.split(abs_file_path)
- def crop_file(file: File, project_root: str,
- x: float, y: float, w: float, h: float,
- max_width: int, max_height: int) -> Tuple[str, str]:
- """
- gets a file for the given file_id, crops the according image to the
- bounding box and saves the crops in the temp folder of the project.
- :param file: file object
- :param project_root: project root folder path
- :param max_width: maximum image or thumbnail width
- :param max_height: maximum image or thumbnail height
- :param x: relative x-coordinate of the top left corner
- :param y: relative y-coordinate of the top left corner
- :param w: relative width of the bounding box
- :param h: relative height of the bounding box
- :return: directory and file name of the cropped patch
- """
- abs_file_path = file.absolute_path
- # extract video thumbnail
- if file.type == 'video':
- abs_target_path = os.path.join(os.getcwd(), project_root, 'temp', f'{file.uuid}.jpg')
- create_thumbnail(abs_file_path, abs_target_path)
- abs_file_path = abs_target_path
- # crop image file
- abs_target_path = os.path.join(os.getcwd(),
- project_root,
- 'temp',
- f'{file.uuid}_{x}_{y}_{w}_{h}.jpg')
- result = crop_image(abs_file_path, abs_target_path, x, y, w, h)
- if result:
- abs_file_path = abs_target_path
- # resize image
- abs_target_path = os.path.join(os.getcwd(),
- project_root,
- 'temp',
- f'{file.uuid}_{max_width}_{max_height}_{x}_{y}_{w}_{h}.jpg')
- result = resize_image(abs_file_path, abs_target_path, max_width, max_height)
- if result:
- abs_file_path = abs_target_path
- # return image
- return os.path.split(abs_file_path)
- def create_thumbnail(file_path: str, target_path: str) -> None:
- """
- extract a thumbnail from a video
- :param file_path: path to source file
- :param target_path: path to target file
- :return:
- """
- # return if file exists
- if os.path.exists(target_path):
- return
- # load video
- video = cv2.VideoCapture(file_path)
- # create thumbnail
- _, image = video.read()
- cv2.imwrite(target_path, image)
- # close video file
- video.release()
- def resize_image(file_path: str, target_path: str, max_width: int, max_height: int) -> bool:
- """
- Resize an image so width < `max_width` and height < `max_height` applies.
- If the image is already smaller than the given dimensions no new file is stored.
- :param file_path: path to source file
- :param target_path: path to target file
- :param max_width: maximum image width
- :param max_height: maximum image height
- :return: `True` if a resize operation was performed or the target file already exists
- """
- # return if file exists
- if os.path.exists(target_path):
- return True
- # load full size image
- image = Image.open(file_path)
- img_width, img_height = image.size
- # abort if file is smaller than desired
- if img_width < max_width and img_height < max_height:
- return False
- # calculate target size
- target_width = int(max_width)
- target_height = int(max_width * img_height / img_width)
- if target_height > max_height:
- target_height = int(max_height)
- target_width = int(max_height * img_width / img_height)
- # resize image
- resized_image = image.resize((target_width, target_height))
- # save to file
- resized_image.save(target_path, quality=DEFAULT_JPEG_QUALITY)
- return True
- def crop_image(file_path: str, target_path: str, x: float, y: float, w: float, h: float) -> bool:
- """
- Crop an image with the given coordinates, width and height.
- If however no crop is applied no new file is stored.
- :param file_path: path to source file
- :param target_path: path to target file
- :param x: crop x position (normalized)
- :param y: crop y position (normalized)
- :param w: crop width (normalized)
- :param h: crop height (normalized)
- :return: `True` if a crop operation was performed or the target file already exists
- """
- # return if file exists
- if os.path.exists(target_path):
- return True
- # abort if no crop would be applied
- if x <= 0 and y <= 0 and w >= 1 and h >= 1:
- return False
- # load full size image
- image = Image.open(file_path)
- img_width, img_height = image.size
- # calculate absolute crop position
- crop_x1 = int(img_width * x)
- crop_y1 = int(img_height * y)
- crop_x2 = min(int(img_width * w) + crop_x1, img_width)
- crop_y2 = min(int(img_height * h) + crop_y1, img_height)
- # crop image
- print(crop_x1, crop_y1, crop_x2, crop_y2)
- cropped_image = image.crop((crop_x1, crop_y1, crop_x2, crop_y2))
- # save to file
- cropped_image.save(target_path, quality=DEFAULT_JPEG_QUALITY)
- return True
|