目录
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实现彩色图转为灰度图。下面是要实现的功能效果:
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 <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
imageGrayByQt w;
w.show();
return a.exec();
}
imageGrayByQt.cpp:
#include "imageGrayByQt.h"
#include<osgViewer/ViewerEventHandlers>
#include<osgDB/FileUtils>
#include<QMessageBox>
#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<osg::Group> 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<osg::Vec2Array*>(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<osg::MatrixTransform> 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<osg::Geode> 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 <QtWidgets/QWidget>
#include "ui_imageGrayByQt.h"
#include<osg/matrixTransform>
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<osg::MatrixTransform> drawQuadGeomtry();
// 创建着色器相关对象
void createProgramAndShader();
// 移除着色器
void removeShader();
private:
osg::ref_ptr <osg::MatrixTransform> _matTransform{nullptr};
osg::ref_ptr <osg::Program> _spProgram{nullptr}; // 着色器程序
osg::ref_ptr<osg::Shader>_spFragShader; // 片段着色器
osg::ref_ptr<osg::Shader>_spVertexShader; // 顶点着色器
osg::ref_ptr<osg::Vec2Array>_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的矩阵的现象:
可以看到点击界面“显示灰度图”按钮后,四边形位置发生了变化,需要鼠标拖动后才看到四边形,这可不是我们想要的结果(只是灰度图片,怎么会改变位置呢)
彩图转为灰度图由showGrayImage函数实现。需要说明的是:图片的纹理坐标必须通过如下代码设置:
_spTextureCoordArray = dynamic_cast<osg::Vec2Array*>(pQuadGeo->getTexCoordArray(0));
而不能通过人为设置,即不能像该函数第90~93行注释那样设置,否则会出现转为灰度图时,四边形位置发生改变或图片反向了,如下为取消90~93行注释,在GLSL 1.2版本对图片灰度化的现象:
可以看到,纹理图片反向了。事实上,通过第102行的如下代码:
_spTextureCoordArray = dynamic_cast<osg::Vec2Array*>(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节 实例-彩色转灰度(做假红外)。