Skip to content

Instantly share code, notes, and snippets.

@insaneyilin
Last active November 20, 2023 05:47
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save insaneyilin/038a022f2ece61c923315306ddcea081 to your computer and use it in GitHub Desktop.
Save insaneyilin/038a022f2ece61c923315306ddcea081 to your computer and use it in GitHub Desktop.
Show opencv cv::Mat image in an OpenGL window(use GLFW)
/*
# 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);
}
/*
# 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);
}
@MichaelMikhael
Copy link

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);

@callumacrae
Copy link

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

@lockieluke
Copy link

it doesn't fit the screen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment