| import os |
| import cv2 |
| import numpy as np |
| from flask import Flask, request, jsonify, send_file |
| from werkzeug.utils import secure_filename |
|
|
| app = Flask(__name__) |
|
|
| |
| |
| |
| |
| |
| |
| UPLOAD_FOLDER = os.path.join(os.getcwd(), "storage", "inputs") |
| OUTPUT_FOLDER = os.path.join(os.getcwd(), "storage", "outputs") |
|
|
| app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER |
| app.config["OUTPUT_FOLDER"] = OUTPUT_FOLDER |
|
|
| |
| os.makedirs(UPLOAD_FOLDER, exist_ok=True) |
| os.makedirs(OUTPUT_FOLDER, exist_ok=True) |
|
|
|
|
| def align_image_to_reference(ref_path: str, target_path: str, output_path: str): |
| """Aligns target_path image to match ref_path image geometry and saves to output_path.""" |
| ref_img = cv2.imread(ref_path) |
| targ_img = cv2.imread(target_path) |
|
|
| if ref_img is None or targ_img is None: |
| raise ValueError("Could not read input images from storage.") |
|
|
| |
| gray_ref = cv2.cvtColor(ref_img, cv2.COLOR_BGR2GRAY) |
| gray_targ = cv2.cvtColor(targ_img, cv2.COLOR_BGR2GRAY) |
|
|
| |
| sift = cv2.SIFT_create() |
| kp_ref, des_ref = sift.detectAndCompute(gray_ref, None) |
| kp_targ, des_targ = sift.detectAndCompute(gray_targ, None) |
|
|
| if des_ref is None or des_targ is None: |
| raise ValueError("Failed to extract keypoints from one or both images.") |
|
|
| |
| INDEX_KDTREE = 1 |
| flann = cv2.FlannBasedMatcher( |
| dict(algorithm=INDEX_KDTREE, trees=5), dict(checks=50) |
| ) |
| matches = flann.knnMatch(des_ref, des_targ, k=2) |
|
|
| |
| good_matches = [m for m, n in matches if m.distance < 0.7 * n.distance] |
|
|
| if len(good_matches) < 10: |
| raise ValueError("Insufficient matching features found between images.") |
|
|
| |
| src_pts = np.float32([kp_ref[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2) |
| dst_pts = np.float32([kp_targ[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2) |
|
|
| |
| matrix, _ = cv2.estimateAffinePartial2D(dst_pts, src_pts, method=cv2.RANSAC) |
|
|
| if matrix is None: |
| raise ValueError("Failed to compute valid alignment transformation matrix.") |
|
|
| |
| h, w = ref_img.shape[:2] |
| aligned_img = cv2.warpAffine( |
| targ_img, |
| matrix, |
| (w, h), |
| flags=cv2.INTER_LANCZOS4, |
| borderMode=cv2.BORDER_CONSTANT, |
| borderValue=(0, 0, 0) |
| ) |
|
|
| |
| |
| |
| |
| |
| cv2.imwrite(output_path, aligned_img) |
|
|
|
|
| @app.route("/align", methods=["POST"]) |
| def align_endpoint(): |
| |
| if "reference" not in request.files or "target" not in request.files: |
| return jsonify({"error": "Missing 'reference' or 'target' file in request form-data."}), 400 |
|
|
| ref_file = request.files["reference"] |
| target_file = request.files["target"] |
|
|
| if ref_file.filename == "" or target_file.filename == "": |
| return jsonify({"error": "No file selected."}), 400 |
|
|
| |
| ref_name = secure_filename(ref_file.filename) |
| target_name = secure_filename(target_file.filename) |
|
|
| |
| |
| |
| |
| |
| input_ref_path = os.path.join(app.config["UPLOAD_FOLDER"], f"ref_{ref_name}") |
| input_target_path = os.path.join(app.config["UPLOAD_FOLDER"], f"target_{target_name}") |
|
|
| ref_file.save(input_ref_path) |
| target_file.save(input_target_path) |
| |
|
|
| |
| |
| |
| |
| |
| output_aligned_path = os.path.join(app.config["OUTPUT_FOLDER"], f"aligned_{target_name}") |
| |
|
|
| try: |
| |
| align_image_to_reference(input_ref_path, input_target_path, output_aligned_path) |
|
|
| |
| return send_file(output_aligned_path, mimetype="image/png") |
|
|
| except Exception as e: |
| return jsonify({"status": "error", "message": str(e)}), 500 |
|
|
|
|
| if __name__ == "__main__": |
| |
| app.run(host="0.0.0.0", port=5000, debug=True) |