游戏图像处理

2014-05-02 17:47:21 hewei0241 阅读数 19380

欢迎转载!转载时请注明出处:http://blog.csdn.net/aa4790139/article/details/8106993

如果你还没接触到过这个工具,那你今天运气非常好了...

下载地址:http://www.codeandweb.com/texturepacker

说明:这款软件需要购买的,能够发一百多块买到这么好的工具还是挺值得,可以工具的价钱,肯定他带给的的好处远不止这个数字..呵呵!

1、为什么要用这个工具呢?有什么好处?

第一点:内存问题, OpenGL ES  纹理的宽和高都要是2次幂数, 以刚才的例子来说, 假如 start.png 本身是 480x320, 但在载入内存後, 它其实会被变成一张 512x512 的纹理, 而start.png 则由 101x131 变成 128x256, 默认情况下面,当你在cocos2d里面加载一张图片的时候,对于每一个像素点使用4个byte来表示--1个byte(8位)代表red,另外3个byte分别代表green、blue和alpha透明通道。这个就简称RGBA8888。

因此,如果你使用默认的像素格式来加载图片的话,你可以通过下面的公式来计算出将要消耗多少内存来加载:

  图像宽度(width)×图像高度(height)×每一个像素的位数(bytes per pixel) = 内存大小

  此时,如果你有一张512×512的图片,那么当你使用默认的像素格式去加载它的话,那么将耗费

  512×512×4=1MB(好多啊!)

第二点:再看看关於渲染速度方面, OpenGL ES 上来说我们应该尽量减少渲染时切换纹理和 glDrawArray 的呼叫, 刚才的例子每画一个图像都会切换一次纹理并呼叫一次 glDrawArray , 我们这里只画3样东西, 所以不会看到有什麽问题, 但如果我们要渲染几十个甚至几百个图像 , 速度上就会被拖慢. 很明显这并不是我们所想要的..

估计就这两点就说服了你吧~   至少我是了...呵呵!

2、认识TexturePacker的界面

我框出来的都是我们常用和一些必须点到的地方

Data Format:导出什么引擎数据,默认cocos2d,下拉列表中有很多,基本常用的引擎都支持了

Data File :导出文件位置(后缀名.plist)

Texture Format:纹理格式,默认png

Image format:图片像素格式,默认RGBA8888...根据对图片质量的需求导出不同的格式

Dithering:抖动,默认NearestNeighbour,(如果图像上面有许许多多的“条条”和颜色梯度变化)将其修改成FloydSteinberg+Alpha;

Scale: 让你可以保存一个比原始图片尺寸要大一点、或者小一点的spritesheet。比如,如果你想在spritesheet中加载“@2x"的图片(也即为Retina-display设备或者ipad创建的)。但是你同时也想为不支持高清显示的iphone和touch制作spritesheet,这时候只需要设置scale为 1.0,同时勾选autoSD就可以了。也就是说,只需要美工提供高清显示的图片,用这个软件可以自己为你生成高清和普清的图片。

Algorithm TexturePacker:里面目前唯一支持的算法就是MaxRects,即按精灵尺寸大小排列,但是这个算法效果非常好,因此你不用管它。

Border/shape padding: 即在spritesheet里面,设置精灵与精灵之间的间隔。如果你在你的游戏当中看到精灵的旁边有一些“杂图”的时候,你就可以增加这个精灵之间的间隔。

Extrude: 精灵边界的重复像素个数. 这个与间隔是相对应的--如果你在你的精灵的边边上看到一些透明的小点点,你就可以通过把这个值设设置大一点。

Trim: 通过移除精灵四周的透明区域使之更好地放在spritesheet中去。不要担心,这些透明的区域仅仅是为了使spritesheet里面的精灵紧凑一点。--当你从cocos2d里面去读取这些精灵的时候,这些透明区域仍然在寻里。(因为,有些情况下,你可能需要这些信息来确定精灵的位置)

Shape outlines: 把这个选项打开,那么就能看到精灵的边边。这在调试的时候非常有用。

AddSprite:添加图片Add Folder:根据文件夹添加图片

Publish:导出资源文件(.plist和png)

3、程序中更改资源加载方式

我就是用的上一个例子,然后将start.png,grossinis.png,grossinis_sister1.png,grossinis_sister2.png打包成一个image.plist和image.png.程序我就不重新全部上传了,就自上传和上一个例子不同的地方,没有的朋友就可以去第一篇文章下载工程,今后我们还会用到的,会用这个工程来开发我们的第一个游戏...游戏暂时保密...呵呵!

SpriteTestLayer.cpp(就只改了这个这个文件

////////////////////////////////////////////////////////////////////////

#include "SpriteTestLayer.h"



SpriteTestLayer::SpriteTestLayer(void)

{

}


bool SpriteTestLayer::init()

{

CCSize s=CCDirector::sharedDirector()->getWinSize();

//第一种加载资源方式

//CCSprite* sprite=CCSprite::create("start.png");

//第二种加载资源方式

CCSpriteFrameCache *cache=CCSpriteFrameCache::sharedSpriteFrameCache();

cache->addSpriteFramesWithFile("image.plist","image.png");

CCTexture2D *texture = CCTextureCache::sharedTextureCache()->textureForKey("image.png"); 

    CCSpriteBatchNode *spriteBatch = CCSpriteBatchNode::batchNodeWithTexture(texture); 

    addChild(spriteBatch); 

CCSprite* sprite=CCSprite::spriteWithSpriteFrameName("start.png");

/*

加载pvr压缩格式文件方式:注意此种方法不可以像上面打成一个文件,然后根据名字来索引对应的图片

如果在AndEngine中使用,然后利用TexturePacker是可以导出三个文件格式的,就是多出来了一个xml文件

保存着索引子图片的索引,已经图片位置等信息,cocos2dx的test也没有找到相应的例子,只有单独一个

精灵才用到了加载pvr这种格式,可能cocos2dx却没有导出这个xml,也可能是这个原因吧!

//第一种方式:

CCSprite* sprite=CCSprite::create("image.pvr.ccz");

//第二种方式:

CCTexture2D *texture;

CCTextureCache *cache=CCTextureCache::sharedTextureCache();

texture=cache->addImage("image.pvr.ccz");

CCSprite* sprite=CCSprite::create(texture);*/


sprite->setAnchorPoint(ccp(0,1));//设置sprite的描点,(0,1)也就是图片的左上角

sprite->setPosition(ccp(0,s.height));//设置sprite位置

this->addChild(sprite);


//sprite的一些基本的操作:缩放、旋转、混色

m_tamara=CCSprite::spriteWithSpriteFrameName("grossini.png");

m_tamara->setScaleX( 2.5f);

    m_tamara->setScaleY( -1.0f);

    m_tamara->setPosition(ccp(100,70) );

    m_tamara->setOpacity( 255);//RGBA值RGB+透明度值

this->addChild(m_tamara);


m_grossini=CCSprite::spriteWithSpriteFrameName("grossinis_sister1.png");

    m_grossini->setRotation( 120);

    m_grossini->setPosition( ccp(s.width/2, s.height/2));

    m_grossini->setColor( ccc3( 255,0,0));

this->addChild(m_grossini);


m_kathia=CCSprite::spriteWithSpriteFrameName("grossinis_sister2.png");

    m_kathia->setPosition( ccp(s.width-100, s.height/2));

    m_kathia->setColor( ccBLUE);

this->addChild(m_kathia);


return true;

}

SpriteTestLayer::~SpriteTestLayer(void)

{

}

运行结果:

2014-12-01 00:06:00 weixin_33834910 阅读数 34

http://dev.gameres.com/Program/Visual/3D/3Darit.htm

float 类型数据有效数字是小数点后面6位 单精度

doluble 类型数据是至少10位,双精度

在赋值是默认是double类型,所以强制转换(floag)或初始化数据后面加1.0f

在运算中double 类型数据快于float

嵌入式设备float 快于double,可以在ti dsp中测试验证!

 

转载于:https://www.cnblogs.com/pengkunfan/p/4134030.html

2018-01-08 15:42:37 u011555996 阅读数 400

图像是人类获取和交换信息的主要来源,因此,图像处理的应用领域必然涉及到人类生活和工作的方方面面。随着人类活动范围的不断扩大,图像处理的应用领域也将随之不断扩大。(1)航天和航空技术方面的应用 数字图像处理技术在航天和航空技术方面的应用,除了上面介绍的JPL对月球、火星照片的处理之外,另一方面的应用是在飞机遥感和卫星遥感技术中。许多国家每天派出很多侦察飞机对地球上有兴趣的地区进行大量的空中摄影。对由此得来的照片进行处理分析,以前需要雇用几千人,而现在改用配备有高级计算机的图像处理系统来判读分析,既节省人力,又加快了速度,还可以从照片中提取人工所不能发现的大量有用情报。从60年代末以来,美国及一些国际组织发射了资源遥感卫星(如LANDSAT系列)和天空实验室(如SKYLAB),由于成像条件受飞行器位置、姿态、环境条件等影响,图像质量总不是很高。因此,以如此昂贵的代价进行简单直观的判读来获取图像是不合算的,而必须采用数字图像处理技术。如LANDSAT系列陆地卫星,采用多波段扫描器(MSS),在900km高空对地球每一个地区以18天为一周期进行扫描成像,其图像分辨率大致相当于地面上十几米或100米左右(如1983年发射的LANDSAT-4,分辨率为30m)。这些图像在空中先处理(数字化,编码)成数字信号存入磁带中,在卫星经过地面站上空时,再高速传送下来,然后由处理中心分析判读。这些图像无论是在成像、存储、传输过程中,还是在判读分析中,都必须采用很多数字图像处理方法。现在世界各国都在利用陆地卫星所获取的图像进行资源调查(如森林调查、海洋泥沙和渔业调查、水资源调查等),灾害检测(如病虫害检测、水火检测、环境污染检测等),资源勘察(如石油勘查、矿产量探测、大型工程地理位置勘探分析等),农业规划(如土壤营养、水份和农作物生长、产量的估算等),城市规划(如地质结构、水源及环境分析等)。我国也陆续开展了以上诸方面的一些实际应用,并获得了良好的效果。在气象预报和对太空其它星球研究方面,数字图像处理技术也发挥了相当大的作用。(2)生物医学工程方面的应用 数字图像处理在生物医学工程方面的应用十分广泛,而且很有成效。除了上面介绍的CT技术之外,还有一类是对医用显微图像的处理分析,如红细胞、白细胞分类,染色体分析,癌细胞识别等。此外,在X光肺部图像增晰、超声波图像处理、心电图分析、立体定向放射治疗等医学诊断方面都广泛地应用图像处理技术。 (3)通信工程方面的应用 当前通信的主要发展方向是声音、文字、图像和数据结合的多媒体通信。具体地讲是将电话、电视和计算机以三网合一的方式在数字通信网上传输。其中以图像通信最为复杂和困难,因图像的数据量十分巨大,如传送彩色电视信号的速率达100Mbit/s以上。要将这样高速率的数据实时传送出去,必须采用编码技术来压缩信息的比特量。在一定意义上讲,编码压缩是这些技术成败的关键。除了已应用较广泛的熵编码、DPCM编码、变换编码外,目前国内外正在大力开发研究新的编码方法,如分行编码、自适应网络编码、小波变换图像压缩编码等。(4)工业和工程方面的应用 在工业和工程领域中图像处理技术有着广泛的应用,如自动装配线中检测零件的质量、并对零件进行分类,印刷电路板疵病检查,弹性力学照片的应力分析,流体力学图片的阻力和升力分析,邮政信件的自动分拣,在一些有毒、放射性环境内识别工件及物体的形状和排列状态,先进的设计和制造技术中采用工业视觉等等。其中值得一提的是研制具备视觉、听觉和触觉功能的智能机器人,将会给工农业生产带来新的激励,目前已在工业生产中的喷漆、焊接、装配中得到有效的利用。(5)军事公安方面的应用 在军事方面图像处理和识别主要用于导弹的精确末制导,各种侦察照片的判读,具有图像传输、存储和显示的军事自动化指挥系统,飞机、坦克和军舰模拟训练系统等;公安业务图片的判读分析,指纹识别,人脸鉴别,不完整图片的复原,以及交通监控、事故分析等。目前已投入运行的高速公路不停车自动收费系统中的车辆和车牌的自动识别都是图像处理技术成功应用的例子。(6)文化艺术方面的应用 目前这类应用有电视画面的数字编辑,动画的制作,电子图像游戏,纺织工艺品设计,服装设计与制作,发型设计,文物资料照片的复制和修复,运动员动作分析和评分等等,现在已逐渐形成一门新的艺术--计算机美术。

数字图像处理技术与图象处理系统是七十年代末期形成一个独立学科,当时只能处理静止图象,主要用于军事、科研医学等领域。图象处理系统是为了加快处理速度而设计的专用系统,在中小型计算机控制下运行。这些系统的规模大,价格昂贵。面向PC机的图象处理系统是八十年代中后期开始出现的。它价格便宜,易于扩充, 软件丰富,因此很快得到推广,带动了图象处理技术的普及。

在过去的二十年里,C和C++已经成为在商业软件的开发领域中使用最广泛的语言。它们为程序员提供了十分灵活的操作,不过同时也牺牲了一定的效率。与诸如Microsoft,Visual Basic, 等语言相比,同等级别的C/C++应用程序往往需要更长时间来开发。由于C/C++语言的复杂性,许多程序员都试图寻找一种新的语言,希望能在功能与效率之间找到一个更为理想的权衡点。

     目前有些语言,以牺牲灵活性的代价来提高效率。可是这些灵活性正是C/C++程序员所需要的。这些解决方案对编程人员的限制过多(如屏蔽一些底层代码控制的机制),其所提供的功能难以令人满意。这些语言无法方便地同早先的系统交互,也无法很好地和当前的网络编程相结合。

    对于C/C++用户来说,最理想的解决方案无疑是在快速开发的同时又可以调用底层平台的所有功能。他们想要一种和最新的网络标准保持同步并且能和已有的应用程序良好整合的环境。另外,一些C/C++开发人员还需要在必要的时候进行一些底层的编程。

    微软推出C# (C sharp)是微软对这一问题的解决方案。C#是一种最新的、面向对象的编程语言。它使得程序员可以快速地编写各种基于Microsoft .NET平台的应用程序,Microsoft .NET提供了一系列的工具和服务来最大程度地开发利用计算与通讯领域。正是由于C#面向对象的卓越设计,使它成为构建各类组件的理想之选--无论是高级的商业对象还是系统级的应用程序。使用简单的C#语言结构,这些组件可以方便的转化为XML 网络服务,从而使它们可以由任何语言在任何操作系统上通过Internet进行调用。最重要的是,C#使得C++程序员可以高效的开发程序,而绝不损失C/C++原有的强大的功能。因为这种继承关系,C#与C/C++具有极大的相似性,熟悉类似语言的开发者可以很快的转向C#。

    效率与安全性新兴的网络经济迫使商务企业必须更加迅速的应对竞争的威胁。开发者必须不断缩短开发周期,不断推出应用程序的新版本,而不仅仅是开发一个"标志性"的版本。

   C#在设计时就考虑了这些问题。它使开发者用更少的代码做更多的事,同时也不易出错。新的应用程序开发模型意味着越来越多地解决方案依赖于新出现的网络标准,例如HTML,XML,SOAP等。现存的开发工具往往都是早于Internet出现的,或者是在我们所熟知的网络还处于孕育期时出现的。所以,它们一般无法很好地支持最新的网络技术。

    C#程序员可以在Microsoft.NET平台上事半功倍的构建应用程序的扩展框架。C#包含了内置的特性,使任何组件可以轻松转化为XML网络服务,通过Internet被任何操作系统上运行的任何程序调用。更突出的是,XML网络服务框架可以使现有的XML网络服务对程序员来说就和C#对象一样。这样,程序员就可以方便地使用他们已有的面向对象的编程技巧来开发利用现有的XML网络服务。

     还有一些精细的特性,使得C#成为一流的网络编程工具。例如,XML正逐渐成为在网络上传输结构化数据的标准。这种数据集合往往非常小。为提高性能,C#允许把XML数据直接映射到struct数据类型,而不是class。这样对处理少量的数据非常有效。

    即使是专家级的C++程序员也常会犯一些最简单的小错误--比如忘了初始化变量,但往往就是这些小错误带来了难以预料的问题,有些甚至需要很长时间来寻找和解决。一旦一个程序作为产品来使用,就算最简单的错误纠正起来也可能要付出极其昂贵的代价。C#的现代化设计能够消除很多常见的C++编程错误。 例如:资源回收减轻了程序员内存管理的负担;C#中变量由环境自动初始化;变量是类型安全的。这样,程序员编写与维护那些解决复杂商业问题的程序就更方便了。

更新软件组件是一项很容易出错的工作,因为代码的修改可能无意间改变原有程序的语义。为协助开发者进行这项工作,C#为版本的更新提供内在的支持。例如,方法重载必须显式声明。这样可以防止编码错误,保证版本更新的灵活性。还有一个相关的特性就是对接口和接口继承的内在支持。这些特性使得C#可以开发复杂的框架并且随着时间不断发展更新它。

    总体来说,这些特性使得开发程序项目的后续版本的过程更加健壮,从而减少后续版本的开发成本。

C#语言允许类型定义的,扩展的元数据。这些元数据可以应用于任何对象。项目构建者可以定义领域特有的属性并把他们应用于任何语言元素-类,接口等等。然后,开发人员可以编程检查每个元素的属性。这样,很多工作都变得方便多了,比如编写一个小工具来自动检查每个类或接口是否被正确定义为某个抽象商业对象的一部分,或者只是创建一份基于对象的领域特有属性的报表。定制的元数据和程序代码之间的紧密对应有助于加强程序的预期行为和实际实现的之间的对应关系。

作为一种自动管理的,类型安全的环境,C#适合于大多数企业应用程序。但实际的经验表明有些应用程序仍然需要一些底层的代码,要么是因为基于性能的考虑,要么是因为要与现有的应用程序接口兼容。这些情况可能会迫使开发者使用C++,即使他们本身宁愿使用更高效的开发环境。C#采用以下对策来解决这一问题:内置对组建对象模型(COM)和基于Windows的API的支持;允许有限制地使用纯指针(Native Pointer)。

在C#中,每个对象都自动生成为一个COM对象。开发者不再需要显式的实现IUnknown和其他COM接口.这些功能都是内置的.类似的,C#可以调用现有的COM对象,无论它是由什么语言编写的。

  C#包含了一个特殊的功能,使程序可以调用任何纯API。在一段特别标记的代码中,开发者可以使用指针和传统C/C++特性,如手工的内存管理和指针运算。这是其相对于其它环境的极大优势。这意味着C#程序员可以在原有的C/C++代码的基础上编写程序,而不是彻底放弃那些代码。无论是支持COM还是纯API的调用,都是为了使开发者在C#环境中直接拥有必要的强大功能。

所以C#是一种现代的面向对象语言。它使程序员快速便捷地创建基于Microsoft .NET平台的解决方案。这种框架使C#组件可以方便地转化为XML网络服务,从而使任何平台的应用程序都可以通过Internet调用它。C#增强了开发者的效率,同时也致力于消除编程中可能导致严重结果的错误。C#使C/C++程序员可以快速进行网络开发,同时也保持了开发者所需要的强大性和灵活性。

C#中,Image为源自 Bitmap  Metafile 的类提供功能的抽象基类,使用Image可以操作各种支持的图片,如GIF, BMP, JPG Image.FromFile()返回的是某个继承自Image的具体类的对象,在这里,就是Bitmap或者Metafile其中之一。这Bitmap不仅仅对应于bmp,其实只要是像素式的图片格式(矢量格式不行),理论上都可以用Bitmap。由于Bitmap是忽略图像格式的,所以,在本图像处理的源代码中,并没有给出不同图像格式转换的代码,我们所做的仅仅是创建一个Bitmap对象,用 Image.FromFile()方法载入图像并保存到我们所创建的Bitmap对象中即可。对载入图像,我们可以使用SystemDrawing命名空间里提供的Getpixel方法提取像素的RGB值来进行处理。

2.1 BMP图像

2.1 BMP图像的基本介绍

如今Windows(3.x以及95,98,NT)系列已经成为绝大多数用户使用的操作系统,它比DOS成功的一个重要因素是它可视化的漂亮界面。那么Windows是如何显示图象的呢?这就要谈到位图(bitmap)。

在 Windows 3.0 以前,Windows系统用的是DDB(设备有关位图)。DDB没有调色板,显示的颜色依赖硬件,处理色彩很不方便。所以 Microsoft 在 Windows 3.0中 重新定义了BMP文件格式(BMP 3.0),使其支持设备无关位图——也就是DIB。时至今日,BMP的版本号已升至5.0(Windows NT 4.0、Windows95 定义了 BMP 4.0,Windows 98、Windows 2000 定义了 BMP 5.0),但基本结构没有变——仍是 BMP文件头 和 DIB 组成。

Windows 3.1以上版本提供了对设备无关位图DIB的支持。DIB位图可以在不同的机器或系统中显示位图所固有的图像。与DDB相比而言,DIB是一种外部的位图格式,经常存储为以BMP为后缀的位图文件(有时也以DIB为后缀)。DIB位图还支持图像数据的压缩。与Windows DIB结构相似,但不完全相同的另一种DIB是OS/2采用的DIB。

DIB位图的位数据紧跟在颜色表后面。数据可以是不压缩的,也可以是压缩的。对4位和8位位图,可以采用RLE(游程长度编码)压缩,分别称为RLE4和RLE8位图。

位数据以行为单位存储,每行都被填充到一个四字节边界,即每行所占的存储长度总是四字节(32位)的倍数,不足时将多余位用0填充。位图行的存储次序是颠倒的,即位图文件中第一行数据对应的是位图的最底行。对于像素位数为1的DIB位图,其每个像素只占1位,每个字节存储八个像素。字节的最高位对应于最左边的像素。在没有压缩的像素位数为4的DIB位图中,每个字节存储两个像素,高四位对应于最左边的像素,每行填充到一个四字节边界。采用RLE编码压缩的四位DIB由一系列组组成。有三种类型的组:重复组、文字组和特殊组。重复组由两个字节组成,第一个字节表示像素个数,第二个字节表示一对像素的值。文字组由一个0字节、一个像素计数字节和文字像素字节组成。像素计数值必须至少为3(小于3时,可采用重复组编码),文字像素应填充到一个偶数字节边界。特殊组中,00 00表示一行的结束,00 01表示位图的结束,00 02 xx yy表示位置增量,即图像向右走xx个像素,向下走yy个像素。

在没有压缩的像素位数为8的DIB位图中,每个字节存储一个像素,每行填充到一个四字节边界。采用R LE编码压缩的四位DIB由一系列组组成。有三种类型的组:重复组、文字组和特殊组。重复组内两个字节组成,第一个字节表示像素个数,第二个字节表示像素值。文字组由一个0字节、一个像素计数字节和文字像素字节组成。像素计数值必须至少为3(小于3时,可采用重复组编码),文字像素应填充到一个偶数字节边界。特殊组中,00 00表示一行的结束,00 01表示位图的结束,0002xx yy表示位置增量,即图像向右走xx个像素, 向下走yy个像素。在像素位数为24的DIB位图中,每个像素占三字节,从左到右的每一字节分别存储蓝、绿、红的颜色值。每行用0填充到一个四字节边界。

OS/2 DIB和Windows DIB的主要区别是位图信息结构(信息头结构和颜色表结构)不同。而它们的图像位数据的存储方式是完全一样的。

我们知道,普通的显示器屏幕是由许许多多点构成的,我们称之为象素。显示时采用扫描的方法:电子枪每次从左到右扫描一行,为每个象素着色,然后从上到下这样扫描若干行,就扫过了一屏。为了防止闪烁,每秒要重复上述过程几十次。例如我们常说的屏幕分辨率为640×480,刷新频率为70Hz,意思是说每行要扫描640个象素,一共有480行,每秒重复扫描屏幕70次。我们称这种显示器为位映象设备。所谓位映象,就是指一个二维的象素矩阵,而位图就是采用位映象方法显示和存储的图象。举个例子,图1.1是一幅普通的黑白位图,图1.2是被放大后的图,图中每个方格代表了一个象素。我们可以看到:整个骷髅就是由这样一些黑点和白点组成的。在设计中,我们也是对图像的像元进行处理的。

    而自然界中的所有颜色都可以由红、绿、蓝(R,G,B)组合而成。有的颜色含有红色成分多一些,如深红;有的含有红色成分少一些,如浅红。针对含有红色成分的多少,可以分成0到255共256个等级,0级表示不含红色成分;255级表示含有100%的红色成分。同样,绿色和蓝色也被分成256级。这种分级概念称为量化。这样,根据红、绿、蓝各种不同的组合我们就能表示出256×256×256,约1600万种颜色。这么多颜色对于我们人眼来说已经足够丰富了。

常见颜色的RGB组合值

颜色   R     G      B

红     255    0      0

蓝      0    255     0

绿      0     0     255

黄     255   255     0

紫     255    0     255

青      0    255    255

白     255   255    255

黑      0     0      0

灰     128   128    128

当一幅图中每个象素赋予不同的RGB值时,能呈现出五彩缤纷的颜色了,这样就形成了彩色图。

图象数据就是该象素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。对于2色位图,用1位就可以表示该象素的颜色(一般0表示黑,1表示白),所以一个字节可以表示8个象素。对于16色位图,用4位可以表示一个象素的颜色,所以一个字节可以表示2个象素。对于256色位图,一个字节刚好可以表示1个象素。对于真彩色图,三个字节才能表示1个象素。

要注意两点:

  (1)每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍biSizeImage时已经提到了。

  (2)一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图象最下面一行的左边第一个象素,然后是左边第二个象素……接下来是倒数第二行左边第一个象素,左边第二个象素……依次类推,最后得到的是最上面一行的最右一个象素。

  Windows操作系统统一管理着诸如显示,打印等操作,将它们看作是一个个的设备,每一个设备都有一个复杂的数据结构来维护。所谓设备上下文就是指这个数据结构。然而,我们不能直接和这些设备上下文打交道,只能通过引用标识它的句柄(实际上是一个整数),让Windows去做相应的处理。

2.2  图像的读取

    图像读取主要方法是利用OpenFileDialog(文件打开控件)获得打开图像的绝对路径,用System.Drawing里Bitmap.FromFile方法将所获得的图像转换成DIB并加载到内存中,同时将加载的图像复制到pictureBox1图像框中。

private void menuItem2_Click(object sender, System.EventArgs e)

{

OpenFileDialog imageopen = new OpenFileDialog();

imageopen.Title = "请打开图像文件" ;

imageopen.InitialDirectory = @"c:/" ;

imageopen.Filter = "(图像文件)*.bmp;jepg;jpg;gif;png|*.bmp;*.jepg;*.jpg;*.gif;*.png" ;

imageopen.FilterIndex = 1 ;

imageopen.RestoreDirectory = true ;

if(imageopen.ShowDialog() == DialogResult.OK)

{

filepath=imageopen.FileName;

this.progressBar1.Value=20;

this.pictureBox1.Image=System.Drawing.Bitmap.FromFile(filepath);

}

this.pictureBox2.Image=null;

3  灰度直方图

3.1灰度直方图的基本概念

从某种角度上来讲,图像处理是基于统计学概念上的,所以,为了能够将图像变成计算机所能够识别并处理的数据,我们必须对图像进行量化,使得我们能从数值概念上获得对图像的映像。这里,我们引入灰度图像的概念:灰度图像是一种具有从黑到白256级灰度色域或等级的单色图像。该图像中的每个像素用8位数据表示,因此像素点值介于黑白间的256种灰度中的一种。该图像只有灰度等级,而没有颜色的变化。这样,我们可以将图像的RGB属性归一为灰度属性,由此就可以方便我们对图像进行处理。

3.2  如何获取灰度直方图

首先我们应该清楚的是灰度直方图是一个从0-255范围变化的步长为一的数组,数组的每一个元素对应的是每一个灰度值。在这里,我们首先定义了一个int型数组,数组的大小为256,数组名为Histogram。其中,Histogram[i]对应得是灰度为i的像素的个数。

在这里,我们使用的计算灰度的算法为Gray=(int)(0.3*r+0.59*g+0.11*b)。其中,r,g,b分别为所处理像素的RGB值。算法的源代码如下:

private void menuItem24_Click(object sender, System.EventArgs e)

       {

               int height=this.pictureBox1.Image.Height;

              int width=this.pictureBox1.Image.Width;

              Bitmap process=(Bitmap)this.pictureBox1.Image;

              Color pixel;

              int  [] Histogram=new int[256];

              int Times,Gray,r,g,b;

              for (i=0;i<=255;i++)

              {

                  Histogram[i]=0;

              }

              for(i=0;i<width;i++)

              {

                  for(j=0;j<height;j++)

                  {

                     pixel=process.GetPixel(i,j);

                     r=pixel.R;

                     g=pixel.G;

                     b=pixel.B;

                     Gray=(int)(0.3*r+0.59*g+0.11*b);

                     Histogram[Gray]=Histogram[Gray]+1;

                  }

              }

       }

下图为对某一图像处理后所获得的灰度直方图,从该图中我们可以获得一个图像的灰度分布的直观映像。

4         图象的几何变换

4.1平移

平移(translation)变换大概是几何变换中最简单的一种了。如下图,初始坐标为(x0,y0)的点经过平移(tx,ty)(以向右,向下为正方向)后,坐标变为(x1,y1)。这两点之间的关系是x1=x0+tx ,y1=y0+ty。

如下图所示

 

以矩阵的形式表示为

 

我们更关心的是它的逆变换:

 

这是因为:我们想知道的是平移后的图象中每个象素的颜色。例如我们想知道,新图中左上角点的RGB值是多少?很显然,该点是原图的某点经过平移后得到的,这两点的颜色肯定是一样的,所以只要知道了原图那点的RGB值即可。那么到底新图中的左上角点对应原图中的哪一点呢?将左上角点的坐标(0,0)入公式(2.2),得到x0=-tx ,y0=-ty;所以新图中的(0,0)点的颜色和原图中(-tx , -ty)的一样。这样就存在一个问题:如果新图中有一点(x1,y1),按照公式(2.2)得到的(x0,y0)不在原图中该怎么办?通常的做法是,把该点的RGB值统一设成(0,0,0)或者(255,255,255)。

4.2旋转

旋转(rotation)有一个绕着什么转的问题,通常的做法是以图象的中心为圆心旋转。在我们熟悉的坐标系中,将一个点顺时针旋转a角后的坐标变换公式,如下图所示,r为该点到原点的距离,在旋转过程中,r保持不变;b为r与x轴之间的夹角。

 

旋转前:x0=rcosb;y0=rsinb

旋转a角度后:

x1=rcos(b-a)=rcosbcosa+rsinbsina=x0cosa+y0sina;

y1=rsin(b-a)=rsinbcosa-rcosbsina=-x0sina+y0cosa;

以矩阵的形式表示:

 

上面的公式中,坐标系xoy是以图象的中心为原点,向右为x轴正方向,向上为y轴正方向。它和以图象左上角点为原点o’,向右为x’轴正方向,向下为y’轴正方向的坐标系x’o’y’之间的转换关系如何呢

 

设图象的宽为w,高为h,容易得到:

 

逆变换为:

 

理解了上述理论基础,其实在C#中我们有现成的方法函数进行操作,我们可以利用Graphics对象所生成的g.RotateTransform方法函数来对图像进行旋转操作。图像旋转后我们还需要将旋转所得到的图像填充到指定的矩形区域中,在这里我们使用了g.FillRectangle方法函数来进行填充。

private void panel2_Paint(object sender, System.Windows.Forms.PaintEventArgs e)

       {

              this.panel2.Refresh();

              Graphics g = e.Graphics;

              int angel=Convert.ToInt16(this.numericUpDown4.Value);

              System.Drawing.Bitmap temp=new Bitmap(filepath);

              TextureBrush brush=new TextureBrush(temp);

              g.RotateTransform(flaot(angel));            g.FillRectangle(brush,0,0,this.ClientRectangle.Width,this.ClientRectangle.Height);

              return;

       }

 4.3缩放

假设放大因子为ratio,(为了避免新图过大或过小,我们在程序中限制0.25≤ratio≤4),缩放(zoom)的变换矩阵很简单:

 

由于放大图象时产生了新的象素,以及浮点数的操作,得到的坐标可能并不是整数,这一点我们在介绍旋转时就提到了。我们采用的做法是找与之最临近的点。实际上,更精确的做法是采用插值(interpolation),即利用邻域的象素来估计新的象素值。其实我们前面的做法也是一种插值,称为最邻近插值(Nearest Neighbour Interpolation)。下面先介绍线形插值(Linear Interpolation)。

线形插值使用原图中两个值来构造所求坐标处的值。举一个一维的例子。下图所示,如果已经知道了两点x0,x2处的函数值f(x0),f(x2),现在要求x1处的函数值f(x1)。我们假设函数是线形的,利用几何知识可以知道

f(x1)=(f(x2)-f(x0))(x1-x0)/(x2-x0)+f(x0)

在图象处理中需要将线形插值扩展到二维的情况,即采用双线形插值(Bilinear Intrepolation), 

线形插值的示意图

 

   双线形插值的示意图

已知a、b、c、d四点的灰度,要求e点的灰度,可以先在水平方向上由a,b线形插值求出g、c、d线形插值求出f,然后在垂直方向上由g,f线形插值求出e。

线形插值基于这样的假设:原图的灰度在两个象素之间是线形变化的。一般情况下,这种插值的效果还不错。更精确的方法是采用曲线插值(Curvilinear Interpolation),即认为象素之间的灰度变化规律符合某种曲线,但这种处理的计算量是很大的。

同样的,我们可以利用Graphics对象所生成的g.FillRectangle方法函数来对图像进行缩放操作。图像缩放后我们还需要将缩放所得到的图像填充到指定的矩形区域中,同样在这里我们使用了g.FillRectangle方法函数来进行填充。

private void panel2_Paint(object sender, System.Windows.Forms.PaintEventArgs e)

       {

           this.panel2.Refresh();

              Graphics g = e.Graphics;

              float fx=(float)(this.numericUpDown1.Value/10);

              float fy=(float)(this.numericUpDown2.Value/10);

              System.Drawing.Bitmap temp=new Bitmap(filepath);

              TextureBrush brush=new TextureBrush(temp);

              g.ScaleTransform(fx,fy);

               g.FillRectangle(brush,0,0,this.ClientRectangle.Width,this.ClientRectangle.Height);

              checkscale=0;

       }

5 简单图像处理

5.1  黑白处理:

彩色图像黑白化处理通常有三种方法:最大值法、平均值法、加权平均值法
三种方法的原理
        最大值法:最大值法是每个像素点的RGB值等于原像素点的RGB值中最大的一个,即R=G=B=MAX( R,G,B ); 效果,最大值发产生亮度很高的黑白图像。
        平均值法:平均值法使每个像素点的RGB值等于原像素点的RGB值的平均值,即R=G=B=(R+G+B)/3 
        加权平均法:加权平均法根据需要指定每个像素点RGB的权数,并取其加权平均值,即R=G=B=(Wr*R+Wg*G+Wb*B )/3 。Wr、Wg、Wb表示RGB的权数,均大于零,通过取不同的权数可实现不同的效果。

  本程序中采用的是平均值法来处理图像:

private void menuItem20_Click(object sender, System.EventArgs e)

       {

           if(this.pictureBox1.Image!=null)

           {

              this.pictureBox2.Visible=true;

              int height=this.pictureBox1.Image.Height;

              int width=this.pictureBox1.Image.Width;

              Bitmap temp=new Bitmap(width,height);

              Bitmap process=(Bitmap)this.pictureBox1.Image;

              Color pixel;

              for( int x=0;x<width;x++)

              {

                  for(int y=0;y<height;y++)

                  {

                     int r,g,b,t;

                     pixel=process.GetPixel(x,y);

                     t=(pixel.R+pixel.G+pixel.B)/3;

                      r=t;

                     g=t;

                     b=t;

                     temp.SetPixel(x,y,Color.FromArgb(r,g,b));

                  }

              }

              this.pictureBox2.Image=temp;

           }

       }     

 

5.2  浮雕处理

浮雕效果就是只将图像的变化部分突出出来,而相同颜色部分则被淡化,使图像出现纵深感,从而达到浮雕效果,这里采用的算法是将要处理的像素取值为与处于对角线上的另一个像素间的差值,这样只有颜色变化区才会出现色彩,而颜色平淡区因差值几乎为零则变成黑色。

         private void menuItem21_Click(object sender, System.EventArgs e)

       {

           if(this.pictureBox1.Image!=null)

           {

              this.pictureBox2.Visible=true;

              int height=this.pictureBox1.Image.Height;

              int width=this.pictureBox1.Image.Width;

              Bitmap temp=new Bitmap(width,height);

              Bitmap process=(Bitmap)this.pictureBox1.Image;

              Color pixel,pixelnext;

              for( int x=0;x<width-1;x++)

              {

                  this.progressBar1.Value=(int)(((float)x/(float)width)*100);

                  for(int y=0;y<height-1;y++)

                  {

                 

                     int r,g,b;

                     pixel=process.GetPixel(x,y);

                     pixelnext=process.GetPixel(x+1,y+1);

                     r=pixel.R-pixelnext.R+128;

                     b=pixel.B-pixelnext.B+128;

                     g=pixel.G-pixelnext.G+128;

                     r=Judge(r);

                     g=Judge(g);

                     b=Judge(b);

                     temp.SetPixel(x,y,Color.FromArgb(r,g,b));

                  }

              }

              this.pictureBox2.Image=temp;

           }

5.2  反色处理

反色的实际含义是将R、G、B值反转。若颜色的量化级别是256,则新图的R、G、B值为255减去原图的R、G、B值。这里针对的是所有图,包括真彩图、带调色板的彩色图(又称为伪彩色图)、和灰度图。

private void menuItem22_Click(object sender, System.EventArgs e)

       {

           if(this.pictureBox1.Image!=null)

           {

              this.pictureBox2.Visible=true;

              int height=this.pictureBox1.Image.Height;

              int width=this.pictureBox1.Image.Width;

              Bitmap temp=new Bitmap(width,height);

              Bitmap process=(Bitmap)this.pictureBox1.Image;

              Color pixel;

              for( int x=0;x<width;x++)

              {

                  this.progressBar1.Value=(int)(((float)x/(float)width)*100);

                  for(int y=0;y<height;y++)

                  {

                     int r,g,b;

                     pixel=process.GetPixel(x,y);

                     r=255-pixel.R;

                     g=255-pixel.G;

                     b=255-pixel.B;

                     temp.SetPixel(x,y,Color.FromArgb(r,g,b));

                  }

              }

              this.pictureBox2.Image=temp;

           }

       }

6图象的轮廓提取

图像的边缘(轮廓)是图像最基本的特征。所谓边缘(或边沿)是指其周围象素灰度有阶跃 变化或“屋顶”变化的那些象素的集合。边缘广泛存在于物体与背景之间、物体与物体之间、基元与基元之间。因此,它是图像分割依赖的重要特征。

物体的边缘是由灰度不连续性形成的。经典的边缘提取方法是考察图像的每个象素在某个邻域内灰度的变化,利用边缘邻近一阶或二阶方向导数变化规律,用简单的方法检测边缘。这种方法称为边缘检测局部算子法。如果一个象素落在图像中某一个物体的边界上,那么他 的邻域将成为一个灰度级的变化带。对这种变化最有用的2个特征:灰度的变化率和方向, 他们分别以梯度向量的幅度和方向来表示。边缘检测算子检查每个象素的邻域并对灰度变化率进行量化,也包括方向的确定。常用的检测算子有Roerts算子、Sobel算子、Prewitt 算子和 Kirsh 算子等。

我们给出一个模板 和一幅图象 。不难发现原图中左边暗,右边亮,中间存在着一条明显的边界。进行模板操作后的结果如下:

可以看出,第34列比其他列的灰度值高很多,人眼观察时,就能发现一条很明显的亮边,其它区域都很暗,这样就起到了边沿检测的作用。为什么会这样呢?仔细看看那个模板就明白了,它的意思是将右邻点的灰度值减左邻点的灰度值作为该点的灰度值。在灰度相近的区域内,这么做的结果使得该点的灰度值接近于0;而在边界附近,灰度值有明显的跳变,这么做的结果使得该点的灰度值很大,这样就出现了上面的结果。这种模板就是一种边沿检测器,它在数学上的涵义是一种基于梯度的滤波器,又称边沿算子,你没有必要知道梯度的确切涵义,只要有这个概念就可以了。梯度是有方向的,和边沿的方向总是正交(垂直)的,例如,对于上面那幅图象的转置图象,边是水平方向的,我们可以用梯度是垂直方向的模板 检测它的边沿。

例如,一个梯度为45度方向模板 ,可以检测出135度方向的边沿。

6 .1Sobel算子轮廓提取:

在边沿检测中,常用的一种模板是Sobel 算子。Sobel 算子有两个,一个是检测水平边沿的 ;另一个是检测垂直平边沿的 。与 相比,Sobel算子对于象素的位置的影响做了加权,因此效果更好。

Sobel算子另一种形式是各向同性Sobel(Isotropic Sobel)算子,也有两个,一个是检测水平边沿的 ,另一个是检测垂直平边沿的 。各向同性Sobel算子和普通Sobel算子相比,它的位置加权系数更为准确,在检测不同方向的边沿时梯度的幅度一致。由于建筑物图像的特殊性,我们可以发现,处理该类型图像轮廓时,并不需要对梯度方向进行运算,所以程序并没有给出各向同性Sobel算子的处理方法。下面的两幅图中,下左图为原图;下右图为Sobel算子处理后的结果图。可以看出Sobel算子确实把图象中的边沿提取了出来。

                        

原图                                Sobel算子处理后的结果图

    由于Sobel算子是滤波算子的形式,用于提取边缘,可以利用快速卷积函数, 简单有效,因此应用广泛。美中不足的是,Sobel算子并没有将图像的主体与背景严格地区分开来,换言之就是Sobel算子没有基于图像灰度进行处理,由于Sobel算子没有严格地模拟人的视觉生理特征,所以提取的图像轮廓有时并不能令人满意。  在观测一幅图像的时候,我们往往首先注意的是图像与背景不同的部分,正是这个部分将主体突出显示,基于该理论,我们给出了下面阈值化轮廓提取算法,该算法已在数学上证明当像素点满足正态分布时所求解是最优的。

6 .2阈值化提取:

阈值化(thresholding)可以看作是削波的一个特例,我们用下图说明阈值化的原理。

 

阈值化的原理

不难看出,只要令削波中的g1old=g2old就实现了阈值化。阈值就象个门槛,比它大就是白,比它小就是黑。经过阈值化处理后的图象变成了黑白二值图,所以说阈值化是灰度图转二值图的一种常用方法(我们以前介绍过图案化和抖动的方法)。进行阈值化只需给出阈值点g1old即可。一般情况下,阈值的选取对程序执行结果有很大的影响,在本程序中采取的是直方图求门限,迭代法求最佳阈值的方法,这个方法在数学上已被证明当图像像素概率分布逼近正态分布时是最佳的。阈值化处理后的结果,是一幅二值图像

 

阈值化处理后的结果

在这里,阈值的选取是很重要的,阈值如果过高,就会产生将图像主体误认为是背景的情况;阈值如果过低,则会将背景部分误认为是主体部分。因此,我们应该仔细选择阈值,我认为阈值不应该是一个固定的值,它的值应该随着图像的不同而不同。在本程序中,采取了动态求取阈值的方法。首先我们先求得被处理图像的灰度直方图,根据直方图求得最大灰度与最小灰度。我们设置阈值的初值为最大灰度与最小灰度值之和的一半。然后求得小于阈值部分灰度的平均值iMean1Gray和大于阈值部分的平均值iMean2Gray,将阈值重新设为这两个灰度均值 的一半,如此迭代,最后求得最佳阈值iThreshold。实验结果证明该阈值是可信的,基本上可以把主体与背景区分开来。求阈值的代码如下:

for(Times=0;Times<128&&iThreshold!=iNewThreshold;Times++)

           {

               iThreshold=iNewThreshold;

              lP1=0;

              lP2=0;

              lS1=0;

              lS2=0;

              for(i=iMinGray;i<iThreshold;i++)

              {

                  lP1+=Histogram[i]*i;

                  lS1+=Histogram[i];

              }

              iMean1Gray=lP1/lS1;

              for(i=iThreshold;i<iMaxGray;i++)

              {

                  lP2+=Histogram[i]*i;

                  lS2+=Histogram[i];

              }

              iMean2Gray=lP2/lS2;

              iNewThreshold=(iMean1Gray+iMean2Gray)/2;

          }

6 .3  Robert算子轮廓提取

Roberts边缘检测算子是一种利用局部差分算子寻找边缘的算子

 

                     Robert算子轮廓提取结果

可以看出,图像处理后结果边缘不是很平滑。经分析,由于Robert算子通常会在图像边缘附近的区域内 产生较宽的响应,故采用上述算子检测的边缘图像常需做细化处理,边缘定位的精度不是很高。

private void menuItem23_Click(object sender, System.EventArgs e)

       {

           if(this.pictureBox1.Image!=null)

           {

              this.pictureBox2.Visible=true;

              int height=this.pictureBox1.Image.Height;

              int width=this.pictureBox1.Image.Width;

              Bitmap temp=new Bitmap(width,height);

              Bitmap process=(Bitmap)this.pictureBox1.Image;

              int i,j,p0,p1,p2,p3;

              Color [] pixel=new Color[4];

              int result;

              for(j=height-2;j>0;j--)

              {

                  for(i=0;i<width-2;i++)

                  {

                      pixel[0]=process.GetPixel(i,j);

                     pixel[1]=process.GetPixel(i,j+1);

                     pixel[2]=process.GetPixel(i+1,j);

                     pixel[3]=process.GetPixel(i+1,j+1);

                     p0=(int)(0.3*pixel[0].R+0.59*pixel[0].G+0.11*pixel[0].B);

                     p1=(int)(0.3*pixel[1].R+0.59*pixel[1].G+0.11*pixel[1].B);

                     p2=(int)(0.3*pixel[2].R+0.59*pixel[2].G+0.11*pixel[2].B);

                     p3=(int)(0.3*pixel[3].R+0.59*pixel[3].G+0.11*pixel[3].B);

                     result=(int)Math.Sqrt((p0-p3)*(p0-p3)+(p1-p2)*(p1-p2));

                     if (result>255)

                         result=255;

                     if (result<0)

                         result=0;

                     temp.SetPixel(i,j,Color.FromArgb(result,result,result));

                  }

              }

              this.pictureBox2.Image=temp;

           }

       }

6程序运行

6 .1 程序运行主界面

 

6 .2 相关功能介绍

在文件菜单下,可以实现图像的打开,保存,程序的退出等功能。

 

编辑菜单下可以将操作结果栏的图像复制到缓存中

 

视图菜单下可实现程序的所有图像处理功能,并可以将被处理图像转换为源图像。

 

窗口栏可将操作结果的图像关闭。

 

程序处理图像的结果如下图所示,左边为被处理图像,右边为处理结果图像。

 

结  论

数字图像处理主要研究的内容有以下几个方面: (1) 图像变换 由于图像阵列很大,直接在空间域中进行处理,涉及计算量很大。因此,往往采用各种图像变换的方法,如傅立叶变换、沃尔什变换、离散余弦变换等间接处理技术,将空间域的处理转换为变换域处理,不仅可减少计算量,而且可获得更有效的处理(如傅立叶变换可在频域中进行数字滤波处理)。(2) 图像编码压缩 图像编码压缩技术可减少描述图像的数据量(即比特数),以便节省图像传输、处理时间和减少所占用的存储器容量。压缩可以在不失真的前提下获得,也可以在允许的失真条件下进行。编码是压缩技术中最重要的方法,它在图像处理技术中是发展最早且比较成熟的技术。(3) 图像增强和复原图像增强和复原的目的是为了提高图像的质量,如去除噪声,提高图像的清晰度等。图像增强不考虑图像降质的原因,突出图像中所感兴趣的部分。如强化图像高频分量,可使图像中物体轮廓清晰,细节明显;如强化低频分量可减少图像中噪声影响。图像复原要求对图像降质的原因有一定的了解,一般讲应根据降质过程建立"降质模型",再采用某种滤波方法,恢复或重建原来的图像。(4) 图像分割 图像分割是数字图像处理中的关键技术之一。图像分割是将图像中有意义的特征部分提取出来,其有意义的特征有图像中的边缘、区域等,这是进一步进行图像识别、分析和理解的基础。虽然目前已研究出不少边缘提取、区域分割的方法,但还没有一种普遍适用于各种图像的有效方法。因此,对图像分割的研究还在不断深入之中,是目前图像处理中研究的热点之一,这个同时也是本设计的重中之重。(5) 图像描述 图像描述是图像识别和理解的必要前提。作为最简单的二值图像可采用其几何特性描述物体的特性,一般图像的描述方法采用二维形状描述,它有边界描述和区域描述两类方法。对于特殊的纹理图像可采用二维纹理特征描述.(6) 图像分类(识别)属于模式识别的范畴,其主要内容是图像经过某些预处理(增强、复原、压缩)后,进行图像分割和特征提取,从而进行判决分类。图像分类常采用经典的模式识别方法,有统计模式分类和句法(结构)模式分类,近年来新发展起来的模糊模式识别和人工神经网络模式分类在图像识别中也越来越受到重视

2018-09-06 09:31:24 zhu2027671609 阅读数 1958

一、要求:

      1.利用Qt和QPainter实现一个图像处理程序,有菜单栏、工具栏和状态栏

       2.有图像选择对话框,以选择和读取图像

       3.状态栏显示图像的像素,位深,导入图像的路径和鼠标所在点的像素点坐标

      4.可以对图像缩放,旋转,水平和垂直翻转,显示原图和全屏

二、思路和过程:

       首先利用QAction实现菜单栏和工具栏的动作,并将这些动作加入到各自的栏目当中,然后设置四个Label控件放置状态栏中,以显示要求,部分代码如下:

openAction = new QAction(QIcon(tr(":/imgs/openfile.jpg")),tr("&Open..."),this);
connect(openAction, SIGNAL(triggered()), this, SLOT(open()));

exitAction = new QAction(QIcon(tr(":/imgs/exit.png")),tr("E&xit"),this);
connect(exitAction, SIGNAL(triggered()), this, SLOT(close()));

zoominAction = new QAction(QIcon(tr(":/imgs/zoomin.png")),tr("Zoom In"),this);
connect(zoominAction, SIGNAL(triggered()),this , SLOT(zoomIn()));

zoomoutAction = new QAction(QIcon(tr(":/imgs/zoomout.jpg")),tr("Zoom out"), this);
connect(zoomoutAction, SIGNAL(triggered()), this,SLOT(zoomOut()));
......
fileMenu = menuBar()->addMenu("&File");
fileMenu->addAction(openAction);
fileMenu->addAction(exitAction);

zoomToolBar = addToolBar("rotate");
zoomToolBar->addAction(r_rotateAction);
zoomToolBar->addAction(l_rotateAction);

statusBar()->addWidget(addressLabel);
statusBar()->addWidget(depthLabel);
statusBar()->addWidget(pixLabel);
statusBar()->addWidget(locationLabel);

        之后,利用QScrollArea()设置背景,然后将一个自定义的CameraImage类放置于QScrollArea中,用于显示图片和实现相关功能。其中比较重要的几个知识点是QPixmap、Qtransform、QPainter和信道间通信传值的过程。

 QPixmap

       在这里,利用了Qpixmap 的load()方法加载图片,transform()方法实现图片的变换,而size()方法用于获取当前图像的大小

Qtransform

       Qtransform的主要功能是控制二维坐标的变换,是一个3*3的矩阵,包括实现位移、缩放、扭曲和旋转,在这里我们使用rotate()来实现旋转功能,而translate用于实现平移。为了实现图像的镜像变换,我们利用toImage()方法获取image类型的图像,并且利用mirrored()来实现镜像。map()方法则是用于设置逻辑坐标和物理坐标的转换比例。

QPainter

     QPainter是用于画图的控件,其中的QPen用于画图像的边缘部分,这里我们可以用drawLine()来画网格线,利用它的settransform()来获取当前的坐标系。

       知道这些之后,我们就可以设置相应的坐标系以及变换了。将当前的图像放入QPainter中,然后利用transform()进行坐标变换,确定位置坐标,代码如下:

QTransform old = painter.transform();
            QTransform current;
            current.scale(xscale,xscale);//设置当前尺寸
            painter.setTransform(current);//设置坐标系
            m_trs = painter.worldTransform();//世界变化
            QPointF logicpos = current.inverted().map(QPoint(edgeDistance,edgeDistance));//确定逻辑原点
            painter.drawPixmap(logicpos, currentpixmap);//物理坐标
            DrawGrid(painter, current);//划线
            painter.setTransform(old);//恢复默认

       通过这,我们就能够建立好坐标系了,再利用一个mousemoveEvent()事件,算出当前鼠标位置的坐标,利用信道——槽,将其传入到状态栏中:

QPointF physicalpos = event->pos()-QPointF(edgeDistance,edgeDistance);//以图片的起点为位置原点
        pos = m_trs.inverted().map(physicalpos);//物理坐标转化为逻辑坐标
        pos-=QPoint(0.5, 0.5);//以像素点中心点为原点
      //  qDebug()<<pos.x()<<pos.y();
        emit mouseMove(pos);//发送信号

大致的,整个项目就完成了。

三、结果

原图:

 

这一章是放大后并显示点线的图像:

 水平镜像对称:

顺时针旋转90度:

2017-07-02 12:26:34 Mahabharata_ 阅读数 4484

时间:2017年7月2日(大三下学期)


写在前面:

        最近觉得Steam的软件界面非常的美观,尤其是图片的渐隐、雾化效果看起来非常漂亮。无奈我PS技术不行,手笨做不出那样的效果…… 于是,我就自己写了一款方便高效的图像处理程序,只需设置相应的参数即可完成处理(懒人模式)。如果点入的读者只是为了使用程序,那么可以无视我下面啰啰嗦嗦的科普,通过下面的链接,就获取到我的打包程序:

                     http://download.csdn.net/detail/mahabharata_/9886497

在正文开始之前,首先贴一下效果图:

(1) 程序界面效果图


(2) 图片的处理效果图:

     

      

     


【原理一】 图像雾化 —— Perlin Noise (柏林噪声)

        关于柏林噪声,我曾经写过一篇“基于Perlin Noise实现的Minecraft游戏”,这里也贴一下传送门:

                  柏林噪声实现的Minecraft游戏

      柏林噪声是由Ken Perlin于1983年提出的一种梯度噪声(Gradient Noise,通常由计算机模拟得到的一组噪声,相较于传统的离散数值噪声value noise要更加连续平滑)他在1985年的SIGGRAPH会议上,做了一场以“An Image Synthesizer”为题的学术报告,正式提出他的这一发现。

      柏林噪声的应用非常广泛: 合成地形高度图、生成物体表面的复杂纹理、火焰烟雾特效、波动效果的模拟等等。下面的几副图片就是以Perlin Noise为原理做出的效果:

                                   

      关于Perlin Noise的基本原理,在wikipedia和perlin的个人主页上都能找到,这里不多做赘述,大家可以通过下面的链接找到关于Ken Perlin关于柏林噪声的介绍以及它的其他研究成果(看了一下感觉蛮有意思的):

     Ken Perlin的个人主页:http://mrl.nyu.edu/~perlin/(纽约大学-媒体研究实验室 nyu Media Research Lab)

     这里只贴一下我的相关实现代码:

class ImageFogger
{
public:
    ImageFogger();

    QImage processImage(const QImage& img);    // 处理img,并返回图像

    float m_persistence;
    int m_octaveNum;
    float m_frequency;
    int m_amplitude;

    // 噪声相关
    double Noise(int x,int y);
    double SmoothedNoise(int x, int y);
    double Cosine_Interpolate(double a,double b, double x);
    double InterpolatedNoise(float x,float y);
    double PerlinNoise(float x,float y);

    QColor reverseRGB(int r,int g,int b);   // 修正像素
};

#include "imagefogger.h"

ImageFogger::ImageFogger()
{
    m_persistence = 0.50;
    m_octaveNum = 4;
    m_frequency = 0.025;
    m_amplitude = 200;
}

// 处理图像
QImage ImageFogger::processImage(const QImage &img)
{
    QImage pimg = img;

    for(int i=0; i<pimg.height() ; i++)          // 遍历图像
    {
        for(int k=0; k<pimg.width(); k++)
        {
            QRgb pixel = pimg.pixel(k,i);

            QColor rgb(pixel);

            double noise = m_amplitude*PerlinNoise(k*m_frequency,i*m_frequency);    // 获取噪声数值

            int r = (noise+rgb.red());
            int g = (noise+rgb.green());
            int b = (noise+rgb.blue());

            pimg.setPixel(k,i,reverseRGB(r,g,b).rgb());
        }
    }

    return pimg;
}

double ImageFogger::Noise(int x,int y)    // 根据(x,y)获取一个初步噪声值
{
    int n = x + y * 57;
    n = (n<<13) ^ n;
    return ( 1.0 - ( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);
}

double ImageFogger::SmoothedNoise(int x, int y)   //光滑噪声
{
    double corners = ( Noise(x-1, y-1)+Noise(x+1, y-1)+Noise(x-1, y+1)+Noise(x+1, y+1) ) / 16;
    double sides = ( Noise(x-1, y) +Noise(x+1, y) +Noise(x, y-1) +Noise(x, y+1) ) / 8;
    double center = Noise(x, y) / 4;
    return corners + sides + center;
}
double ImageFogger::Cosine_Interpolate(double a,double b, double x)  // 余弦插值
{
    double ft = x * 3.1415927;
    double f = (1 - cos(ft)) * 0.5;
    return a*(1-f) + b*f;
}

double ImageFogger::InterpolatedNoise(float x,float y)   // 获取插值噪声
{
    int integer_X = int(x);
    float  fractional_X = x - integer_X;
    int integer_Y = int(y);
    float fractional_Y = y - integer_Y;
    double v1 = SmoothedNoise(integer_X, integer_Y);
    double v2 = SmoothedNoise(integer_X + 1, integer_Y);
    double v3 = SmoothedNoise(integer_X, integer_Y + 1);
    double v4 = SmoothedNoise(integer_X + 1, integer_Y + 1);
    double i1 = Cosine_Interpolate(v1, v2, fractional_X);
    double i2 = Cosine_Interpolate(v3, v4, fractional_X);
    return Cosine_Interpolate(i1, i2, fractional_Y);
}

double ImageFogger::PerlinNoise(float x,float y)    // 最终调用:根据(x,y)获得其对应的PerlinNoise值
{
    double total = 0;
    double p = m_persistence;
    int n = m_octaveNum;
    for(int i=0; i<n; i++)
    {
        double frequency = pow(2,i);
        double amplitude = pow(p,i);
        total = total + InterpolatedNoise(x * frequency, y * frequency) * amplitude;
    }

    return total;
}

// 修正像素
QColor ImageFogger::reverseRGB(int r, int g, int b)
{
    r = r<(0)?0:r;
    g = g<(0)?0:g;
    b = b<(0)?0:b;
    r = r>(255)?255:r;
    g = g>(255)?255:g;
    b = b>(255)?255:b;

    return QColor(r,g,b);
}

【原理二】 图像刻蚀效果

       这个相对来讲就不那么复杂了。 我们可以预先设定一个限制条件t,对于图像中的每个像素,如果它不满足条件t,那么我们就可以剔除该点的像素并以黑色替代。条件t可以随便设置,以实现不同的刻蚀效果,比如: 

       颜色(r,g,b)的红色分量小于50、红绿分量的壁纸(r/g)小于0.7等。

      相关实现的代码如下:

QImage ImageCarver::processImage(const QImage &img)
{
    QImage pimg = img;

    for(int i=0; i<pimg.height() ; i++)          // 遍历y
    {
        for(int k=0; k<pimg.width(); k++)        // 遍历x
        {
            QRgb pixel = pimg.pixel(k,i);

            QColor rgb(pixel);                // 获取像素的rgb值

            if(rgb.red()/(float)rgb.blue()<0.7)
                    pimg.setPixel(k,i, QColor(0,0,0) );   // 黑色
            else
                    pimg.setPixel(k,i, rgb);     // 保持颜色不变

        }
    }

    return pimg;
}

【原理三】  图像光晕效果

      图像的光晕可以通过一种与距离有关的插值实现。实现的步骤如下:

      (1)  选定一个点(cx,cy)为光晕的中心。

      (2)  遍历图像的每个像素(x,y),计算(x,y)与(cx,cy)的距离dist;

      (3)  根据dist修改该像素的颜色值,这里可以随便设置插值函数,比如这里采用平方过渡:

                   (r',g',b') = (r,g,b) + dist*dist*0.0001;   

      (4)  修正(r',g',b')的数值在0-255之间,并作为该点的新颜色数值。


      三种特效的大概原理就是这样。这个程序是我偶然间想到可以写一个工具来帮助我这种不会用PS的人处理图片,三种特效除了柏林噪声,另两种是我随便琢磨的、觉得可行的方法,没想到效果看起来还挺不错的。欢迎讨论~~