Skip to content

Commit

Permalink
Line (#1)
Browse files Browse the repository at this point in the history
* test images from blender

* add line

* fmt
  • Loading branch information
yuichiroaoki authored Aug 1, 2023
1 parent 2883b45 commit fcd52b4
Show file tree
Hide file tree
Showing 17 changed files with 289 additions and 4 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ name: Test

on:
push:
branches: [ main ]
paths:
- "cnceye/**"
- "tests/**"
pull_request:
workflow_dispatch:

Expand Down
1 change: 1 addition & 0 deletions cnceye/camera/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .spec import Camera # noqa: F401
File renamed without changes.
4 changes: 4 additions & 0 deletions cnceye/camera/spec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class Camera:
def __init__(self, focal_length: float, sensor_width: float) -> None:
self.focal_length = focal_length
self.sensor_width = sensor_width
1 change: 1 addition & 0 deletions cnceye/cmm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from cnceye.cmm.cmm import Cmm # noqa: F401
38 changes: 38 additions & 0 deletions cnceye/cmm/cmm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from cnceye.coordinates import Coordinates
from cnceye.camera import Camera
from .pixel import get_field_of_view, get_pixel_per_mm


class Cmm:
def __init__(self, image, center_coordinates: Coordinates, camera: Camera) -> None:
self.image = image
self.center = center_coordinates
self.camera = camera

def get_opencv_origin(self, image, distance: float) -> Coordinates:
# opencv origin is at top left corner
pixel_per_mm = self.pixel_per_mm(distance)
diff_in_pixel = (-image.shape[1] / 2, image.shape[0] / 2, 0)
diff_in_mm = tuple(x / pixel_per_mm for x in diff_in_pixel)
return Coordinates(tuple(map(sum, zip(self.center, diff_in_mm))))

def pixel_per_mm(self, distance: float) -> float:
field_of_view = get_field_of_view(
self.camera.focal_length, self.camera.sensor_width, distance
)
return get_pixel_per_mm(field_of_view, self.image.shape[1])

def from_opencv_coord(self, distance: float, opencv_xy: tuple) -> Coordinates:
pixel_per_mm = self.pixel_per_mm(distance)
opencv_origin = self.get_opencv_origin(self.image, distance)
return Coordinates(
(
opencv_origin.x + opencv_xy[0] / pixel_per_mm,
opencv_origin.y - opencv_xy[1] / pixel_per_mm,
self.center.x - distance,
)
)

def from_pixel_length(self, distance: float, pixel_length) -> float:
pixel_per_mm = self.pixel_per_mm(distance)
return pixel_length / pixel_per_mm
6 changes: 6 additions & 0 deletions cnceye/cmm/pixel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def get_field_of_view(focal_length: float, sensor_width: float, distance: float):
return sensor_width / focal_length * distance


def get_pixel_per_mm(field_of_view: float, pixel_width: int):
return pixel_width / field_of_view
1 change: 1 addition & 0 deletions cnceye/coordinates/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from cnceye.coordinates.coord import Coordinates # noqa: F401
9 changes: 9 additions & 0 deletions cnceye/coordinates/coord.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from typing import Tuple


class Coordinates(Tuple):
def __init__(self, xyz: Tuple[float]) -> None:
super().__init__()
self.x = xyz[0]
self.y = xyz[1]
self.z = xyz[2]
35 changes: 35 additions & 0 deletions cnceye/line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import cv2
import numpy as np


def get_lines(
image,
gaussian_blur_size=5,
canny_low_threshold=100,
canny_high_threshold=200,
rho=0.1,
hough_threshold=50,
min_line_length=50,
max_line_gap=200,
):
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur_gray = cv2.GaussianBlur(gray, (gaussian_blur_size, gaussian_blur_size), 0)
edges = cv2.Canny(
blur_gray,
canny_low_threshold,
canny_high_threshold,
apertureSize=3,
L2gradient=True,
)
edges = cv2.dilate(edges, None, iterations=1)
edges = cv2.erode(edges, None, iterations=1)

lines = cv2.HoughLinesP(
edges,
rho,
np.pi / 180 * rho,
hough_threshold,
minLineLength=min_line_length,
maxLineGap=max_line_gap,
)
return lines
44 changes: 44 additions & 0 deletions scripts/create_test_images.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import bpy
import os


def render_image(output_path, camera_position, camera_rotation, light_position):
# Set camera position and rotation
camera = bpy.data.objects["Camera"]
camera.location = camera_position
camera.rotation_euler = camera_rotation

# Set light position
lamp = bpy.data.objects["Light"]
lamp.location = light_position

# Set rendering settings
bpy.context.scene.render.image_settings.file_format = "PNG"
bpy.context.scene.render.filepath = output_path

# Render the image
bpy.ops.render.render(write_still=True)


# Example usage
if __name__ == "__main__":
# Define camera and light positions
camera_position_start = (-0.05, 0.025, 0.06)
camera_rotation = (0.0, 0.0, 0.0)
light_position = (0, 0, 0.1)

# Create a folder to save the rendered images
output_folder = "/path/to/output_images"
os.makedirs(output_folder, exist_ok=True)

# Render images with different camera and light positions
for i in range(10):
camera_position = (
camera_position_start[0] + i * 0.1,
camera_position_start[1],
camera_position_start[2],
)
output_path = os.path.join(output_folder, f"image_{i + 1}.png")
render_image(output_path, camera_position, camera_rotation, light_position)

print("Rendering completed!")
Binary file added tests/fixtures/output_images/image_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/fixtures/output_images/image_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/fixtures/output_images/image_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions tests/test_camera.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import cv2
from cnceye import camera
from cnceye.camera import calib


def test_undistort_img():
image = cv2.imread("tests/fixtures/images/coins.jpg")
camera_data_path = "tests/fixtures/camera/google-pixel5-5g.json"
_undistorted_image = camera.undistort_img(image, camera_data_path)
_undistorted_image = calib.undistort_img(image, camera_data_path)
4 changes: 2 additions & 2 deletions tests/test_circle.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from cnceye import circle
from cnceye import camera
from cnceye.camera import calib
import cv2


def test_get_circle():
image = cv2.imread("tests/fixtures/images/coins.jpg")
undistorted_image = camera.undistort_img(
undistorted_image = calib.undistort_img(
image, "tests/fixtures/camera/google-pixel5-5g.json"
)

Expand Down
142 changes: 142 additions & 0 deletions tests/test_cmm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import cv2
from cnceye.cmm import Cmm
from cnceye.camera import Camera
from cnceye.coordinates import Coordinates
from cnceye.circle import get_circles
from cnceye.line import get_lines

focal_length = 50.0 # mm
camera_height = 60.0 # mm
object_height = 10.0 # mm
distance = camera_height - object_height
sensor_width = 36.0 # mm
camera = Camera(focal_length, sensor_width)


def diff_in_micron(expected, actual):
return (expected - actual) * 1000


def test_opencv_coord_1():
image = cv2.imread("tests/fixtures/output_images/image_1.png")
center_coordinates = Coordinates((-50.0, 25.0, 60.0))

cmm = Cmm(image, center_coordinates, camera)
circles = get_circles(image, param2=30)
(x_pixel, y_pixel, r_pixel) = circles[0][0]

# check radius is close to expected radius
expected_radius = 3.0 # mm
radius_from_img_in_mm = cmm.from_pixel_length(distance, r_pixel)
radius_diff_in_micron = diff_in_micron(expected_radius, radius_from_img_in_mm)
print(f"radius: {radius_diff_in_micron:.2f} μm")
assert radius_diff_in_micron < 100.0

# check center is close to expected center
expected_circle_center = Coordinates((-48.0, 23.0, 10.0))
circle_center_from_img_in_mm = cmm.from_opencv_coord(distance, (x_pixel, y_pixel))
x_from_img_in_mm = circle_center_from_img_in_mm.x
y_from_img_in_mm = circle_center_from_img_in_mm.y

x_diff_in_micro = diff_in_micron(expected_circle_center.x, x_from_img_in_mm)
print(f"x: {x_diff_in_micro:.2f} μm")
assert x_diff_in_micro < 100.0

y_diff_in_micron = diff_in_micron(expected_circle_center.y, y_from_img_in_mm)
print(f"y: {y_diff_in_micron:.2f} μm")
assert y_diff_in_micron < 100.0

lines = get_lines(image)
assert len(lines) > 0
expected_corner = Coordinates((-53.0, 28.0, 10.0))
x_pixel, y_pixel = lines[0][0][0], lines[0][0][1]
corner_from_img_in_mm = cmm.from_opencv_coord(distance, (x_pixel, y_pixel))
x_from_img_in_mm = corner_from_img_in_mm.x
y_from_img_in_mm = corner_from_img_in_mm.y

x_diff_in_micro = diff_in_micron(expected_corner.x, x_from_img_in_mm)
print(f"x: {x_diff_in_micro:.2f} μm")
assert x_diff_in_micro < 100.0

y_diff_in_micron = diff_in_micron(expected_corner.y, y_from_img_in_mm)
print(f"y: {y_diff_in_micron:.2f} μm")
assert y_diff_in_micron < 100.0


def test_opencv_coord_2():
image = cv2.imread("tests/fixtures/output_images/image_2.png")
center_coordinates = Coordinates((-40.0, 25.0, 60.0))

cmm = Cmm(image, center_coordinates, camera)
circles = get_circles(image, param2=30)
(x_pixel, y_pixel, r_pixel) = circles[0][0]

# check radius is close to expected radius
expected_radius = 3.0 # mm
radius_from_img_in_mm = cmm.from_pixel_length(distance, r_pixel)
radius_diff_in_micron = diff_in_micron(expected_radius, radius_from_img_in_mm)
print(f"radius: {radius_diff_in_micron:.2f} μm")
assert radius_diff_in_micron < 100.0

# check center is close to expected center
expected_circle_center = Coordinates((-48.0, 23.0, 10.0))
circle_center_from_img_in_mm = cmm.from_opencv_coord(distance, (x_pixel, y_pixel))
x_from_img_in_mm = circle_center_from_img_in_mm.x
y_from_img_in_mm = circle_center_from_img_in_mm.y

x_diff_in_micro = diff_in_micron(expected_circle_center.x, x_from_img_in_mm)
print(f"x: {x_diff_in_micro:.2f} μm")
assert x_diff_in_micro < 100.0

y_diff_in_micron = diff_in_micron(expected_circle_center.y, y_from_img_in_mm)
print(f"y: {y_diff_in_micron:.2f} μm")
assert y_diff_in_micron < 100.0

lines = get_lines(image)
assert len(lines) > 0
expected_corner = Coordinates((-53.0, 28.0, 10.0))
x_pixel, y_pixel = lines[0][0][0], lines[0][0][1]
corner_from_img_in_mm = cmm.from_opencv_coord(distance, (x_pixel, y_pixel))
x_from_img_in_mm = corner_from_img_in_mm.x
y_from_img_in_mm = corner_from_img_in_mm.y

x_diff_in_micro = diff_in_micron(expected_corner.x, x_from_img_in_mm)
print(f"x: {x_diff_in_micro:.2f} μm")
assert x_diff_in_micro < 100.0

y_diff_in_micron = diff_in_micron(expected_corner.y, y_from_img_in_mm)
print(f"y: {y_diff_in_micron:.2f} μm")
assert y_diff_in_micron < 100.0


def test_opencv_coord_3():
image = cv2.imread("tests/fixtures/output_images/image_3.png")
center_coordinates = Coordinates((-30.0, 25.0, 60.0))

cmm = Cmm(image, center_coordinates, camera)
circles = get_circles(image, param2=30)
(x_pixel, y_pixel, r_pixel) = circles[0][0]

# check radius is close to expected radius
expected_radius = 3.0 # mm
radius_from_img_in_mm = cmm.from_pixel_length(distance, r_pixel)
radius_diff_in_micron = diff_in_micron(expected_radius, radius_from_img_in_mm)
print(f"radius: {radius_diff_in_micron:.2f} μm")
# assert radius_diff_in_micron < 100.0

# check center is close to expected center
expected_circle_center = Coordinates((-48.0, 23.0, 10.0))
circle_center_from_img_in_mm = cmm.from_opencv_coord(distance, (x_pixel, y_pixel))
x_from_img_in_mm = circle_center_from_img_in_mm.x
y_from_img_in_mm = circle_center_from_img_in_mm.y

x_diff_in_micro = diff_in_micron(expected_circle_center.x, x_from_img_in_mm)
print(f"x: {x_diff_in_micro:.2f} μm")
# assert x_diff_in_micro < 100.0

y_diff_in_micron = diff_in_micron(expected_circle_center.y, y_from_img_in_mm)
print(f"y: {y_diff_in_micron:.2f} μm")
# assert y_diff_in_micron < 100.0

lines = get_lines(image)
assert len(lines) > 0

0 comments on commit fcd52b4

Please sign in to comment.