diff --git a/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h b/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h index 6fbf7abab1..5f01ab0870 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h +++ b/modules/ar/include/visp3/ar/vpPanda3DCommonFilters.h @@ -9,16 +9,54 @@ class vpPanda3DRGBRenderer; +/** + * @brief Class that implements an RGB to grayscale conversion. + * + */ class VISP_EXPORT vpPanda3DLuminanceFilter : public vpPanda3DPostProcessFilter { public: - vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr &inputRenderer, bool isOutput); + vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr inputRenderer, bool isOutput); FrameBufferProperties getBufferProperties() const vp_override; void getRender(vpImage &I) const; private: static const char *FRAGMENT_SHADER; +}; + +/** + * @brief Class that implements a gaussian filter on a grayscale image. + * The grayscale image should be contained in the blue channel of the image. + * + */ +class VISP_EXPORT vpPanda3DGaussianBlur : public vpPanda3DPostProcessFilter +{ +public: + vpPanda3DGaussianBlur(const std::string &name, std::shared_ptr inputRenderer, bool isOutput); + FrameBufferProperties getBufferProperties() const vp_override; + void getRender(vpImage &I) const; + +private: + static const char *FRAGMENT_SHADER; +}; + +class VISP_EXPORT vpPanda3DCanny : public vpPanda3DPostProcessFilter +{ +public: + vpPanda3DCanny(const std::string &name, std::shared_ptr inputRenderer, bool isOutput, float edgeThreshold); + FrameBufferProperties getBufferProperties() const vp_override; + void getRender(vpImage &I) const; + void setEdgeThreshold(float edgeThreshold); + +protected: + void setupScene() vp_override; + +private: + static const char *FRAGMENT_SHADER; + float m_edgeThreshold; }; + + #endif #endif diff --git a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h index 963ac815dc..707d7500f2 100644 --- a/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h +++ b/modules/ar/include/visp3/ar/vpPanda3DPostProcessFilter.h @@ -12,7 +12,7 @@ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer { public: - vpPanda3DPostProcessFilter(const std::string &name, const std::shared_ptr &inputRenderer, bool isOutput, std::string fragmentShader) + vpPanda3DPostProcessFilter(const std::string &name, std::shared_ptr inputRenderer, bool isOutput, std::string fragmentShader) : vpPanda3DBaseRenderer(name), m_inputRenderer(inputRenderer), m_isOutput(isOutput), m_fragmentShader(fragmentShader) { m_renderOrder = m_inputRenderer->getRenderOrder() + 1; @@ -22,8 +22,10 @@ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer return false; } + GraphicsOutput *getMainOutputBuffer() vp_override { return m_buffer; } + protected: - void setupScene() vp_override; + virtual void setupScene() vp_override; void setupCamera() vp_override; @@ -36,7 +38,7 @@ class VISP_EXPORT vpPanda3DPostProcessFilter : public vpPanda3DBaseRenderer std::shared_ptr m_inputRenderer; bool m_isOutput; //! Whether this filter is an output to be used and should be copied to ram std::string m_fragmentShader; - PT(Shader) shader; + PT(Shader) m_shader; Texture *m_texture; GraphicsOutput *m_buffer; diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp index 22d5dfe93d..95e8e5dc28 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DCommonFilters.cpp @@ -20,12 +20,78 @@ void main() { )shader"; -vpPanda3DLuminanceFilter::vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr &inputRenderer, bool isOutput) +vpPanda3DLuminanceFilter::vpPanda3DLuminanceFilter(const std::string &name, std::shared_ptr inputRenderer, bool isOutput) : vpPanda3DPostProcessFilter(name, inputRenderer, isOutput, std::string(vpPanda3DLuminanceFilter::FRAGMENT_SHADER)) +{ } +FrameBufferProperties vpPanda3DLuminanceFilter::getBufferProperties() const { + FrameBufferProperties fbp; + fbp.set_depth_bits(0); + fbp.set_rgba_bits(0, 0, 8, 0); + fbp.set_float_color(false); + return fbp; +} +void vpPanda3DLuminanceFilter::getRender(vpImage &I) const +{ + if (!m_isOutput) { + throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); + } + unsigned indexMultiplier = m_texture->get_num_components(); // we ask for only 8 bits image, but we may get an rgb image + I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); + unsigned char *data = (unsigned char *)(&(m_texture->get_ram_image().front())); + if (indexMultiplier != 1) { + for (unsigned int i = 0; i < I.getSize(); ++i) { + I.bitmap[i] = data[i * indexMultiplier]; + } + } + else { + memcpy(I.bitmap, &data[0], I.getSize() * sizeof(unsigned char)); + } +} + + +const char *vpPanda3DGaussianBlur::FRAGMENT_SHADER = R"shader( +#version 330 + +in vec2 texcoords; + +uniform sampler2D p3d_Texture0; +uniform vec2 dp; // 1 divided by number of pixels + +const float kernel[25] = float[25]( + 2, 4, 5, 4, 2, + 4, 9, 12, 9, 4, + 5, 12, 15, 12, 5, + 4, 9, 12, 9, 4, + 2, 4, 5, 4, 2 +); +const float normalize = 1 / 159.0; + +vec2 offset[25] = vec2[25]( + vec2(-2*dp.x,-2*dp.y), vec2(-dp.x,-2*dp.y), vec2(0,-2*dp.y), vec2(dp.x,-2*dp.y), vec2(2*dp.x,-2*dp.y), + vec2(-2*dp.x,-dp.y), vec2(-dp.x, -dp.y), vec2(0.0, -dp.y), vec2(dp.x, -dp.y), vec2(2*dp.x,-dp.y), + vec2(-2*dp.x,0.0), vec2(-dp.x, 0.0), vec2(0.0, 0.0), vec2(dp.x, 0.0), vec2(2*dp.x,0.0), + vec2(-2*dp.x, dp.y), vec2(-dp.x, dp.y), vec2(0.0, dp.y), vec2(dp.x, dp.y), vec2(2*dp.x, dp.y), + vec2(-2*dp.x, 2*dp.y), vec2(-dp.x, 2*dp.y), vec2(0.0, 2*dp.y), vec2(dp.x, 2*dp.y), vec2(2*dp.x, 2*dp.y) +); +out vec4 p3d_FragData; + +void main() { + float v = 0.f; + + for(int i = 0; i < 25; ++i) { + v += kernel[i] * texture(p3d_Texture0, texcoords + offset[i]).b ; + } + p3d_FragData.b = v * normalize; } -FrameBufferProperties vpPanda3DLuminanceFilter::getBufferProperties() const +)shader"; + +vpPanda3DGaussianBlur::vpPanda3DGaussianBlur(const std::string &name, std::shared_ptr inputRenderer, bool isOutput) + : vpPanda3DPostProcessFilter(name, inputRenderer, isOutput, vpPanda3DGaussianBlur::FRAGMENT_SHADER) +{ } + +FrameBufferProperties vpPanda3DGaussianBlur::getBufferProperties() const { FrameBufferProperties fbp; fbp.set_depth_bits(0); @@ -33,13 +99,14 @@ FrameBufferProperties vpPanda3DLuminanceFilter::getBufferProperties() const fbp.set_float_color(false); return fbp; } -void vpPanda3DLuminanceFilter::getRender(vpImage &I) const + +void vpPanda3DGaussianBlur::getRender(vpImage &I) const { if (!m_isOutput) { throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); } unsigned indexMultiplier = m_texture->get_num_components(); // we ask for only 8 bits image, but we may get an rgb image - I.resize(m_texture->get_y_size(), m_texture->get_x_size()); + I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); unsigned char *data = (unsigned char *)(&(m_texture->get_ram_image().front())); if (indexMultiplier != 1) { for (unsigned int i = 0; i < I.getSize(); ++i) { @@ -51,6 +118,104 @@ void vpPanda3DLuminanceFilter::getRender(vpImage &I) const } } +const char *vpPanda3DCanny::FRAGMENT_SHADER = R"shader( +#version 330 + +in vec2 texcoords; + +uniform sampler2D p3d_Texture0; +uniform vec2 dp; // 1 divided by number of pixels +uniform float edgeThreshold; + +const float kernel[9] = float[9]( + 0.0, 1.0, 0.0, + 1.0,-4.0, 1.0, + 0.0, 1.0, 0.0 +); + +const float kernel_h[9] = float[9]( + -1.0, 0.0, 1.0, + -2.0, 0.0, 2.0, + -1.0, 0.0, 1.0 +); + +const float kernel_v[9] = float[9]( + -1.0, -2.0, -1.0, + 0.0, 0.0, 0.0, + 1.0, 2.0, 1.0 +); + +vec2 offset[9] = vec2[9]( + vec2(-dp.x, -dp.y), vec2(0.0, -dp.y), vec2(dp.x, -dp.y), + vec2(-dp.x, 0.0), vec2(0.0, 0.0), vec2(dp.x, 0.0), + vec2(-dp.x, dp.y), vec2(0.0, dp.y), vec2(dp.x, dp.y) +); + + +out vec4 p3d_FragData; + +void main() { + float sum = 0.f; + for(int i = 0; i < 9; ++i) { + float pix = texture(p3d_Texture0, texcoords + offset[i]).b; + sum += pix * kernel[i]; + } + if(abs(sum * 255.f) > edgeThreshold) { + float sum_h = 0.f; + float sum_v = 0.f; + for(int i = 0; i < 9; ++i) { + float pix = texture(p3d_Texture0, texcoords + offset[i]).b; + sum_h += pix * kernel_h[i]; + sum_v += pix * kernel_v[i]; + } + + vec2 orientationAndValid = sum_h * sum_h + sum_v * sum_v > 0 ? vec2(atan(sum_v/sum_h), 1.f) : vec2(0.f, 0.f); + p3d_FragData = vec4(sum_h, sum_v, orientationAndValid.x, orientationAndValid.y); + } else { + p3d_FragData = vec4(0.f, 0.f, 0.f, 0.f); + } +} +)shader"; + +vpPanda3DCanny::vpPanda3DCanny(const std::string &name, std::shared_ptr inputRenderer, bool isOutput, float edgeThreshold) + : vpPanda3DPostProcessFilter(name, inputRenderer, isOutput, vpPanda3DCanny::FRAGMENT_SHADER), m_edgeThreshold(edgeThreshold) +{ } + +void vpPanda3DCanny::setupScene() +{ + vpPanda3DPostProcessFilter::setupScene(); + m_renderRoot.set_shader_input("edgeThreshold", LVector2f(m_edgeThreshold)); +} +void vpPanda3DCanny::setEdgeThreshold(float edgeThreshold) +{ + m_edgeThreshold = edgeThreshold; + m_renderRoot.set_shader_input("edgeThreshold", LVector2f(m_edgeThreshold)); +} + + +FrameBufferProperties vpPanda3DCanny::getBufferProperties() const +{ + FrameBufferProperties fbp; + fbp.set_depth_bits(0); + fbp.set_rgba_bits(32, 32, 32, 32); + fbp.set_float_color(true); + return fbp; +} + +void vpPanda3DCanny::getRender(vpImage &I) const +{ + if (!m_isOutput) { + throw vpException(vpException::fatalError, "Tried to fetch output of a postprocessing filter that was configured as an intermediate output"); + } + unsigned indexMultiplier = m_texture->get_num_components(); // we ask for only 8 bits image, but we may get an rgb image + I.resize(m_renderParameters.getImageHeight(), m_renderParameters.getImageWidth()); + float *data = (float *)(&(m_texture->get_ram_image().front())); + for (unsigned int i = 0; i < I.getSize(); ++i) { + I.bitmap[i].B = (data[i * 4]); + I.bitmap[i].G = (data[i * 4 + 1]); + I.bitmap[i].R = (data[i * 4 + 2]); + } +} #endif diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp index 662f10011d..4ddd5a933b 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DPostProcessFilter.cpp @@ -30,10 +30,11 @@ void vpPanda3DPostProcessFilter::setupScene() throw vpException(vpException::fatalError, "Cannot add a postprocess filter to a renderer that does not define getMainOutputBuffer()"); } - shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, + m_shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, FILTER_VERTEX_SHADER, m_fragmentShader); - m_renderRoot.set_shader(shader); + m_renderRoot.set_shader(m_shader); + m_renderRoot.set_shader_input("dp", LVector2f(1.0 / buffer->get_texture()->get_x_size(), 1.0 / buffer->get_texture()->get_y_size())); std::cout << m_fragmentShader << std::endl; m_renderRoot.set_texture(buffer->get_texture()); m_renderRoot.set_attrib(LightRampAttrib::make_identity()); @@ -77,7 +78,6 @@ void vpPanda3DPostProcessFilter::setupRenderTarget() m_buffers.push_back(m_buffer); m_buffer->set_inverted(gsg->get_copy_texture_inverted()); m_texture = new Texture(); - //fbp.setup_color_texture(m_texture); m_buffer->add_render_texture(m_texture, m_isOutput ? GraphicsOutput::RenderTextureMode::RTM_copy_ram : GraphicsOutput::RenderTextureMode::RTM_copy_texture); m_buffer->set_clear_color(LColor(0.f)); m_buffer->set_clear_color_active(true); @@ -96,7 +96,10 @@ void vpPanda3DPostProcessFilter::setRenderParameters(const vpPanda3DRenderParame bool resize = previousH != params.getImageHeight() || previousW != params.getImageWidth(); m_renderParameters = params; - + if (m_window != nullptr) { + GraphicsOutput *buffer = m_inputRenderer->getMainOutputBuffer(); + m_renderRoot.set_shader_input("dp", LVector2f(1.0 / buffer->get_texture()->get_x_size(), 1.0 / buffer->get_texture()->get_y_size())); + } if (resize) { for (GraphicsOutput *buffer: m_buffers) { //buffer->get_type().is_derived_from() diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp index d0c75e45ac..4135a7e6af 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRGBRenderer.cpp @@ -85,7 +85,6 @@ const char *vpPanda3DRGBRenderer::COOK_TORRANCE_FRAG = R"shader( // Version 330, specified when generating shader #define M_PI 3.1415926535897932384626433832795 - in vec3 oNormal; in vec4 viewVertex; in vec3 F0; @@ -138,8 +137,6 @@ float G(float hn, float nv, float nl, float vh) return min(1.0, min((2.f * hn * nv) / vh, (2.f * hn * nl) / vh)); } - - vec3 F(vec3 F0, float vh) { return F0 + (vec3(1.f, 1.f, 1.f) - F0) * pow(1.f - vh, 5); @@ -151,14 +148,16 @@ void main() vec3 n = normalize(oNormal); // normalized normal vector vec3 v = normalize(-viewVertex.xyz); // normalized view vector float nv = max(0.f, dot(n, v)); - float roughness2 = pow(p3d_Material.roughness, 2); + float roughness2 = clamp(pow(p3d_Material.roughness, 2), 0.01, 0.99); #ifdef HAS_TEXTURE vec4 baseColor = texture(p3d_Texture0, texcoords); + vec4 ambientColor = baseColor; #else + vec4 ambientColor = p3d_Material.ambient; vec4 baseColor = p3d_Material.baseColor; #endif - //p3d_FragData = vec4(0.0, 0.0, 0.0, 1.f); + p3d_FragData = p3d_LightModel.ambient * baseColor; @@ -176,9 +175,8 @@ void main() float attenuation = 1.f / (aFac[0] + aFac[1] * lightDist + aFac[2] * lightDist * lightDist); vec3 FV = F(F0, vh); - vec3 kd = (1.f - p3d_Material.metallic) * (1.f - FV) * (1.f / M_PI); + vec3 kd = (1.f - p3d_Material.metallic) * (1.f - FV) * (1.f / M_PI); - vec4 diffuseColor = baseColor; #ifdef SPECULAR vec3 specularColor = vec3(0.f, 0.f, 0.f); @@ -192,7 +190,7 @@ void main() vec3 specularColor = vec3(0.0, 0.0, 0.0); #endif - p3d_FragData += (p3d_LightSource[i].color * attenuation) * nl * (diffuseColor * vec4(kd, 1.f) + vec4(specularColor, 1.f)); + p3d_FragData += (p3d_LightSource[i].color * attenuation) * nl * (baseColor * vec4(kd, 1.f) + vec4(specularColor, 1.f)); } } )shader"; @@ -219,10 +217,12 @@ void vpPanda3DRGBRenderer::addNodeToScene(const NodePath &object) { NodePath objectInScene = object.copy_to(m_renderRoot); objectInScene.set_name(object.get_name()); + const RenderAttrib *textureAttrib = objectInScene.get_attrib(TextureAttrib::get_class_type()); + bool hasTexture = textureAttrib != nullptr; std::cout << "SHOW SPECULARS = " << m_showSpeculars << std::endl; PT(Shader) shader = Shader::make(Shader::ShaderLanguage::SL_GLSL, COOK_TORRANCE_VERT, - makeFragmentShader(true, m_showSpeculars)); + makeFragmentShader(hasTexture, m_showSpeculars)); objectInScene.set_shader(shader); diff --git a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp index 5002a113e3..a72f71ab3e 100644 --- a/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp +++ b/modules/ar/src/panda3d-simulator/vpPanda3DRendererSet.cpp @@ -38,6 +38,7 @@ vpPanda3DRendererSet::vpPanda3DRendererSet(const vpPanda3DRenderParameters &renderParameters) : vpPanda3DBaseRenderer("set") { m_renderParameters = renderParameters; + load_prc_file_data("", "textures-power-2 none"); } vpPanda3DRendererSet::~vpPanda3DRendererSet() @@ -51,6 +52,7 @@ void vpPanda3DRendererSet::initFramework() load_prc_file_data("", "gl-version 3 2"); + if (m_framework.use_count() > 0) { throw vpException(vpException::notImplementedError, "Panda3D renderer: Reinitializing is not supported!"); } diff --git a/tutorial/ar/tutorial-panda3d-renderer.cpp b/tutorial/ar/tutorial-panda3d-renderer.cpp index 885c1365c2..23439ecd60 100644 --- a/tutorial/ar/tutorial-panda3d-renderer.cpp +++ b/tutorial/ar/tutorial-panda3d-renderer.cpp @@ -56,7 +56,19 @@ void displayLightDifference(const vpImage &colorImage, const vpImage &cannyRawData, + vpImage &canny) +{ +#pragma omp parallel for + for (int i = 0; i < cannyRawData.getSize(); ++i) { + vpRGBf &px = cannyRawData.bitmap[i]; + //canny.bitmap[i] = 255 * (px.R * px.R + px.G * px.G > 0); + canny.bitmap[i] = static_cast(127.5f + 127.5f * atan(px.B)); + } + vpDisplay::display(canny); + vpDisplay::flush(canny); +} int main(int argc, const char **argv) { @@ -106,12 +118,13 @@ int main(int argc, const char **argv) const std::string objectName = "object"; - std::shared_ptr geometryRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS)); - - std::shared_ptr cameraRenderer = std::shared_ptr(new vpPanda3DGeometryRenderer(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS)); - std::shared_ptr rgbRenderer = std::shared_ptr(new vpPanda3DRGBRenderer()); - std::shared_ptr rgbDiffuseRenderer = std::shared_ptr(new vpPanda3DRGBRenderer(false)); - std::shared_ptr grayscaleFilter = std::make_shared("toGrayscale", rgbRenderer, true); + std::shared_ptr geometryRenderer = std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::WORLD_NORMALS); + std::shared_ptr cameraRenderer = std::make_shared(vpPanda3DGeometryRenderer::vpRenderType::CAMERA_NORMALS); + std::shared_ptr rgbRenderer = std::make_shared(); + std::shared_ptr rgbDiffuseRenderer = std::make_shared(false); + std::shared_ptr grayscaleFilter = std::make_shared("toGrayscale", rgbRenderer, false); + std::shared_ptr blurFilter = std::make_shared("blur", grayscaleFilter, false); + std::shared_ptr cannyFilter = std::make_shared("canny", blurFilter, true, 20.f); renderer.addSubRenderer(geometryRenderer); @@ -122,6 +135,9 @@ int main(int argc, const char **argv) } if (showCanny) { renderer.addSubRenderer(grayscaleFilter); + renderer.addSubRenderer(blurFilter); + renderer.addSubRenderer(cannyFilter); + } renderer.setVerticalSyncEnabled(false); @@ -144,17 +160,19 @@ int main(int argc, const char **argv) vpPanda3DAmbientLight alight("Ambient", vpRGBf(0.2)); renderer.addLight(alight); - vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.0, 0.1, -0.1 }), vpColVector({ 0.0, 0.0, 2.0 })); + vpPanda3DPointLight plight("Point", vpRGBf(1.0), vpColVector({ 0.0, 0.4, -0.1 }), vpColVector({ 0.0, 0.0, 1.0 })); renderer.addLight(plight); rgbRenderer->printStructure(); std::cout << "Setting camera pose" << std::endl; renderer.setCameraPose(vpHomogeneousMatrix(0.0, 0.0, -0.3, 0.0, 0.0, 0.0)); + unsigned h = renderParams.getImageHeight(), w = renderParams.getImageWidth(); + vpImage normalsImage; vpImage cameraNormalsImage; + vpImage cannyRawData; vpImage depthImage; - unsigned h = renderParams.getImageHeight(), w = renderParams.getImageWidth(); vpImage colorImage(h, w); vpImage colorDiffuseOnly(h, w); vpImage lightDifference(h, w); @@ -211,7 +229,7 @@ int main(int argc, const char **argv) renderer.getRenderer(rgbDiffuseRenderer->getName())->getRender(colorDiffuseOnly); } if (showCanny) { - renderer.getRenderer()->getRender(cannyImage); + renderer.getRenderer()->getRender(cannyRawData); } const double beforeConvert = vpTime::measureTimeMs(); @@ -222,10 +240,9 @@ int main(int argc, const char **argv) if (showLightContrib) { displayLightDifference(colorImage, colorDiffuseOnly, lightDifference); } - vpDisplay::display(cannyImage); - vpDisplay::flush(cannyImage); - - + if (showCanny) { + displayCanny(cannyRawData, cannyImage); + } vpDisplay::display(colorImage); vpDisplay::displayText(colorImage, 15, 15, "Click to quit", vpColor::red);