From 89b591b7e46b01ab587d7e873eb56fe564e0ad50 Mon Sep 17 00:00:00 2001 From: Marcos Junior Date: Fri, 4 Nov 2022 11:51:01 -0500 Subject: [PATCH] Expand image when rotating to re-read ArUco markers --- meterparser/CHANGELOG.md | 24 ++++++++++++--- meterparser/config.yaml | 2 +- meterparser/src/app/parsers/image_utils.py | 34 ++++++++++++++-------- meterparser/tests/test_parse_image.py | 32 +++++++++++--------- 4 files changed, 61 insertions(+), 31 deletions(-) diff --git a/meterparser/CHANGELOG.md b/meterparser/CHANGELOG.md index a906009..bbac775 100644 --- a/meterparser/CHANGELOG.md +++ b/meterparser/CHANGELOG.md @@ -1,18 +1,34 @@ # Changelog + +### [1.0.1.3] + +- Expand image when rotating to re-read ArUco markers. + ### [1.0.1.2] + - Fix incorrect error message + ### [1.0.1.1] + - Fix incorrect mqtt_url default + ### [1.0.1.0] + - Set an specific aruco marker number so you can add multiple counters using the same camera - Move custom mqtt setting to a single url (breaking change) - If url schema is not http(s), try get an image using ffmpeg + ### [1.0.0.28] -- Adjust time limit for camera scan interval + +- Adjust time limit for camera scan interval - Update base docker images + ### [1.0.0.25] -- Deal with possible HA restart and username/password change. + +- Deal with possible HA restart and username/password change. + ### [1.0.0.*] + - Make a lower limit so device won't be unavailable - Easier to read log messages - Tune limits and fail safe OCR readings @@ -28,6 +44,6 @@ - Fix service type on devices - Fix incorrect variable name. - Save image to PNG for OCR. -- Add `stdin` input to list and reset the control value. +- Add `stdin` input to list and reset the control value. - Fix code errors and error messages. -- Initial version. \ No newline at end of file +- Initial version. diff --git a/meterparser/config.yaml b/meterparser/config.yaml index f73973b..b505397 100644 --- a/meterparser/config.yaml +++ b/meterparser/config.yaml @@ -1,5 +1,5 @@ name: Meter Parser -version: 1.0.1.2 +version: 1.0.1.3 slug: meter-parser description: Read meter needles and numbers from a camera snapshot. url: https://github.com/junalmeida/homeassistant-addons/tree/main/meterparser diff --git a/meterparser/src/app/parsers/image_utils.py b/meterparser/src/app/parsers/image_utils.py index 9393770..03195e8 100644 --- a/meterparser/src/app/parsers/image_utils.py +++ b/meterparser/src/app/parsers/image_utils.py @@ -125,21 +125,31 @@ def angle_between(p1: tuple[int, int], p2: tuple[int, int]) -> float: # tuple[x (p2x, p2y) = p2 return math.degrees(math.atan2(p2y-p1y, p2x-p1x)) -def rotate_image(image, angle, center=None, scale=1.0): - # grab the dimensions of the image - (h, w) = image.shape[:2] +def rotate_image(image, angle): + """ + Rotates an image (angle in degrees) and expands image to avoid cropping + """ - # if the center is None, initialize it as the center of - # the image - if center is None: - center = (w // 2, h // 2) + height, width = image.shape[:2] # image shape has 3 dimensions + image_center = (width/2, height/2) # getRotationMatrix2D needs coordinates in reverse order (width, height) compared to shape - # perform the rotation - matrix = cv2.getRotationMatrix2D(center, -angle, scale) - rotated = cv2.warpAffine(image, matrix, (w, h), flags=cv2.INTER_NEAREST) + rotation_mat = cv2.getRotationMatrix2D(image_center, -angle, 1.) - # return the rotated image - return rotated + # rotation calculates the cos and sin, taking absolutes of those. + abs_cos = abs(rotation_mat[0,0]) + abs_sin = abs(rotation_mat[0,1]) + + # find the new width and height bounds + bound_w = int(height * abs_sin + width * abs_cos) + bound_h = int(height * abs_cos + width * abs_sin) + + # subtract old image center (bringing image back to origo) and adding the new image center coordinates + rotation_mat[0, 2] += bound_w/2 - image_center[0] + rotation_mat[1, 2] += bound_h/2 - image_center[1] + + # rotate image with the new bounds and translated rotation matrix + rotated_mat = cv2.warpAffine(image, rotation_mat, (bound_w, bound_h), flags=cv2.INTER_NEAREST) + return rotated_mat def crop_image(image, rect): diff --git a/meterparser/tests/test_parse_image.py b/meterparser/tests/test_parse_image.py index 5d4f0a2..080a797 100644 --- a/meterparser/tests/test_parse_image.py +++ b/meterparser/tests/test_parse_image.py @@ -7,15 +7,15 @@ from src.app.parsers.image_utils import crop_image, rotate_image from .config import config -ocr_space_key = config["cameras"][0]["ocr_space_key"] -ocr_gvision_key = config["cameras"][0]["ocr_gvision_key"] +ocr_space_key = config["cameras"][0]["ocr_space_key"] if "ocr_space_key" in config["cameras"][0] else None +ocr_gvision_key = config["cameras"][0]["ocr_gvision_key"] if "ocr_gvision_key" in config["cameras"][0] else None dir_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "results") def test_electricity_dials_1(request): samplepath = os.path.join(os.path.dirname(__file__), "images", 'sample_electricity-1.jpg') inputFrame = cv2.imread(samplepath) - reading = parse_dials(inputFrame, ["CCW", "CW", "CCW", "CW"], 0, request.node.name, minDiameter=150, maxDiameter=340, debug_path=dir_path) + reading = parse_dials(inputFrame, ["CCW", "CW", "CCW", "CW"], 0, request.node.name, minDiameter=300, maxDiameter=400, debug_path=dir_path) assert reading == 3124 @@ -64,8 +64,8 @@ def test_water_dial_2(request): def test_water_dial_3(request): samplepath = os.path.join(os.path.dirname(__file__), "images", 'sample_water-3.jpg') inputFrame = cv2.imread(samplepath) - reading = parse_dials(inputFrame, ["CW"], 0, request.node.name, minDiameter=1010, maxDiameter=1230, debug_path=dir_path) - assert reading == 5 + reading = parse_dials(inputFrame, ["CW"], 0, request.node.name, minDiameter=530, maxDiameter=700, debug_path=dir_path) + assert reading == 0 def test_water_digits_1(request): @@ -74,8 +74,9 @@ def test_water_digits_1(request): inputFrame = crop_image(inputFrame, [179, 155, 194, 50]) reading = parse_digits_ocr_space(inputFrame, 6, 1, ocr_space_key, request.node.name, debug_path=dir_path) assert reading == 2802.0 - reading = parse_digits_gvision(inputFrame, 6, 1, ocr_gvision_key, request.node.name, debug_path=dir_path) - assert reading == 2802.0 + if ocr_gvision_key: + reading = parse_digits_gvision(inputFrame, 6, 1, ocr_gvision_key, request.node.name, debug_path=dir_path) + assert reading == 2802.0 def test_water_digits_2(request): @@ -84,8 +85,9 @@ def test_water_digits_2(request): inputFrame = crop_image(inputFrame, [40, 144, 118, 50]) reading = parse_digits_ocr_space(inputFrame, 6, 2, ocr_space_key, request.node.name, debug_path=dir_path) assert reading == 1.10 - reading = parse_digits_gvision(inputFrame, 6, 2, ocr_gvision_key, request.node.name, debug_path=dir_path) - assert reading == 1.10 + if ocr_gvision_key: + reading = parse_digits_gvision(inputFrame, 6, 2, ocr_gvision_key, request.node.name, debug_path=dir_path) + assert reading == 1.10 def test_water_digits_3(request): @@ -94,17 +96,19 @@ def test_water_digits_3(request): inputFrame = crop_image(inputFrame, [451, 414, 372, 133]) reading = parse_digits_ocr_space(inputFrame, 6, 2, ocr_space_key, request.node.name, debug_path=dir_path) assert reading == 2.00 - reading = parse_digits_gvision(inputFrame, 6, 2, ocr_gvision_key, request.node.name, debug_path=dir_path) - assert reading == 2.00 + if ocr_gvision_key: + reading = parse_digits_gvision(inputFrame, 6, 2, ocr_gvision_key, request.node.name, debug_path=dir_path) + assert reading == 2.00 def test_water_rotate_crop(request): samplepath = os.path.join(os.path.dirname(__file__), "images", 'sample_water-rotate.jpg') inputFrame = cv2.imread(samplepath) inputFrame = rotate_image(inputFrame, -121) cv2.imwrite(os.path.join(dir_path, "%s-rotate.jpg" % request.node.name), inputFrame) - inputFrame = crop_image(inputFrame, [365, 405, 160, 60]) - reading = parse_digits_gvision(inputFrame, 6, 1, ocr_gvision_key, request.node.name, debug_path=dir_path) - assert reading >= 2806.0 and reading <= 2806.9 + inputFrame = crop_image(inputFrame, [450, 668, 160, 60]) + if ocr_gvision_key: + reading = parse_digits_gvision(inputFrame, 6, 1, ocr_gvision_key, request.node.name, debug_path=dir_path) + assert reading >= 2806.0 and reading <= 2806.9 reading = parse_digits_ocr_space(inputFrame, 6, 1, ocr_space_key, request.node.name, debug_path=dir_path) assert reading >= 2806.0 and reading <= 2806.9