利用OSG和GLSL实现彩色图转为灰度图

目录

1. 前言

2. 开发环境说明 

3. 预备知识

4. 功能实现

4.1. 代码

4.2. 代码说明

5. 附加说明


1. 前言

       灰色图片其rgb值是一样的,比如(0.5, 0.5, 0.5)就是一张灰度图。彩色转黑白算法有很多种。因此由彩色转黑白关键就是由彩色的rgb算出灰度gray,然后最终的颜色就是(gray, gray , gray)。网上搜索到RGB转gray的算法有很多种,其中最常见的几种如下:

浮点算法:Gray = R * 0.3 + G * 0.59 + B * 0.11
整数方法:Gray = (R * 30 + G * 59 + B * 11) / 100
移位方法:Gray = (R * 76 + G * 151 + B*28) >> 8;
平均值法:Gray = (R + G + B) / 3;
仅取绿色:Gray = G;

       利用编程语言如C、C++等自己实现彩色图转为灰度图也是很容易的。本博文仅仅只是说明如何利用OSG和GLSL实现彩色图转为灰度图。下面是要实现的功能效果:

 利用OSG和GLSL实现彩色图转为灰度图_第1张图片

2. 开发环境说明 

          本次用到的开发环境说明如下:

  • OpenSceneGraph 3.6.2。
  • Visual studio 2022 64位社区版。
  • Windows 11 家庭中文版。
  • Qt  5.14.1。

3. 预备知识

       因为本博文用到了GLSL,如果不了解GLSL编程,请参考本人博客GLSL专栏或者自行问度妈。本文用到了OSG和GLSL混合编程,不懂的可以参考如下博文:

  • OSG与opengl的shader结合。
  • 利用GLSL和OSG进行三维渲染项目实战。

4. 功能实现

4.1. 代码

  直接上代码,main.cpp

#include "imageGrayByQt.h"
#include 

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    imageGrayByQt w;
    w.show();
    return a.exec();
}

imageGrayByQt.cpp:

#include "imageGrayByQt.h"
#include
#include
#include
#include"shaderScript.h"

class MVPCallback : public osg::Uniform::Callback
{
public:
	MVPCallback(osg::Camera* camera, osg::MatrixTransform* pMatTransform)
		:mCamera(camera), _pMatTransform(pMatTransform)
	{
	}
	virtual void operator()(osg::Uniform* uniform, osg::NodeVisitor* nv)
	{
		const osg::Matrix& mat = _pMatTransform->getMatrix();
		osg::Matrixf modelView = mCamera->getViewMatrix();
		osg::Matrixf projectM = mCamera->getProjectionMatrix();
		uniform->set(mat * modelView * projectM); // 要乘以_pMatTransform的矩阵,否则不正确
	}

private:
	osg::Camera* mCamera;
	osg::MatrixTransform* _pMatTransform;
};

imageGrayByQt::imageGrayByQt(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::imageGrayByQtClass())
{
    ui->setupUi(this);

    this->setWindowState(Qt::WindowMaximized);

    connect(ui->showOrigImageBtn, &QAbstractButton::clicked, this, &imageGrayByQt::showOrigImage);
    connect(ui->showGrayImageBtn, &QAbstractButton::clicked, this, &imageGrayByQt::showGrayImage);

	osg::ref_ptr spRoot = new osg::Group;

    // 画四边形
    auto spQuadGeo = drawQuadGeomtry();
	
	spRoot->addChild(spQuadGeo);

	ui->viewWnd->setSceneData(spRoot);
	ui->viewWnd->setCameraManipulator(new osgGA::TrackballManipulator);
	ui->viewWnd->addEventHandler(new osgViewer::WindowSizeHandler);
	ui->viewWnd->addEventHandler(new osgViewer::StatsHandler);

	createProgramAndShader();

    showOrigImage();
}

imageGrayByQt::~imageGrayByQt()
{
    delete ui;
}

void imageGrayByQt::showOrigImage()
{
	if (!osgDB::fileExists("test.png"))
	{
		QMessageBox::warning(this, "test.png is not exist !", "file exist check");
		return;
	}

	removeShader();

	auto pTexture2D = new osg::Texture2D();
	auto pImage = osgDB::readImageFile("test.png");
	pTexture2D->setImage(pImage);
	pTexture2D->setFilter(osg::Texture::FilterParameter::MAG_FILTER, osg::Texture::FilterMode::LINEAR);
	pTexture2D->setFilter(osg::Texture::FilterParameter::MIN_FILTER, osg::Texture::FilterMode::LINEAR);
	pTexture2D->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
	pTexture2D->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
	auto pGeode = _matTransform->getChild(0)->asGeode();
	pGeode->getOrCreateStateSet()->setTextureAttributeAndModes(0, pTexture2D);
}

// 显示灰度图
void imageGrayByQt::showGrayImage()
{
	if (nullptr == _spTextureCoordArray)
	{
		_spTextureCoordArray = new osg::Vec2Array();
	}
	_spTextureCoordArray->clear();

	//_spTextureCoordArray->push_back(osg::Vec2d(0.0, 0.0));
	//_spTextureCoordArray->push_back(osg::Vec2d(0.0, 1.0));
	//_spTextureCoordArray->push_back(osg::Vec2d(1.0, 1.0));
	//_spTextureCoordArray->push_back(osg::Vec2d(1.0, 0.0));

	auto spGeode = _matTransform->getChild(0)->asGeode();
	auto pQuadGeo = spGeode->getChild(0)->asGeometry();
	
#ifdef _SHADER_VER_120
	pQuadGeo->setTexCoordArray(0, _spTextureCoordArray);
#endif
#ifdef _SHADER_VER_430
	_spTextureCoordArray = dynamic_cast(pQuadGeo->getTexCoordArray(0));
	pQuadGeo->setVertexAttribArray(0, pQuadGeo->getVertexArray());
	pQuadGeo->setVertexAttribBinding(0, osg::Geometry::BIND_PER_VERTEX); // 必须是BIND_PER_VERTEX,否则四边形不会显示,下同
	pQuadGeo->setVertexAttribArray(1, _spTextureCoordArray);
	pQuadGeo->setVertexAttribBinding(1, osg::Geometry::BIND_PER_VERTEX);

	// 必须用osg::Matrixf,而不能用osg::Matrixd或osg::Matrix,否则报错,场景不会绘制出来
	osg::Matrixf modelView = ui->viewWnd->getCamera()->getViewMatrix(); 
	osg::Matrixf projectM = ui->viewWnd->getCamera()->getProjectionMatrix();
	osg::Uniform* pMVPUniform = new osg::Uniform("mvp", modelView * projectM);
	osg::StateSet* ss = spGeode->getOrCreateStateSet();
	ss->addUniform(pMVPUniform);//对应的Program和Uniform要加到同一个Node下的StateSet中
	pMVPUniform->setUpdateCallback(new MVPCallback(ui->viewWnd->getCamera(), _matTransform));
#endif // _SHADER_VER_430

	spGeode->getOrCreateStateSet()->setAttributeAndModes(_spProgram);
	spGeode->getOrCreateStateSet()->addUniform(new osg::Uniform("colorMap", 0));

	_spProgram->addShader(_spVertexShader);
	_spProgram->addShader(_spFragShader);
}

// 移除着色器
void imageGrayByQt::removeShader()
{
	if (nullptr != _spProgram)
	{
		if (nullptr != _spVertexShader)
		{
			_spProgram->removeShader(_spVertexShader);
		}

		if (nullptr != _spFragShader)
		{
			_spProgram->removeShader(_spFragShader);
		}
	}
}

// 创建着色器相关对象
void imageGrayByQt::createProgramAndShader()
{
	if (nullptr == _spProgram)
	{
		_spProgram = new osg::Program;
	}

	if (nullptr == _spVertexShader)
	{
		_spVertexShader = new osg::Shader(osg::Shader::VERTEX, vs());
	}

	if (nullptr == _spFragShader)
	{
		_spFragShader = new osg::Shader(osg::Shader::FRAGMENT, fs());
	}
}

// 画四边形
osg::ref_ptr imageGrayByQt::drawQuadGeomtry()
{
	if (nullptr == _matTransform)
	{
		_matTransform = new osg::MatrixTransform;

		// 转动60度,便于观察
		_matTransform->setMatrix(osg::Matrix::rotate(osg::inDegrees(60.0), osg::X_AXIS));
	}

	auto pChildNum = _matTransform->getNumChildren();
	if (0 == pChildNum)
	{
		osg::ref_ptr spGeode = new osg::Geode;
		_matTransform->addChild(spGeode);

		auto pQuadGeo = osg::createTexturedQuadGeometry(osg::Vec3d(-1.0, -1.0, 0.0), osg::Vec3d(1.0, 1.0, 0.0), osg::Vec3d(-1.0, 1.0, 0.0));
		spGeode->addDrawable(pQuadGeo);
	}

	return _matTransform;
}

imageGrayByQt.h: 

#pragma once

#include 
#include "ui_imageGrayByQt.h"
#include
QT_BEGIN_NAMESPACE
namespace Ui { class imageGrayByQtClass; };
QT_END_NAMESPACE

class imageGrayByQt : public QWidget
{
    Q_OBJECT

public:
    imageGrayByQt(QWidget *parent = nullptr);
    ~imageGrayByQt();

private:

    void showOrigImage();

    void showGrayImage();

    // 画四边形
    osg::ref_ptr drawQuadGeomtry();

    // 创建着色器相关对象
    void createProgramAndShader();

    // 移除着色器
    void removeShader();
private:

    osg::ref_ptr  _matTransform{nullptr};
    osg::ref_ptr  _spProgram{nullptr};  // 着色器程序
    osg::ref_ptr_spFragShader;           // 片段着色器
    osg::ref_ptr_spVertexShader;         // 顶点着色器
    osg::ref_ptr_spTextureCoordArray;  // 纹理数组

private:
    Ui::imageGrayByQtClass *ui;
};

shaderScript.h:

#ifndef _SHADERSCRIPT_H
#define _SHADERSCRIPT_H

//#define _SHADER_VER_120
#define _SHADER_VER_430

// 顶点着色器
#ifdef _SHADER_VER_120
static const char* vs() {
	static const char* p = "#version 120\n"
		"varying  vec2 vTexCoord; \n"
		"void main()\n"
		"{\n"
		"gl_Position = ftransform();\n"
		"vTexCoord = gl_MultiTexCoord0.xy;\n"
		"}\n";

	return p;
}

// 片段着色器
static const char* fs() {
	static const char* p = "#version 120\n"
		"uniform sampler2D colorMap;\n"
		"varying  vec2 vTexCoord;\n"
		"void main(void)\n"
		"{\n"
		"vec3 W = vec3(0.2125, 0.7154, 0.0721);\n"
		"vec4 mask = texture2D(colorMap, vTexCoord);\n"
		"float luminance = dot(mask.rgb, W);\n"
		"gl_FragColor = vec4(vec3(luminance), 1.0);\n"
		"}\n";

	return p;
}
#endif

#ifdef _SHADER_VER_430

static const char* vs() {
	static const char* p = "#version 430\n"
		"layout (location=0) in vec3 VertexPosition; \n"
		"layout (location=1) in vec2 vTexCoord; \n"
		"out vec2 texCoord;\n"
		"uniform mat4 mvp;\n"
		"void main()\n"
		"{\n"
		"    gl_Position = mvp * vec4(VertexPosition, 1.0);\n"
		"    texCoord = vTexCoord;\n"
		"}\n";

	return p;
}

// 片段着色器
static const char* fs() {
	static const char* p = "#version 430 \n"
		"in vec2 texCoord;\n"
		"out vec4 FragColor; \n"
		"uniform sampler2D colorMap;\n"
		"void main(void)\n"
		"{\n"
		"vec3 W = vec3(0.2125, 0.7154, 0.0721);\n"
		"vec4 mask = texture2D(colorMap, texCoord);\n"
		"float luminance = dot(mask.rgb, W);\n"
		"FragColor = vec4(vec3(luminance), 1.0);\n"
		"}\n";

	return p;
}
#endif // _SHADER_VER_430

#endif

   ui->viewWnd是QtOsgView类型指针,QtOsgView.h、QtOsgView.cpp文件参见:osg嵌入到Qt窗体,实现Qt和osg混合编程 博文。

4.2. 代码说明

        shaderScript.h文件实现了顶点着色器和片段着色器。着色器分为1.2.0版本和4.3.0版本。1.2.0版本中用到了ftransform函数,该函数理论上可以用如下的GLSL内置函数等效:

gl_Position =gl_ModelViewProjectionMatrix * gl_Vertex;

关于ftransform函数的更多知识,请参考:opengl ftransform 。

      注意:

gl_FragColor、varying类型、gl_ModelViewProjectionMatrix 、gl_Vertex、gl_MultiTexCoord0在GLSL 1.40之后的版本中移除了,因此在4.30版本使用这些关键字会报错。

       在GLSL 4.3.0版本当向顶点着色器的uniform mat4类型的mvp传入模型视图投影矩阵时,传入的矩阵类型必须是osg::Matrixf,而不能是osg::Matrix或osg::Matrixd,否则会报如下错误,且四边形不会绘制出来:  

Warning: detected OpenGL error 'invalid operation' at after pcp->apply(Unfiorm&) in GLObjectsVisitor::apply(osg::StateSet& stateset), unifrom name: mvp

      在GLSL 4.3.0版本必须像下面那样对uniform设置回调,否则四边形绘制的位置不对,可能会看不见。

pMVPUniform->setUpdateCallback(new MVPCallback(ui->viewWnd->getCamera(), _matTransform));

 且在MVPCallback类的operator函数中乘以_pMatTransform包含的矩阵,即如下那样:

virtual void operator()(osg::Uniform* uniform, osg::NodeVisitor* nv)
	{
	     .......................// 其它代码略
		uniform->set(mat * modelView * projectM); // 要乘以_pMatTransform的矩阵,否则不正确
	}

否则当点击界面“显示灰度图”按钮时,场景也即四边形位置会变化,如下为没乘以_pMatTransform的矩阵的现象:

利用OSG和GLSL实现彩色图转为灰度图_第2张图片

可以看到点击界面“显示灰度图”按钮后,四边形位置发生了变化,需要鼠标拖动后才看到四边形,这可不是我们想要的结果(只是灰度图片,怎么会改变位置呢) 

       彩图转为灰度图由showGrayImage函数实现。需要说明的是:图片的纹理坐标必须通过如下代码设置:

_spTextureCoordArray = dynamic_cast(pQuadGeo->getTexCoordArray(0));

而不能通过人为设置,即不能像该函数第90~93行注释那样设置,否则会出现转为灰度图时,四边形位置发生改变或图片反向了,如下为取消90~93行注释,在GLSL 1.2版本对图片灰度化的现象:

利用OSG和GLSL实现彩色图转为灰度图_第3张图片

可以看到,纹理图片反向了。事实上,通过第102行的如下代码:

 _spTextureCoordArray = dynamic_cast(pQuadGeo->getTexCoordArray(0));

返回的纹理坐标依次是:(0.0, 1.0)、(0.0, 0.0)、(1.0, 0.0)、(1.0, 1.0),即人为预想的纹理坐标和真实的纹理坐标可能会不一样,即将第90~93行改为如下那样则纹理图片不会反向:

    _spTextureCoordArray->push_back(osg::Vec2d(0.0, 1.0));
  	_spTextureCoordArray->push_back(osg::Vec2d(0.0, 0.0));
	_spTextureCoordArray->push_back(osg::Vec2d(1.0, 0.0));
	_spTextureCoordArray->push_back(osg::Vec2d(1.0, 1.0));

总之:出于安全便捷考虑,不要人为设置纹理坐标,只需要按照第102行那样,调用osg的接口获取纹理坐标就行。另外如下代码可以去掉,不影响功能,留下来仅仅是为了验证刚才说的纹理坐标问题的。

#ifdef _SHADER_VER_120
	pQuadGeo->setTexCoordArray(0, _spTextureCoordArray);
#endif

5. 附加说明

     代码中读取了png图片,故请保证png插件存在,否则读取png会失败。关于怎么编译png插件到osg,请参见:osg第三方插件的编译方法(以jpeg插件来讲解)

     参考链接:第13节 实例-彩色转灰度(做假红外)。

你可能感兴趣的:(#,osg项目实战,osg,GLSL,彩图变为灰度图)