原文地址:http://blog.youkuaiyun.com/hjimce/article/details/51475644
作者:hjimce
最近开始学习深度学习的一些gpu编程,大体学了cuda后,感觉要在手机上跑深度学习,没有nvidia显卡,不能加速。所以只能老老实实的学习opengl的shader编程,进行gpu通用计算加速,总的感觉shader编程比cuda编程难,还好自己之前研究生的时候,经常用opengl的一些API函数,对opengl比较了解。对于每一种语言,最简单的学习程序就是:hello world,下面记录一下shader编程最简单的例子,利用gpu加速实现彩色图像转灰度图像。大体包含五个步骤:
1、初始化环境、创建shader程序等;
2、根据需求,编写片元着色器的纹理图像处理代码;
3、从cpu传入数据(利用uniform类型变量,传数据到shader中,如果是图片可以直接采用纹理传入);
4、设置渲染模型、纹理坐标,绘制矩形,进行渲染计算;
5、取回图片处理后的数据;
一、opengl与shader环境初始化、编译链接
(1)初始化环境
//1、初始化环境
glutInit(&argc, argv);
//glutInitWindowSize(512,512);
//glutInitWindowPosition(100,100);
glutCreateWindow("GLEW Test");
glewExperimental = GL_TRUE;
glewInit();
if (!glewIsSupported("GL_VERSION_4_0"))
{
std::cerr << "Failed to initialize GLEW with OpenGL 4.0!" << std::endl;
return EXIT_FAILURE;
}
(2)创建链接、编译shader程序
//2、读取、创建shader程序,编译连接等
auto program_id = ShaderProgram("shader/gl_texture.vert", "shader/gl_texture.frag");
glUseProgram(program_id);
ShaerProgram函数的代码如下:
GLuint ShaderProgram(const std::string &vertex_shader_file, const std::string &fragment_shader_file) {
// 创建shader程序
auto vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
auto fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
auto result = GL_FALSE;
auto info_length = 0;
// 读取shader源码
std::ifstream vertex_shader_stream(vertex_shader_file);
std::string vertex_shader_code((std::istreambuf_iterator<char>(vertex_shader_stream)), std::istreambuf_iterator<char>());
std::ifstream fragment_shader_stream(fragment_shader_file);
std::string fragment_shader_code((std::istreambuf_iterator<char>(fragment_shader_stream)), std::istreambuf_iterator<char>());
// 编译顶点shader代码
std::cout << "Compiling Vertex Shader ..." << std::endl;
auto vertex_shader_code_ptr = vertex_shader_code.c_str();
glShaderSource(vertex_shader_id, 1, &vertex_shader_code_ptr, NULL);
glCompileShader(vertex_shader_id);
// Check vertex shader log
glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &result);
if (result == GL_FALSE) {
glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_length);
std::string vertex_shader_log((unsigned int)info_length, ' ');
glGetShaderInfoLog(vertex_shader_id, info_length, NULL, &vertex_shader_log[0]);
std::cout << vertex_shader_log << std::endl;
}
// 编译片元着色器代码
std::cout << "Compiling Fragment Shader ..." << std::endl;
auto fragment_shader_code_ptr = fragment_shader_code.c_str();
glShaderSource(fragment_shader_id, 1, &fragment_shader_code_ptr, NULL);
glCompileShader(fragment_shader_id);
// Check fragment shader log
glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &result);
if (result == GL_FALSE) {
glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_length);
std::string fragment_shader_log((unsigned long)info_length, ' ');
glGetShaderInfoLog(fragment_shader_id, info_length, NULL, &fragment_shader_log[0]);
std::cout << fragment_shader_log << std::endl;
}
// 创建链接程序
std::cout << "Linking Shader Program ..." << std::endl;
auto program_id = glCreateProgram();
glAttachShader(program_id, vertex_shader_id);
glAttachShader(program_id, fragment_shader_id);
glBindFragDataLocation(program_id, 0, "FragmentColor");
glLinkProgram(program_id);
//打印编译信息,如果编译错误,就可以看见错误信息了
glGetProgramiv(program_id, GL_LINK_STATUS, &result);
if (result == GL_FALSE) {
glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_length);
std::string program_log((unsigned long)info_length, ' ');
glGetProgramInfoLog(program_id, info_length, NULL, &program_log[0]);
std::cout << program_log << std::endl;
}
glDeleteShader(vertex_shader_id);
glDeleteShader(fragment_shader_id);
return program_id;
}
二、编写shader程序
上面在创建shader程序的时候,需要shader源码,对于图像处理来说,顶点着色器我们一般不需要用到,主要是利用片元着色器进行图像处理的相关计算。
(1)顶点着色器源码文件gl_texture.vert的代码如下:
#version 400
//顶点着色器输入
in vec2 Position;
in vec2 TexCoord;
//顶点着色器的输出,这个变量会进入片元着色器
out vec2 fragTexCoord;
void main() {
// Copy the input to the fragment shader
fragTexCoord =TexCoord.xy;
// Calculate the final position on screen
// Note the visible portion of the screen is in <-1,1> range for x and y coordinates
gl_Position = vec4(Position.x,Position.y, 0.0, 1.0);
}
(2)片元着色器gl_texture.frag
#version 400
// 需要注意opengl纹理输入
uniform sampler2D Texture;
// 来自顶点着色器
in vec2 fragTexCoord;
//输出
out vec4 FragmentColor;
void main() {
// Lookup the color in Texture on coordinates given by fragTexCoord
vec3 pColor = texture(Texture, fragTexCoord.xy).rgb;
//转换成灰度图像
float gray=pColor.r*0.2126+ 0.7152* pColor.g + 0.0722*pColor.b;
FragmentColor =vec4(gray, gray,gray, 1.0);
}
三、opencv读取图片、输入纹理、启用纹理等
//3、设置纹理相关参数、或者输入shader计算所需要的数据
auto texture_id = LoadImage("2.jpg", height_width, height_width);//读入一张图片,转成纹理格式,把并把图片数据拷贝到opengl纹理单元。
auto texture_attrib = glGetUniformLocation(program_id, "Texture");//找到shader程序中,变量名为Texture,类型为uniform的变量索引
glUniform1i(texture_attrib,0);
glActiveTexture(GL_TEXTURE0 + 0);//启用第一个纹理,并绑定纹理数据
glBindTexture(GL_TEXTURE_2D, texture_id);
LoadImage函数代码如下:
GLuint LoadImage(const std::string &image_file, unsigned int width, unsigned int height)
{
// Create new texture object
GLuint texture_id;
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
// Set mipmaps
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
cv::Mat texture_cv=cv::imread(image_file);
if (texture_cv.empty())
{
return -1;
}
else
{
glTexImage2D(GL_TEXTURE_2D, 0, 3, texture_cv.cols, texture_cv.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, texture_cv.data);
return texture_id;
}
}
四、绘制渲染、计算
通过绘制一个矩形,该矩形的纹理刚好就是我们的图片,这样就可以实现gpu图片处理了:
//4、设置渲染相关参数(矩形4个顶点、及其对应的纹理坐标)
InitializeGeometry(program_id);
//5、绘制、渲染
glClearColor(0.f,0.f,0.f,1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
InitializeGeometry函数:
void InitializeGeometry(GLuint program_id)
{
// Generate a vertex array object
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// 顶点缓存
GLfloat tempv[12] = {1.0f,1.0f,-1.0f,1.0f,1.0f,-1.0f,-1.0f, -1.0f}; //二维顶点坐标,分别为矩形的四个顶点坐标
std::vector<GLfloat> vertex_buffer(tempv , tempv+12);
// Generate a vertex buffer object
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertex_buffer.size() * sizeof(GLfloat), vertex_buffer.data(), GL_STATIC_DRAW);
// Setup vertex array lookup
auto position_attrib = glGetAttribLocation(program_id, "Position");
glVertexAttribPointer(position_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(position_attrib);
// Generate another vertex buffer object for texture coordinates
GLfloat temptex[8] = {1.0f,0.0f,0.0f,0.0f,1.0f,1.0f,0.0f, 1.0f};
std::vector<GLfloat> texcoord_buffer(temptex,temptex+8);
GLuint tbo;
glGenBuffers(1, &tbo);
glBindBuffer(GL_ARRAY_BUFFER, tbo);
glBufferData(GL_ARRAY_BUFFER, texcoord_buffer.size() * sizeof(GLfloat), texcoord_buffer.data(), GL_STATIC_DRAW);
auto texcoord_attrib = glGetAttribLocation(program_id, "TexCoord");
glVertexAttribPointer(texcoord_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(texcoord_attrib);
glBindVertexArray(vao);
}
这个需要设置好顶点坐标和纹理坐标。
五、取出数据到opencv中
渲染完毕后,我们就要把图像处理的结果保存回opencv的cv::Mat上,然后看看处理结果
void show(int height=height_width,int width=height_width)
{
cv::Mat img=cv::Mat::zeros(height, width, CV_8UC3);
//use fast 4-byte alignment (default anyway) if possible
//glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3) ? 1 : 4);
//set length of one complete row in destination data (doesn't need to equal img.cols)
//glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize());
glReadPixels(0, 0, img.cols, img.rows, GL_BGR, GL_UNSIGNED_BYTE, img.data);//opencv存储为BGR顺序
cv::flip(img, img, 0);//需要翻转
cv::imshow("result",img);
cv::waitKey(0);
}
最后贴一下主程序流程的完整代码:
#include "stdafx.h"
#include <stdlib.h>
#include "include/glew.h"
#include "include/GLUT.H"
#include <opencv2/opencv.hpp>
#include "shader.h"
#define height_width 512
void show(int height=height_width,int width=height_width)
{
cv::Mat img=cv::Mat::zeros(height, width, CV_8UC3);
//use fast 4-byte alignment (default anyway) if possible
//glPixelStorei(GL_PACK_ALIGNMENT, (img.step & 3) ? 1 : 4);
//set length of one complete row in destination data (doesn't need to equal img.cols)
//glPixelStorei(GL_PACK_ROW_LENGTH, img.step/img.elemSize());
glReadPixels(0, 0, img.cols, img.rows, GL_BGR, GL_UNSIGNED_BYTE, img.data);//opencv存储为BGR顺序
cv::flip(img, img, 0);//需要翻转
cv::imshow("result",img);
cv::waitKey(0);
}
int main( int argc, char** argv )
{
//1、初始化环境
glutInit(&argc, argv);
//glutInitWindowSize(512,512);
//glutInitWindowPosition(100,100);
glutCreateWindow("GLEW Test");
glewExperimental = GL_TRUE;
glewInit();
if (!glewIsSupported("GL_VERSION_4_0"))
{
std::cerr << "Failed to initialize GLEW with OpenGL 4.0!" << std::endl;
return EXIT_FAILURE;
}
//2、读取、创建shader程序,编译连接等
auto program_id = ShaderProgram("shader/gl_texture.vert", "shader/gl_texture.frag");
glUseProgram(program_id);
//3、设置纹理相关参数、或者输入shader计算所需要的数据
auto texture_id = LoadImage("2.jpg", height_width, height_width);//读入一张图片,转成纹理格式,把并把图片数据拷贝到opengl纹理单元。
auto texture_attrib = glGetUniformLocation(program_id, "Texture");//找到shader程序中,变量名为Texture,类型为uniform的变量索引
glUniform1i(texture_attrib,0);
glActiveTexture(GL_TEXTURE0 + 0);//启用第一个纹理,并绑定纹理数据
glBindTexture(GL_TEXTURE_2D, texture_id);
//4、设置渲染相关参数(矩形4个顶点、及其对应的纹理坐标)
InitializeGeometry(program_id);
//5、绘制、渲染
glClearColor(0.f,0.f,0.f,1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
//6、取回数据到opencv,并显示结果
show();//
return 0;
}
结果: