Skip to content

Commit

Permalink
Expand image when rotating to re-read ArUco markers
Browse files Browse the repository at this point in the history
  • Loading branch information
junalmeida committed Nov 4, 2022
1 parent dcfd573 commit 89b591b
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 31 deletions.
24 changes: 20 additions & 4 deletions meterparser/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
- Initial version.
2 changes: 1 addition & 1 deletion meterparser/config.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
34 changes: 22 additions & 12 deletions meterparser/src/app/parsers/image_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
32 changes: 18 additions & 14 deletions meterparser/tests/test_parse_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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

0 comments on commit 89b591b

Please sign in to comment.