Skip to content

Commit

Permalink
Removed unnecessary rendering code.
Browse files Browse the repository at this point in the history
The tool can now run in a plain console, with no UI.
  • Loading branch information
zlogic committed Apr 24, 2022
1 parent 09c2615 commit e8733b6
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 130 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,29 @@ Regular photos are not likely to work correctly, because regular cameras have pe

More information is available in the [Wiki](/zlogic/cybervision/wiki).

## How to use it

Download a release .whl file for your platform from [releases](/zlogic/cybervision/releases).

Install the .whl file by running:

```sheell
pip3 install <filename>.whl
```

Run cybervision:

```shell
python3 -m cybervision <img1.tif> <img2.tif> --output-file=<out.png> [--no-interpolate]
```

## Python version

Cybervision was rewritten in Python (with C extensions).

Originally, it was a full all-in-one tool built based on Qt and using a different approach.
For more details about the C++ version, see [Releases](releases).
The source code is available in the [branch_qt_sift](tree/branch_qt_sift) branch.
For more details about the C++ version, see [Releases](/zlogic/cybervision/releases).
The source code is available in the [branch_qt_sift](/zlogic/cybervision/tree/branch_qt_sift) branch.

The Python rewrite focuses on the primary goal - generating a 3D surface from an image stereopair;
anything else (like a UI) can be added separately.
Expand All @@ -34,4 +50,5 @@ anything else (like a UI) can be added separately.
## External libraries

* [fast](https://www.edwardrosten.com/work/fast.html) keypoint detector
* [scipy](https://scipy.org) and [numpy](https://numpy.org) for point interpolation
* [Pillow](https://python-pillow.org) image library
11 changes: 9 additions & 2 deletions cybervision/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ def main():
parser.add_argument('img1')
parser.add_argument('img2')
parser.add_argument('--resize-scale', type=float, default=1.0)
parser.add_argument('--interpolate', dest='interpolate', action='store_true')
parser.add_argument('--no-interpolate', dest='interpolate', action='store_false')
parser.set_defaults(interpolate=True)
parser.add_argument('--output-file', required=True)
args = parser.parse_args()

img1 = SEMImage(args.img1, args.resize_scale)
Expand All @@ -25,8 +29,11 @@ def main():
except NoMatchesFound as err:
sys.exit(err)

v = Visualiser(img1.img, img2.img, reconstructor.get_matches(), reconstructor.points3d)
v.show_results()
v = Visualiser(img1.img, img2.img, reconstructor.points3d)
if args.interpolate:
v.save_surface_image_interpolated(args.output_file)
else:
v.save_surface_image(args.output_file)


if __name__ == '__main__':
Expand Down
15 changes: 6 additions & 9 deletions cybervision/reconstruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,9 @@ def reconstruct(self):
raise NoMatchesFound('Failed to fit the model')

matches_count = len(self.matches)
if not self.keep_intermediate_results:
self.matches = []
self.points1 = []
self.points2 = []
del(self.matches)
del(self.points1)
del(self.points2)

time_completed_ransac = datetime.now()
self.log.info(f'Completed RANSAC fitting in {time_completed_ransac-time_completed_matching}')
Expand All @@ -151,9 +150,8 @@ def reconstruct(self):
self.points3d = self.create_surface()
w1 = self.img1.width
h1 = self.img1.height
if not self.keep_intermediate_results:
del(self.img1)
del(self.img2)
del(self.img1)
del(self.img2)

time_completed_surface = datetime.now()
self.log.info(f'Completed surface generation in {time_completed_surface-time_completed_ransac}')
Expand All @@ -180,12 +178,11 @@ def get_matches(self):
matches.append((p1[0], p1[1], p2[0], p2[1], corr))
return matches

def __init__(self, img1: Image, img2: Image, keep_intermediate_results=False):
def __init__(self, img1: Image, img2: Image):
self.img1 = img1.convert('L')
self.img2 = img2.convert('L')
self.log = logging.getLogger("reconstructor")
self.num_threads = os.cpu_count()
self.keep_intermediate_results = keep_intermediate_results

# Tunable parameters
self.fast_threshold = 15
Expand Down
113 changes: 12 additions & 101 deletions cybervision/visualisation.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from PIL import Image, ImageDraw

import math
import numpy as np
import numpy
import scipy.interpolate
import scipy.spatial
import matplotlib.pyplot as plt
import matplotlib.tri as mtri

# Sunset from https://jiffyclub.github.io/palettable/cartocolors/sequential/
COLORMAP = [
Expand Down Expand Up @@ -35,84 +33,7 @@ def map_color(self, p):
b = int(c2[2]*ratio+c1[2]*(1-ratio))
return (r, g, b)

def show_points(self):
size = (self.img1.width+self.img2.width, max(self.img1.height, self.img2.height))
composite = Image.new("RGBA", size, (255, 255, 255, 0))
composite.paste(self.img1)
composite.paste(self.img2, (self.img1.width, 0))
draw = ImageDraw.Draw(composite)
for m in self.matches:
point1 = (m[0], m[1])
point2 = (m[2]+self.img1.width, m[3])
draw.point(point1, fill=(255, 0, 0, 255))
draw.point(point2, fill=(255, 0, 0, 255))
composite.show()

def show_matches(self):
size = (self.img1.width+self.img2.width, max(self.img1.height, self.img2.height))
composite = Image.new("RGBA", size, (255, 255, 255, 0))
composite.paste(self.img1)
composite.paste(self.img2, (self.img1.width, 0))
draw = ImageDraw.Draw(composite)

for m in self.matches:
point1 = (m[0], m[1])
point2 = (m[2]+self.img1.width, m[3])
draw.line(point1 + point2, fill=(255, 0, 0, 255))
for m in self.matches:
draw.point(point1, fill=(0, 255, 0, 255))
draw.point(point2, fill=(0, 255, 0, 255))

composite.show()

def show_distances(self):
size = (self.img1.width+self.img2.width, max(self.img1.height, self.img2.height))
composite = Image.new("RGBA", size, (255, 255, 255, 0))
composite.paste(self.img1)
composite.paste(self.img2, (self.img1.width, 0))
draw = ImageDraw.Draw(composite)

for m in self.matches:
point11 = (m[0], m[1])
point12 = (m[2], m[3])
point21 = (m[0]+self.img1.width, m[1])
point22 = (m[2]+self.img1.width, m[3])
draw.line(point11 + point12, fill=(255, 0, 0, 255))
draw.line(point21 + point22, fill=(255, 0, 0, 255))
draw.point(point11, fill=(0, 255, 0, 255))
draw.point(point12, fill=(0, 255, 0, 255))
draw.point(point21, fill=(0, 255, 0, 255))
draw.point(point22, fill=(0, 255, 0, 255))

composite.show()

def show_surface_plot(self):
x_coords = np.linspace(0, self.img1.width, self.img1.width, endpoint=False)
y_coords = np.linspace(0, self.img1.height, self.img1.height, endpoint=False)
xx, yy = np.meshgrid(x_coords, y_coords)
xy_coords = [(p[0], self.img1.height-p[1]) for p in self.points3d]
z_values = [p[2] for p in self.points3d]
interp_grid = scipy.interpolate.griddata(xy_coords, z_values, (xx, yy), method='linear')

ax = plt.axes(projection='3d')
ax.plot_surface(xx, yy, interp_grid, rcount=100, ccount=100, shade=True, cmap='jet')
plt.show()

def show_surface_mesh(self):
# Too slow without resampling
x = [p[0] for p in self.points3d]
y = [self.img1.height-p[1] for p in self.points3d]
xy_points = [[p[0], self.img1.height-p[1]] for p in self.points3d]
z_values = [p[2] for p in self.points3d]

mesh = scipy.spatial.Delaunay(xy_points)
triang = mtri.Triangulation(x=x, y=y, triangles=mesh.vertices)

ax = plt.axes(projection='3d')
ax.plot_trisurf(triang, z_values, cmap='jet', shade=False)
plt.show()

def show_surface_image(self):
def save_surface_image(self, filename):
surface = Image.new("RGBA", self.img1.size, (0, 0, 0, 0))
min_z = min(self.points3d, key=lambda p: p[2])[2]
max_z = max(self.points3d, key=lambda p: p[2])[2]
Expand All @@ -122,39 +43,29 @@ def show_surface_image(self):
(r, g, b) = self.map_color(z)
draw.point((p[0], p[1]), fill=(r, g, b, 255))

surface.show()
surface.save(filename)

def show_surface_image_interpolated(self):
def save_surface_image_interpolated(self, filename):
xy_points = [(p[0], p[1]) for p in self.points3d]
x_coords = np.linspace(0, self.img1.width, self.img1.width, endpoint=False)
y_coords = np.linspace(0, self.img1.height, self.img1.height, endpoint=False)
xx, yy = np.meshgrid(x_coords, y_coords)
x_coords = numpy.linspace(0, self.img1.width, self.img1.width, endpoint=False)
y_coords = numpy.linspace(0, self.img1.height, self.img1.height, endpoint=False)
xx, yy = numpy.meshgrid(x_coords, y_coords)
z_values = [p[2] for p in self.points3d]
depth_values = scipy.interpolate.griddata(xy_points, z_values, (xx, yy), method='linear')

surface = Image.new("RGBA", self.img1.size, (0, 0, 0, 0))
min_z = np.nanmin(depth_values)
max_z = np.nanmax(depth_values)
for (ix, iy), z in np.ndenumerate(depth_values.T):
min_z = numpy.nanmin(depth_values)
max_z = numpy.nanmax(depth_values)
for (ix, iy), z in numpy.ndenumerate(depth_values.T):
if not math.isnan(z):
x = round(x_coords[ix])
y = round(y_coords[iy])
z = (z-min_z)/(max_z-min_z)
(r, g, b) = self.map_color(z)
surface.putpixel((x, y), (r, g, b, 255))
surface.show()

def show_results(self):
# self.show_points()
# self.show_matches()
# self.show_distances()
# self.show_surface_plot()
# self.show_surface_mesh()
self.show_surface_image()
self.show_surface_image_interpolated()
surface.save(filename)

def __init__(self, img1: Image, img2: Image, matches, points3d):
def __init__(self, img1: Image, img2: Image, points3d):
self.img1 = img1
self.img2 = img2
self.matches = matches
self.points3d = points3d
8 changes: 0 additions & 8 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
cycler==0.11.0
fonttools==4.32.0
kiwisolver==1.4.2
matplotlib==3.5.1
numpy==1.22.3
packaging==21.3
Pillow==9.1.0
pyparsing==3.0.8
python-dateutil==2.8.2
scipy==1.8.0
six==1.16.0
12 changes: 4 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import sys
from setuptools import setup, Extension
from glob import glob

extra_compile_args = []
sources = [
'machine/cybervision.c',
'machine/correlation.c',
'machine/fast/fast_9.c',
'machine/fast/fast_10.c',
'machine/fast/fast_11.c',
'machine/fast/fast_12.c',
'machine/fast/fast.c',
'machine/fast/nonmax.c'
'machine/correlation.c'
]

if sys.platform in ['darwin', 'linux']:
extra_compile_args.append('-pthread')
elif sys.platform == 'win32':
sources.append('machine/win32/pthread.c')

sources = sources + glob('machine/fast/*.c')

machine = Extension(
'cybervision.machine',
sources=sources,
Expand Down Expand Up @@ -51,7 +48,6 @@
setup_requires=['wheel'],
install_requires=[
"Pillow>=9.1.0",
"matplotlib>=3.5.1",
"scipy>=1.8.0",
"numpy>=1.22.3"
]
Expand Down

0 comments on commit e8733b6

Please sign in to comment.