-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
443 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,6 +63,8 @@ | |
infield_correction, | ||
Matrix4x4, | ||
data_model, | ||
projection, | ||
ProjectedImage, | ||
) | ||
except ImportError as ex: | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
"""Module for experimental projection features. This API may change in the future.""" | ||
|
||
import _zivid | ||
from zivid.frame_2d import Frame2D | ||
from zivid.settings_2d import Settings2D, _to_internal_settings2d | ||
|
||
|
||
class ProjectedImage: | ||
"""A handle to a 2D image being displayed on Zivid camera's projector. | ||
The image projection will stop either when the instance is destroyed, when the stop() method is called, | ||
or when exiting the "with" block if used as a context manager. | ||
""" | ||
|
||
def __init__(self, impl): | ||
"""Initialize ProjectedImage wrapper. | ||
This constructor is only used internally, and should not be called by the end-user. | ||
Args: | ||
impl: Reference to internal/back-end instance. | ||
Raises: | ||
TypeError: If argument does not match the expected internal class. | ||
""" | ||
if isinstance(impl, _zivid.ProjectedImage): | ||
self.__impl = impl | ||
else: | ||
raise TypeError( | ||
"Unsupported type for argument impl. Got {}, expected {}.".format( | ||
type(impl), type(_zivid.ProjectedImage) | ||
) | ||
) | ||
|
||
def __str__(self): | ||
return str(self.__impl) | ||
|
||
def capture(self, settings2d): | ||
"""Capture a single 2D frame without stopping the ongoing image projection. | ||
This function can only be used with a zero-brightness 2D capture, otherwise it | ||
will interfere with the projected image. An exception will be thrown if settings | ||
contains brightness > 0. | ||
Args: | ||
settings2d: A Settings2D instance to be used for 2D capture. | ||
Returns: | ||
A Frame2D containing a 2D image plus metadata. | ||
Raises: | ||
RuntimeError: If the Settings2D contains an acquisition with brightness != 0 | ||
""" | ||
|
||
if isinstance(settings2d, Settings2D): | ||
return Frame2D(self.__impl.capture(_to_internal_settings2d(settings2d))) | ||
raise TypeError("Unsupported settings type: {}".format(type(settings2d))) | ||
|
||
def stop(self): | ||
"""Stop the ongoing image projection""" | ||
self.__impl.stop() | ||
|
||
def active(self): | ||
"""Check if a handle is associated with an ongoing image projection. | ||
Returns: | ||
A boolean indicating projection state. | ||
""" | ||
return self.__impl.active() | ||
|
||
def release(self): | ||
"""Release the underlying resources and stop projection.""" | ||
try: | ||
impl = self.__impl | ||
except AttributeError: | ||
pass | ||
else: | ||
impl.release() | ||
|
||
def __enter__(self): | ||
return self | ||
|
||
def __exit__(self, exception_type, exception_value, traceback): | ||
self.release() | ||
|
||
def __del__(self): | ||
self.release() | ||
|
||
|
||
def projector_resolution(camera): | ||
"""Get the resolution of the internal projector in the Zivid camera | ||
Args: | ||
camera: The Camera instance to get the projector resolution of. | ||
Returns: | ||
The resolution as a tuple (height,width). | ||
""" | ||
return _zivid.projection.projector_resolution( | ||
camera._Camera__impl # pylint: disable=protected-access | ||
) | ||
|
||
|
||
def show_image(camera, image_bgra): | ||
"""Display a 2D color image using the projector. | ||
This function returns a ProjectedImage instance. Projection will continue until this object is | ||
destroyed or if its stop() method is called. Alternatively it can be used as a context manager, | ||
in which case the projection will stop when exiting the "with" block. | ||
Args: | ||
camera: The Camera instance to project with. | ||
image_bgra: The image to project in the form of a HxWx4 numpy array with BGRA colors. | ||
Returns: | ||
A handle in the form of a ProjectedImage instance. | ||
""" | ||
|
||
return ProjectedImage( | ||
_zivid.projection.show_image( | ||
camera._Camera__impl, # pylint: disable=protected-access | ||
image_bgra, | ||
) | ||
) | ||
|
||
|
||
def pixels_from_3d_points(camera, points): | ||
"""Get 2D projector pixel coordinates corresponding to 3D points relative to the camera. | ||
This function takes 3D points in the camera's reference frame and converts them to the | ||
projector's 2D (XY) frame using the internal calibration of a Zivid camera. The returned | ||
points maps to projector pixels as round(X)->column, round(Y)->row. | ||
Args: | ||
camera: The Camera instance that the 3D points are in the frame of. | ||
points: A list of 3D (XYZ) points (List[List[float[3]]]) | ||
Returns: | ||
The corresponding 2D (XY) points in the projector (List[List[float[2]]]) | ||
""" | ||
|
||
return _zivid.projection.pixels_from_3d_points( | ||
camera._Camera__impl, # pylint: disable=protected-access | ||
points, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
from zivid import Application, Settings, Settings2D | ||
from zivid.calibration import detect_feature_points | ||
from zivid.experimental.projection import ( | ||
projector_resolution, | ||
show_image, | ||
pixels_from_3d_points, | ||
) | ||
|
||
import numpy as np | ||
from datetime import timedelta | ||
|
||
|
||
def _detect_checkerboard(camera): | ||
print(f"Detecting checkerboard...") | ||
settings = Settings() | ||
settings.acquisitions.append(Settings.Acquisition()) | ||
with camera.capture(settings) as frame: | ||
detection_result = detect_feature_points(frame.point_cloud()) | ||
if not detection_result.valid(): | ||
raise RuntimeError("Failed to detect checkerboard") | ||
print("Successfully detected checkerboard") | ||
return detection_result | ||
|
||
|
||
def _create_image_bgra_array(camera, detection_result): | ||
resolution = projector_resolution(camera) | ||
print(f"Projector resolution: {resolution}") | ||
|
||
channels_bgra = 4 | ||
resolution_bgra = resolution + (channels_bgra,) | ||
image_bgra = np.zeros(resolution_bgra, dtype=np.uint8) | ||
|
||
# Draw frame around projector FOV | ||
image_bgra[:5, :, :] = 255 | ||
image_bgra[-5:, :, :] = 255 | ||
image_bgra[:, :5, :] = 255 | ||
image_bgra[:, -5:, :] = 255 | ||
|
||
# Draw circle at checkerboard centroid | ||
centroid_xyz = list(detection_result.centroid()) | ||
print(f"Located checkerboard at xyz={centroid_xyz}") | ||
centroid_projector_xy = pixels_from_3d_points(camera, [centroid_xyz])[0] | ||
print(f"Projector coords (x,y) corresponding to centroid: {centroid_projector_xy}") | ||
col = round(centroid_projector_xy[0]) | ||
row = round(centroid_projector_xy[1]) | ||
print(f"Projector pixel corresponding to centroid: row={row}, col={col}") | ||
for i in np.arange(-10, 10, 1): | ||
for j in np.arange(-10, 10, 1): | ||
dist = np.sqrt(i**2 + j**2) | ||
if dist <= 5: | ||
color = (0, 0, 255, 0) | ||
elif dist > 5 and dist < 7: | ||
color = (255, 255, 255, 0) | ||
else: | ||
color = (0, 0, 0, 0) | ||
image_bgra[row + i, col + j, :] = color | ||
|
||
return image_bgra | ||
|
||
|
||
def _capture_image_of_projection(projected_image): | ||
settings_2d = Settings2D() | ||
settings_2d.acquisitions.append( | ||
Settings2D.Acquisition( | ||
brightness=0.0, | ||
exposure_time=timedelta(milliseconds=50), | ||
) | ||
) | ||
settings_2d.processing.color.gamma = 0.75 | ||
|
||
with projected_image.capture(settings_2d) as frame2d: | ||
filename = "projection_picture.png" | ||
print(f"Saving image of projection to {filename}") | ||
frame2d.image_rgba().save(filename) | ||
|
||
|
||
def _main(): | ||
app = Application() | ||
|
||
print("Connecting to camera...") | ||
with app.connect_camera() as camera: | ||
detection_result = _detect_checkerboard(camera) | ||
image_bgra = _create_image_bgra_array(camera, detection_result) | ||
|
||
with show_image(camera, image_bgra) as projected_image: | ||
_capture_image_of_projection(projected_image) | ||
input("Press enter to stop projection") | ||
|
||
|
||
if __name__ == "__main__": | ||
_main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
|
||
#include <ZividPython/Projection.h> | ||
|
||
#include <Zivid/Experimental/Projection.h> | ||
#include <Zivid/Resolution.h> | ||
#include <ZividPython/ReleasableCamera.h> | ||
#include <ZividPython/ReleasableProjectedImage.h> | ||
|
||
#include <array> | ||
#include <vector> | ||
|
||
namespace ZividPython::Projection | ||
{ | ||
void wrapAsSubmodule(pybind11::module &dest) | ||
{ | ||
dest.def("projector_resolution", [](const ReleasableCamera &camera) { | ||
const auto resolution = Zivid::Experimental::Projection::projectorResolution(camera.impl()); | ||
return std::make_pair(resolution.height(), resolution.width()); | ||
}); | ||
|
||
dest.def( | ||
"show_image", | ||
[](ReleasableCamera &camera, | ||
const pybind11::array_t<uint8_t, pybind11::array::c_style | pybind11::array::forcecast> imageBGRA) { | ||
const auto info = imageBGRA.request(); | ||
|
||
if(info.ndim != 3) | ||
{ | ||
throw std::runtime_error("Input image array must be three dimensional."); | ||
} | ||
|
||
const auto height = info.shape[0]; | ||
const auto width = info.shape[1]; | ||
const auto channels = info.shape[2]; | ||
|
||
if(channels != 4) | ||
{ | ||
throw std::runtime_error("Input image array must have four color channels (BGRA)."); | ||
} | ||
|
||
const auto resolution = Zivid::Resolution{ static_cast<size_t>(width), static_cast<size_t>(height) }; | ||
const auto *dataPtr = reinterpret_cast<const Zivid::ColorBGRA *>(imageBGRA.data()); | ||
const Zivid::Image<Zivid::ColorBGRA> zividImage{ resolution, dataPtr, dataPtr + resolution.size() }; | ||
auto projectedImage = Zivid::Experimental::Projection::showImage(camera.impl(), zividImage); | ||
return ZividPython::ReleasableProjectedImage(std::move(projectedImage)); | ||
}); | ||
|
||
dest.def("pixels_from_3d_points", | ||
[](const ReleasableCamera &camera, const std::vector<std::array<float, 3>> points) { | ||
auto pointsInternal = std::vector<Zivid::PointXYZ>(); | ||
pointsInternal.reserve(points.size()); | ||
std::transform(points.begin(), | ||
points.end(), | ||
std::back_inserter(pointsInternal), | ||
[](const auto &point) { | ||
return Zivid::PointXYZ{ point[0], point[1], point[2] }; | ||
}); | ||
|
||
const auto outputInternal = | ||
Zivid::Experimental::Projection::pixelsFrom3DPoints(camera.impl(), pointsInternal); | ||
|
||
auto output = std::vector<std::array<float, 2>>(); | ||
output.reserve(outputInternal.size()); | ||
std::transform(outputInternal.begin(), | ||
outputInternal.end(), | ||
std::back_inserter(output), | ||
[](const auto &pointxy) { | ||
return std::array<float, 2>{ pointxy.x, pointxy.y }; | ||
}); | ||
return output; | ||
}); | ||
} | ||
} // namespace ZividPython::Projection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#include <ZividPython/ReleasableProjectedImage.h> | ||
|
||
#include <pybind11/pybind11.h> | ||
|
||
namespace py = pybind11; | ||
|
||
namespace ZividPython | ||
{ | ||
void wrapClass(pybind11::class_<ReleasableProjectedImage> pyClass) | ||
{ | ||
pyClass.def("stop", &ReleasableProjectedImage::stop) | ||
.def("active", &ReleasableProjectedImage::active) | ||
.def("capture", &ReleasableProjectedImage::capture); | ||
} | ||
} // namespace ZividPython |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#pragma once | ||
|
||
#include <ZividPython/Wrappers.h> | ||
|
||
#include <pybind11/numpy.h> | ||
#include <pybind11/pybind11.h> | ||
|
||
namespace ZividPython::Projection | ||
{ | ||
void wrapAsSubmodule(pybind11::module &dest); | ||
} // namespace ZividPython::Projection |
Oops, something went wrong.