diff --git a/golden_frame/__init__.py b/golden_frame/__init__.py deleted file mode 100644 index 6df5671..0000000 --- a/golden_frame/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from golden_frame.lib import * diff --git a/golden_frame/assets/big_frame.jpg b/golden_frame/assets/big_frame.jpg deleted file mode 100644 index 1c3dd3b..0000000 Binary files a/golden_frame/assets/big_frame.jpg and /dev/null differ diff --git a/golden_frame/assets/big_frame.json b/golden_frame/assets/big_frame.json deleted file mode 100644 index 94c5a8c..0000000 --- a/golden_frame/assets/big_frame.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Big Golden Frame in the field", - "pos": "668,197,869,497" -} \ No newline at end of file diff --git a/golden_frame/assets/golden_frame.json b/golden_frame/assets/golden_frame.json deleted file mode 100644 index 5135360..0000000 --- a/golden_frame/assets/golden_frame.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Golden Frame ทพจร", - "pos": "122,122,620,844" -} \ No newline at end of file diff --git a/golden_frame/assets/golden_frame.png b/golden_frame/assets/golden_frame.png deleted file mode 100644 index 353e680..0000000 Binary files a/golden_frame/assets/golden_frame.png and /dev/null differ diff --git a/golden_frame/assets/obamium_portrait.jpg b/golden_frame/assets/img/obamium_portrait.jpg similarity index 100% rename from golden_frame/assets/obamium_portrait.jpg rename to golden_frame/assets/img/obamium_portrait.jpg diff --git a/golden_frame/assets/vladdy_daddy.jpg b/golden_frame/assets/img/vladdy_daddy.jpg similarity index 100% rename from golden_frame/assets/vladdy_daddy.jpg rename to golden_frame/assets/img/vladdy_daddy.jpg diff --git a/golden_frame/assets/wessuwan.png b/golden_frame/assets/img/wessuwan.png similarity index 100% rename from golden_frame/assets/wessuwan.png rename to golden_frame/assets/img/wessuwan.png diff --git a/golden_frame/assets/json/obamium_portrait.json b/golden_frame/assets/json/obamium_portrait.json new file mode 100644 index 0000000..8852abd --- /dev/null +++ b/golden_frame/assets/json/obamium_portrait.json @@ -0,0 +1,9 @@ +{ + "name": "Obama Portrait", + "pos": [ + [133, 29], + [835, 26], + [640, 786], + [119, 786] + ] +} \ No newline at end of file diff --git a/golden_frame/assets/json/vladdy_daddy.json b/golden_frame/assets/json/vladdy_daddy.json new file mode 100644 index 0000000..b37c102 --- /dev/null +++ b/golden_frame/assets/json/vladdy_daddy.json @@ -0,0 +1,10 @@ +{ + "name": "Vladimir Putin does not #StandWithUkraine but I do", + "pos": [ + [173, 459], + [363, 469], + [365, 797], + [175, 789] + ] + +} \ No newline at end of file diff --git a/golden_frame/assets/json/wessuwan.json b/golden_frame/assets/json/wessuwan.json new file mode 100644 index 0000000..1c51287 --- /dev/null +++ b/golden_frame/assets/json/wessuwan.json @@ -0,0 +1,9 @@ +{ + "name": "ท้าวเวสสุวรรณ", + "pos": [ + [77, 505], + [164, 502], + [173, 615], + [64, 620] + ] +} \ No newline at end of file diff --git a/golden_frame/assets/obamium_portrait.json b/golden_frame/assets/obamium_portrait.json deleted file mode 100644 index 75d0082..0000000 --- a/golden_frame/assets/obamium_portrait.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Obama Portrait", - "pos": "114,26,642,788" -} \ No newline at end of file diff --git a/golden_frame/assets/plaingoldenframe.jpg b/golden_frame/assets/plaingoldenframe.jpg deleted file mode 100644 index 1a767ea..0000000 Binary files a/golden_frame/assets/plaingoldenframe.jpg and /dev/null differ diff --git a/golden_frame/assets/plaingoldenframe.json b/golden_frame/assets/plaingoldenframe.json deleted file mode 100644 index 943acf9..0000000 --- a/golden_frame/assets/plaingoldenframe.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Plain Golden Frame", - "pos": "114,26,642,788", - "flex": true -} \ No newline at end of file diff --git a/golden_frame/assets/vladdy_daddy.json b/golden_frame/assets/vladdy_daddy.json deleted file mode 100644 index 70c85dd..0000000 --- a/golden_frame/assets/vladdy_daddy.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "Vladimir Putin does not #StandWithUkraine but I do", - "pos": "172,456,369,803" -} \ No newline at end of file diff --git a/golden_frame/assets/wessuwan.json b/golden_frame/assets/wessuwan.json deleted file mode 100644 index e03ba0b..0000000 --- a/golden_frame/assets/wessuwan.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "ท้าวเวสสุวรรณ", - "pos": "631,403,842,657" -} \ No newline at end of file diff --git a/golden_frame/cli.py b/golden_frame/cli.py index 7d2334f..1a8f27a 100644 --- a/golden_frame/cli.py +++ b/golden_frame/cli.py @@ -1,43 +1,34 @@ #!/usr/bin/env python3 -# pylint: disable=import-error import os import typer - -if os.environ.get("DEBUG") is None: - from golden_frame.lib import PosOptions, buildFromPreset, listFrames -else: - print("[DEBUG] Only use this mode for local development") - from lib import PosOptions, buildFromPreset, listFrames +from golden_frame.lib import build_from_preset, listFrames app = typer.Typer() - @app.command() -def build(frame_name: str, input: str, output="output.png", - pos=PosOptions.CENTER, res=720): - """Build the Golden Frame +def build( + frame_name: str, + input: str, + output: str = "output.png", + res: int = 720 +): + """ + Build the Golden Frame Args: - frame_name (str): Name of Template Frame - input (str): Location of your Image - output (str, optional): Output Location. Defaults to "output.png". - - pos (0 | 1 | 2, optional): Position Type CENTER = 0, START = 1, END = 2. Defaults to CENTER. - - res (int, optional): Minimum size of image + res (int, optional): Minimum size of image. Defaults to 720. """ - buildFromPreset(frame_name, input, output, int(pos), int(res)) + build_from_preset(frame_name, input, output, res) @app.command() def list(): - """Print List of Available Frames - """ + """Print List of Available Frames""" print(listFrames()) diff --git a/golden_frame/lib.py b/golden_frame/lib.py index b07a83a..28ed0cb 100644 --- a/golden_frame/lib.py +++ b/golden_frame/lib.py @@ -1,5 +1,3 @@ -# pylint: disable=no-member,misplaced-bare-raise - from typing import Dict, List, Tuple, Union import json import os @@ -7,105 +5,78 @@ import cv2 import numpy as np -import pkg_resources -ASSET_PATH = pkg_resources.resource_filename("golden_frame", "assets") + +ASSET_PATH = os.path.join("golden_frame", "assets") if os.environ.get("DEBUG") is not None: ASSET_PATH = "./golden_frame/assets" -class CropOptions(Enum): - CROP = 0 - PRESERVE = 1 - - -class PosOptions: - CENTER = 0 - START = 1 - END = 2 - - -def getPos(x: Union[int, float], dx: Union[int, float], opt) -> Tuple[int, int]: - if opt == PosOptions.START: - return 0, round(x) - if opt == PosOptions.CENTER: - return round(dx), round(x+dx) - if opt == PosOptions.END: - return round(2*dx), round(2*dx+x) - - raise Exception("opt is not valid PosOptions") - - -def resizeImage( - pic: np.ndarray, - size: List[int], - posOption=PosOptions.CENTER -) -> np.ndarray: - py, px = pic.shape[0:2] - ratio = max(size[0]/px, size[1]/py) - resized = cv2.resize(pic, dsize=( - round(px*ratio), round(py*ratio))) +def build_frame( + source_image: np.ndarray, + frame_image: np.ndarray, + frame_marks: List[List[int]], + res = 720 + )->np.ndarray: + + # Scale Frame to match size requirements + # - Scale to match height + # - Calculate change of x point and y point to shift position mark on frame + og_height, og_width = frame_image.shape[0:2] + frame_image = cv2.resize(frame_image, (frame_image.shape[1]*res//frame_image.shape[0], res)) - ysize, xsize = resized.shape[0:2] - dx = (xsize - size[0])/2 - dy = (ysize - size[1])/2 + # Calculate change of x point and y point to shift position mark on frame + for mark in frame_marks: + mark[0] = mark[0]*frame_image.shape[1]//og_width + mark[1] = mark[1]*frame_image.shape[0]//og_height + - sx, ex = getPos(size[0], dx, posOption) - sy, ey = getPos(size[1], dy, posOption) - return resized[sy:ey, sx:ex] + # Calculate width and height of the destination frame & Resize Source Image + # Convert frame_marks to np.float32 + frame_marks = np.float32(frame_marks) + width = int(max(np.linalg.norm(frame_marks[0] - frame_marks[1]), np.linalg.norm(frame_marks[2] - frame_marks[3]))) + height = int(max(np.linalg.norm(frame_marks[0] - frame_marks[3]), np.linalg.norm(frame_marks[1] - frame_marks[2]))) + source_image = cv2.resize(source_image, (width, height)) + + # Perform Perspective Transformation + # - Define source points + # - Calculate the perspective transformation matrix + # - Apply the perspective transformation + src_pts = np.float32([[0, 0], [width, 0], [width, height], [0, height]]) + M = cv2.getPerspectiveTransform(src_pts, np.float32(frame_marks)) + warped = cv2.warpPerspective(source_image, M, (frame_image.shape[1], frame_image.shape[0])) -def scaleImage(img: np.ndarray, res: int) -> Tuple[np.ndarray, float, float]: - ly, lx = img.shape[0:2] - ratio = res / min(ly, lx, res) + # Create a mask and inverse mask of the warped image + mask = cv2.warpPerspective(np.ones_like(source_image) * 255, M, (frame_image.shape[1], frame_image.shape[0])) + inv_mask = cv2.bitwise_not(mask) - resized = cv2.resize(img, dsize=(round(lx*ratio), round(ly*ratio))) - return (resized, ratio, ratio) + # Blend the images + result = cv2.bitwise_and(frame_image, inv_mask) + result = cv2.add(result, warped) + + return result -def buildGoldenFrame( - frame: np.ndarray, - picture: np.ndarray, - pos: List[int], - posOption=PosOptions.CENTER, - res=720 -) -> np.ndarray: - # Scale frame to minimum size - frame, ratiox, ratioy = scaleImage(frame, res) - - resized = resizeImage( - picture, - [round((pos[2] - pos[0]) * ratiox), - round((pos[3] - pos[1]) * ratioy)], - posOption) - xstart, ystart = (round(pos[0] * ratiox), round(pos[1] * ratioy)) - ysize, xsize = resized.shape[0:2] - - # Why can't you use same axis system? - frame[ystart:ystart+ysize, xstart:xstart+xsize] = resized - - return frame - - -def loadConfig(name: str) -> Dict: - with open(".".join(name.split(".")[:-1]) + ".json") as f: +def load_config(frame: str): + with open(os.path.join(ASSET_PATH, "json", "{}.json".format(frame)), "r") as f: return json.load(f) + - -def buildFromPreset( - frame: str, image: str, out: str, opt=PosOptions.CENTER, res=720): +def build_from_preset( + frame: str, image: str, out: str, res=720): frame = f"{ASSET_PATH}/{frame}" try: - frameimg = cv2.imread(frame) - if not frameimg.data: + frame_image = cv2.imread(frame) + if not frame_image.data: raise except: print( @@ -114,38 +85,31 @@ def buildFromPreset( return try: - inputimg = cv2.imread(image) - if not inputimg.data: + input_image = cv2.imread(image) + if not input_image.data: raise except: print(f"ERROR: Cannot Read Input Image {image}!") return - cfg = loadConfig(frame)["pos"] - outim = buildGoldenFrame(frameimg, inputimg, - list(int(k) for k in cfg.split(",")), opt, res) - + cfg = load_config(frame)["pos"] + outim = build_frame(input_image, frame_image, cfg, res) try: cv2.imwrite(out, outim) except: print(f"Error writing Image!") -def listFrames() -> str: +def list_frames() -> str: import os items = list(filter(lambda x: not x.endswith( - ".json"), os.listdir(ASSET_PATH))) + ".json"), os.listdir(os.path.join(ASSET_PATH, "json")))) text = f"There are {len(items)} frames available.\n" for item in items: - cfg = loadConfig(f"{ASSET_PATH}/{item}") + cfg = load_config(item.replace(".json", "")) text += f"\n{item} : {cfg['name']}" return text - -# if __name__ == "__main__": -# buildFromPreset( -# "big_frame.jpg", "example/zhongxina_before.jpg", "output.png", -# PosOptions.CENTER, 720) diff --git a/server/app.py b/server/app.py index eb1be2d..f4b16da 100644 --- a/server/app.py +++ b/server/app.py @@ -4,7 +4,7 @@ import os import cv2 import numpy as np -from golden_frame.lib import buildGoldenFrame, listFrames, ASSET_PATH, loadConfig, PosOptions +from golden_frame.lib import build_frame, list_frames, ASSET_PATH, load_config from datetime import datetime from flask import Flask, request, Response @@ -31,19 +31,20 @@ def line_to_json(line: str): } -def build_golden_frame(frame_name: str, input_img: np.ndarray): - frame_path = f"{ASSET_PATH}/{frame_name}" - frameimg = cv2.imread(frame_path) - cfg = loadConfig(frame_path)["pos"] - - out_image = buildGoldenFrame(frame=frameimg, picture=input_img, pos=list( - int(k) for k in cfg.split(",")), posOption=PosOptions.CENTER) - +def build_golden_frame(frame_name: str, input_image: np.ndarray): + frame_path = os.path.join(ASSET_PATH, frame_name) + frame_image = cv2.imread(frame_path) + + out_image = build_frame( + source_image = input_image, + frame_image = frame_image, + frame_marks = load_config(frame_name)["pos"] + ) return out_image -def listFramesJson(): - frames = listFrames() +def list_frame_json(): + frames = list_frames() items = list(map(line_to_json, filter( lambda x: len(x), frames.split("\n")[1:]))) @@ -52,7 +53,7 @@ def listFramesJson(): @app.route("/", methods=["GET"]) def get_frames(): - return listFramesJson(), 200 + return list_frame_json(), 200 @app.route("/", methods=["POST"]) @@ -75,7 +76,7 @@ def build_frame(): if frame_name is None: return 'No frame name selected', 400 - if not any(map(lambda x: x['name'] == frame_name, listFramesJson())): + if not any(map(lambda x: x['name'] == frame_name, list_frame_json())): return 'Invalid frame name', 400 # Read the image file as bytes