Last active
July 24, 2024 02:37
-
-
Save insaneyilin/038a022f2ece61c923315306ddcea081 to your computer and use it in GitHub Desktop.
Show opencv cv::Mat image in an OpenGL window(use GLFW)
This file contains hidden or 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
/* | |
# Released under MIT License | |
Copyright (c) 2017 insaneyilin. | |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | |
associated documentation files (the "Software"), to deal in the Software without restriction, | |
including without limitation the rights to use, copy, modify, merge, publish, distribute, | |
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all copies or substantial | |
portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | |
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES | |
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <iostream> | |
#include <GL/glew.h> | |
#include <GLFW/glfw3.h> | |
#include <opencv2/opencv.hpp> | |
using std::cout; | |
using std::endl; | |
int window_width = 640; | |
int window_height = 480; | |
// Function turn a cv::Mat into a texture, and return the texture ID as a GLuint for use | |
static GLuint matToTexture(const cv::Mat &mat, GLenum minFilter, GLenum magFilter, GLenum wrapFilter) { | |
// Generate a number for our textureID's unique handle | |
GLuint textureID; | |
glGenTextures(1, &textureID); | |
// Bind to our texture handle | |
glBindTexture(GL_TEXTURE_2D, textureID); | |
// Catch silly-mistake texture interpolation method for magnification | |
if (magFilter == GL_LINEAR_MIPMAP_LINEAR || | |
magFilter == GL_LINEAR_MIPMAP_NEAREST || | |
magFilter == GL_NEAREST_MIPMAP_LINEAR || | |
magFilter == GL_NEAREST_MIPMAP_NEAREST) | |
{ | |
cout << "You can't use MIPMAPs for magnification - setting filter to GL_LINEAR" << endl; | |
magFilter = GL_LINEAR; | |
} | |
// Set texture interpolation methods for minification and magnification | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter); | |
// Set texture clamping method | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapFilter); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapFilter); | |
// Set incoming texture format to: | |
// GL_BGR for CV_CAP_OPENNI_BGR_IMAGE, | |
// GL_LUMINANCE for CV_CAP_OPENNI_DISPARITY_MAP, | |
// Work out other mappings as required ( there's a list in comments in main() ) | |
GLenum inputColourFormat = GL_BGR; | |
if (mat.channels() == 1) | |
{ | |
inputColourFormat = GL_LUMINANCE; | |
} | |
// Create the texture | |
glTexImage2D(GL_TEXTURE_2D, // Type of texture | |
0, // Pyramid level (for mip-mapping) - 0 is the top level | |
GL_RGB, // Internal colour format to convert to | |
mat.cols, // Image width i.e. 640 for Kinect in standard mode | |
mat.rows, // Image height i.e. 480 for Kinect in standard mode | |
0, // Border width in pixels (can either be 1 or 0) | |
inputColourFormat, // Input image format (i.e. GL_RGB, GL_RGBA, GL_BGR etc.) | |
GL_UNSIGNED_BYTE, // Image data type | |
mat.ptr()); // The actual image data itself | |
// If we're using mipmaps then generate them. Note: This requires OpenGL 3.0 or higher | |
if (minFilter == GL_LINEAR_MIPMAP_LINEAR || | |
minFilter == GL_LINEAR_MIPMAP_NEAREST || | |
minFilter == GL_NEAREST_MIPMAP_LINEAR || | |
minFilter == GL_NEAREST_MIPMAP_NEAREST) | |
{ | |
glGenerateMipmap(GL_TEXTURE_2D); | |
} | |
return textureID; | |
} | |
static void error_callback(int error, const char* description) { | |
fprintf(stderr, "Error: %s\n", description); | |
} | |
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { | |
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { | |
glfwSetWindowShouldClose(window, GLFW_TRUE); | |
} | |
} | |
static void resize_callback(GLFWwindow* window, int new_width, int new_height) { | |
glViewport(0, 0, window_width = new_width, window_height = new_height); | |
glMatrixMode(GL_PROJECTION); | |
glLoadIdentity(); | |
glOrtho(0.0, window_width, window_height, 0.0, 0.0, 100.0); | |
glMatrixMode(GL_MODELVIEW); | |
} | |
static void draw_frame(const cv::Mat& frame) { | |
// Clear color and depth buffers | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
glMatrixMode(GL_MODELVIEW); // Operate on model-view matrix | |
glEnable(GL_TEXTURE_2D); | |
GLuint image_tex = matToTexture(frame, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP); | |
/* Draw a quad */ | |
glBegin(GL_QUADS); | |
glTexCoord2i(0, 0); glVertex2i(0, 0); | |
glTexCoord2i(0, 1); glVertex2i(0, window_height); | |
glTexCoord2i(1, 1); glVertex2i(window_width, window_height); | |
glTexCoord2i(1, 0); glVertex2i(window_width, 0); | |
glEnd(); | |
glDeleteTextures(1, &image_tex); | |
glDisable(GL_TEXTURE_2D); | |
} | |
static void init_opengl(int w, int h) { | |
glViewport(0, 0, w, h); // use a screen size of WIDTH x HEIGHT | |
glMatrixMode(GL_PROJECTION); // Make a simple 2D projection on the entire window | |
glLoadIdentity(); | |
glOrtho(0.0, w, h, 0.0, 0.0, 100.0); | |
glMatrixMode(GL_MODELVIEW); // Set the matrix mode to object modeling | |
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | |
glClearDepth(0.0f); | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the window | |
} | |
int main(int argc, char **argv) | |
{ | |
if (argc != 2) { | |
cout << "Usage: " << argv[0] << "<path_to_image_file>" << endl; | |
exit(EXIT_FAILURE); | |
} | |
cv::Mat img = cv::imread(argv[1]); | |
if (img.empty()) { | |
cout << "Cannot load image: " << argv[1] << endl; | |
exit(EXIT_FAILURE); | |
} | |
window_width = img.cols; | |
window_height = img.rows; | |
GLFWwindow* window; | |
glfwSetErrorCallback(error_callback); | |
if (!glfwInit()) { | |
exit(EXIT_FAILURE); | |
} | |
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); | |
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); | |
window = glfwCreateWindow(window_width, window_height, "Simple example", NULL, NULL); | |
if (!window) { | |
glfwTerminate(); | |
exit(EXIT_FAILURE); | |
} | |
glfwSetKeyCallback(window, key_callback); | |
glfwSetWindowSizeCallback(window, resize_callback); | |
glfwMakeContextCurrent(window); | |
glfwSwapInterval(1); | |
// Initialise glew (must occur AFTER window creation or glew will error) | |
GLenum err = glewInit(); | |
if (GLEW_OK != err) | |
{ | |
cout << "GLEW initialisation error: " << glewGetErrorString(err) << endl; | |
exit(-1); | |
} | |
cout << "GLEW okay - using version: " << glewGetString(GLEW_VERSION) << endl; | |
init_opengl(window_width, window_height); | |
while (!glfwWindowShouldClose(window)) { | |
draw_frame(img); | |
glfwSwapBuffers(window); | |
glfwPollEvents(); | |
} | |
glfwDestroyWindow(window); | |
glfwTerminate(); | |
exit(EXIT_SUCCESS); | |
} |
This file contains hidden or 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
/* | |
# Released under MIT License | |
Copyright (c) 2017 insaneyilin. | |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and | |
associated documentation files (the "Software"), to deal in the Software without restriction, | |
including without limitation the rights to use, copy, modify, merge, publish, distribute, | |
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all copies or substantial | |
portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT | |
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES | |
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <iostream> | |
#include <GL/glew.h> | |
#include <GLFW/glfw3.h> | |
#include <opencv2/opencv.hpp> | |
using std::cout; | |
using std::endl; | |
int window_width = 640; | |
int window_height = 480; | |
// Frame counting and limiting | |
int frame_count = 0; | |
double frame_start_time, frame_end_time, frame_draw_time; | |
// Function turn a cv::Mat into a texture, and return the texture ID as a GLuint for use | |
static GLuint matToTexture(const cv::Mat &mat, GLenum minFilter, GLenum magFilter, GLenum wrapFilter) { | |
// Generate a number for our textureID's unique handle | |
GLuint textureID; | |
glGenTextures(1, &textureID); | |
// Bind to our texture handle | |
glBindTexture(GL_TEXTURE_2D, textureID); | |
// Catch silly-mistake texture interpolation method for magnification | |
if (magFilter == GL_LINEAR_MIPMAP_LINEAR || | |
magFilter == GL_LINEAR_MIPMAP_NEAREST || | |
magFilter == GL_NEAREST_MIPMAP_LINEAR || | |
magFilter == GL_NEAREST_MIPMAP_NEAREST) | |
{ | |
cout << "You can't use MIPMAPs for magnification - setting filter to GL_LINEAR" << endl; | |
magFilter = GL_LINEAR; | |
} | |
// Set texture interpolation methods for minification and magnification | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter); | |
// Set texture clamping method | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapFilter); | |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapFilter); | |
// Set incoming texture format to: | |
// GL_BGR for CV_CAP_OPENNI_BGR_IMAGE, | |
// GL_LUMINANCE for CV_CAP_OPENNI_DISPARITY_MAP, | |
// Work out other mappings as required ( there's a list in comments in main() ) | |
GLenum inputColourFormat = GL_BGR; | |
if (mat.channels() == 1) | |
{ | |
inputColourFormat = GL_LUMINANCE; | |
} | |
// Create the texture | |
glTexImage2D(GL_TEXTURE_2D, // Type of texture | |
0, // Pyramid level (for mip-mapping) - 0 is the top level | |
GL_RGB, // Internal colour format to convert to | |
mat.cols, // Image width i.e. 640 for Kinect in standard mode | |
mat.rows, // Image height i.e. 480 for Kinect in standard mode | |
0, // Border width in pixels (can either be 1 or 0) | |
inputColourFormat, // Input image format (i.e. GL_RGB, GL_RGBA, GL_BGR etc.) | |
GL_UNSIGNED_BYTE, // Image data type | |
mat.ptr()); // The actual image data itself | |
// If we're using mipmaps then generate them. Note: This requires OpenGL 3.0 or higher | |
if (minFilter == GL_LINEAR_MIPMAP_LINEAR || | |
minFilter == GL_LINEAR_MIPMAP_NEAREST || | |
minFilter == GL_NEAREST_MIPMAP_LINEAR || | |
minFilter == GL_NEAREST_MIPMAP_NEAREST) | |
{ | |
glGenerateMipmap(GL_TEXTURE_2D); | |
} | |
return textureID; | |
} | |
static void error_callback(int error, const char* description) { | |
fprintf(stderr, "Error: %s\n", description); | |
} | |
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { | |
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { | |
glfwSetWindowShouldClose(window, GLFW_TRUE); | |
} | |
} | |
static void resize_callback(GLFWwindow* window, int new_width, int new_height) { | |
glViewport(0, 0, window_width = new_width, window_height = new_height); | |
glMatrixMode(GL_PROJECTION); | |
glLoadIdentity(); | |
glOrtho(0.0, window_width, window_height, 0.0, 0.0, 100.0); | |
glMatrixMode(GL_MODELVIEW); | |
} | |
static void draw_frame(const cv::Mat& frame) { | |
// Clear color and depth buffers | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
glMatrixMode(GL_MODELVIEW); // Operate on model-view matrix | |
glEnable(GL_TEXTURE_2D); | |
GLuint image_tex = matToTexture(frame, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP); | |
/* Draw a quad */ | |
glBegin(GL_QUADS); | |
glTexCoord2i(0, 0); glVertex2i(0, 0); | |
glTexCoord2i(0, 1); glVertex2i(0, window_height); | |
glTexCoord2i(1, 1); glVertex2i(window_width, window_height); | |
glTexCoord2i(1, 0); glVertex2i(window_width, 0); | |
glEnd(); | |
glDeleteTextures(1, &image_tex); | |
glDisable(GL_TEXTURE_2D); | |
} | |
void lock_frame_rate(double frame_rate) { | |
static double allowed_frame_time = 1.0 / frame_rate; | |
// Note: frame_start_time is called first thing in the main loop | |
frame_end_time = glfwGetTime(); // in seconds | |
frame_draw_time = frame_end_time - frame_start_time; | |
double sleep_time = 0.0; | |
if (frame_draw_time < allowed_frame_time) { | |
sleep_time = allowed_frame_time - frame_draw_time; | |
usleep(1000000 * sleep_time); | |
} | |
// Debug stuff | |
double potential_fps = 1.0 / frame_draw_time; | |
double locked_fps = 1.0 / (glfwGetTime() - frame_start_time); | |
cout << "Frame [" << frame_count << "] "; | |
cout << "Draw: " << frame_draw_time << " Sleep: " << sleep_time; | |
cout << " Pot. FPS: " << potential_fps << " Locked FPS: " << locked_fps << endl; | |
} | |
static void init_opengl(int w, int h) { | |
glViewport(0, 0, w, h); // use a screen size of WIDTH x HEIGHT | |
glMatrixMode(GL_PROJECTION); // Make a simple 2D projection on the entire window | |
glLoadIdentity(); | |
glOrtho(0.0, w, h, 0.0, 0.0, 100.0); | |
glMatrixMode(GL_MODELVIEW); // Set the matrix mode to object modeling | |
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | |
glClearDepth(0.0f); | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the window | |
} | |
int main(int argc, char **argv) | |
{ | |
if (argc != 2) { | |
cout << "Usage: " << argv[0] << "<path_to_video_file>" << endl; | |
exit(EXIT_FAILURE); | |
} | |
cv::VideoCapture capture(argv[1]); | |
if (!capture.isOpened()) { | |
cout << "Cannot open video: " << argv[1] << endl; | |
exit(EXIT_FAILURE); | |
} | |
double fps = 0.0; | |
fps = capture.get(CV_CAP_PROP_FPS); | |
if (fps != fps) { // NaN | |
fps = 25.0; | |
} | |
cout << "FPS: " << fps << endl; | |
window_width = capture.get(CV_CAP_PROP_FRAME_WIDTH); | |
window_height = capture.get(CV_CAP_PROP_FRAME_HEIGHT); | |
cout << "Video width: " << window_width << endl; | |
cout << "Video height: " << window_height << endl; | |
GLFWwindow* window; | |
glfwSetErrorCallback(error_callback); | |
if (!glfwInit()) { | |
exit(EXIT_FAILURE); | |
} | |
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); | |
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); | |
window = glfwCreateWindow(window_width, window_height, "Simple example", NULL, NULL); | |
if (!window) { | |
glfwTerminate(); | |
exit(EXIT_FAILURE); | |
} | |
glfwSetKeyCallback(window, key_callback); | |
glfwSetWindowSizeCallback(window, resize_callback); | |
glfwMakeContextCurrent(window); | |
glfwSwapInterval(1); | |
// Initialise glew (must occur AFTER window creation or glew will error) | |
GLenum err = glewInit(); | |
if (GLEW_OK != err) | |
{ | |
cout << "GLEW initialisation error: " << glewGetErrorString(err) << endl; | |
exit(-1); | |
} | |
cout << "GLEW okay - using version: " << glewGetString(GLEW_VERSION) << endl; | |
init_opengl(window_width, window_height); | |
double video_start_time = glfwGetTime(); | |
double video_end_time = 0.0; | |
cv::Mat frame; | |
while (!glfwWindowShouldClose(window)) { | |
frame_start_time = glfwGetTime(); | |
if (!capture.read(frame)) { | |
cout << "Cannot grab a frame." << endl; | |
break; | |
} | |
draw_frame(frame); | |
video_end_time = glfwGetTime(); | |
glfwSwapBuffers(window); | |
glfwPollEvents(); | |
++frame_count; | |
lock_frame_rate(fps); | |
} | |
cout << "Total video time: " << video_end_time - video_start_time << " seconds" << endl; | |
glfwDestroyWindow(window); | |
glfwTerminate(); | |
exit(EXIT_SUCCESS); | |
} |
I changed the window hint to 3 and 3 but I'm getting a segmentation error
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
This won't work on all images, you need to tell opengl how to unpack the data. If you're getting skewed images or segfaults like I was, here's the fix: https://stackoverflow.com/a/53566791
it doesn't fit the screen
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Reference:
https://www.opengl.org/discussion_boards/showthread.php/181714-Does-opengl-help-in-the-display-of-an-existing-image
https://r3dux.org/2012/01/how-to-convert-an-opencv-cvmat-to-an-opengl-texture/