bryce1010的图像处理课程设计
一、要求
完成课程教学中的大部分图像处理功能
二、平台
- Qt
- c++
- windows或者linux下
三、思路收集
1.QPixmap类
(一)QPixmap和QImage的区别
QPixmap是专门为绘图而生,当需要绘制图片时你需要使用QPixmap。QImage则是为I/O,为图片像素访问以及修改而设计的。如果你 想访问图片的像素或是修改图片像素,则需要使用QImage,或者借助于QPainter来操作像素。另外跟QImage不同是,QPixmap跟硬件是 相关的,如X11, Mac 以及 Symbian平台上,QPixmap 是存储在服务器端,而QImage则是存储在客户端,在Windows平台上,QPixmap和QImage都是存储在客户端,并不使用任何的GDI资 源。
相信大家更关心的是谁比较快,哈哈,现在来总结一下:
在X11, Mac 以及 Symbian平台上,QImage: 因为它是存储在客户端,往QImage上绘图比较快,但显示它则比较慢。QPixmap: 因为它是存储在服务器端,往QPixmap上绘图比较慢,但显示它则比较快。但在Windows平台上则是是一样的,因为它们都存储在客户端。
Qt上图片处理使用QPixmap和QImage时最多了,不过既然谈到图片了,我们把其他几个图片处理类也说一下:
QBitmap只是一个继承于QPixmap的简单类,它可以确保图片深度为1。
QBitmap是QPixmap的子类,提供单色图像,可以用来制作游标(QCursor)或者笔刷(QBrush)。
QPicture是一个绘画设备类,它记录了并可以重演QPainter的命令。你可以使用QPainter的begin()方法,指定在 QPicture上绘图,使用end()方法结束绘图,使用QPicture的save()方法將QPainter所使用过的绘图指令存至档案。要重播绘 图指令的话,建立一個QPicture,使用load()方法载入绘图指令的档案,然后在指定的绘图裝置上绘制QPicture:
(二)QImage与QPixmap完全解析
用Qt程序在手机上显示一幅图片对编程人员来说是再基础不过的一件事情了。那么先让大家看两段代码:
//dangerous should not be used, cannot display earth.png,
//but if we change earth.png to a smaller image e.g. apple.png, apple.png can be displayed
QPixmap pixmap;
pixmap.load( “:/pics/earth.png” );
label->setPixmap( pixmap );
和
//dangerous should not be used, cannot display earth.png,
//but if we change earth.png to a smaller image e.g. apple.png, apple.png can be displayed
QPixmap pixmap;
pixmap.load( “:/pics/earth.png” );
QPainter painter(this);
painter.drawPixmap(0,0, pixmap);
大 家认为这两段代码有什么问题吗? 看起来好像没什么问题啊。是的,在Windows操作系统上是没有问题的。问题是我们做的是Qt for Symbian! 手机上的资源本来就是比较紧缺的,所以我们使用的时候就需要更加注意。 Qt 为我们提供了四个处理图像的类:QImage,QPixmap,QBitmap 和QPicture。其中前两个是最常使用的。
本文就通过一个例子,一步一步为大家讲解QImage与QPixmap的使用奥秘,在此过程中为大家揭示以上代码存在的缺陷。
QPixmap依赖于硬件
首先需要知道的是QPixmap的具体实现是依赖于系统的。在Symbian系统上QPixmap是被存放在Server端的。
目前的Qt会把QPixmap都存储在graphics memory中,这明显是依赖硬件的。因此我们对QPixmap的使用需要格外注意。这也正是以上两段代码存在问题的根源。
那么Qt为什么要这么做呢?很简单,设计之初QPixmap就是用来加速显示的,例如我们在paint的时候用QPixmap就会比用其他类的效果好许多。
现在回到我们最初的问题,以上代码到底有什么问题呢?我们可以先用本文提供的例子程序做个试验。当使用上述代码显示较小图片的时候(比如例子程序中的background.png 和apple.png)是没有问题的,图片都能在手机上正确显示。
但是当我们把图片换成一副较大图片287KB,1058 x 1058的“earth.png”的时候就出现问题了,图片无法显示,程序的界面是一片空白。
据测算,“earth.png”被完全解码后存储在graphics memory中会占用大约4.3MB的空间。如果此时还有其他加载的窗口和QPixmap,很可能就没有空间了。
使用QImage加载后转换成QPixmap 显示
那么安全和正确的方法应该是什么呢?答案是我们需要用QImage做一下预处理:
//correct and recommended way
QImage image;
image.load( “:/pics/earth.png” );
QPainter painter(this);
QPixmap pixmapToShow = QPixmap::fromImage( image.scaled(size(), Qt::KeepAspectRatio) );
painter.drawPixmap(0,0, pixmapToShow);
和QPixmap 不同,QImage是独立于硬件的,它可以同时被另一个线程访问。QImage是存储在客户端的,对QImage的使用是非常方便和安全的。 又由于 QImage 也是一种QPaintDevice,因此我们可以在另一个线程中对其进行绘制,而不需要在GUI 线程中处理,使用这一方式可以很大幅度提高UI响应速度。 因此当图片较大时,我们可以先通过QImage将图片加载进来,然后把图片缩放成需要的尺寸,最后转换成QPixmap 进行显示。 下图是显示效果(图片是按照earth.png的原始尺寸比例缩放后显示的):
其中需要注意的是Qt::KeepAspectRatio的使用,默认参数是Qt::IgnoreAspectRatio,如果我们在程序中这么写:
QPixmap pixmapToShow = QPixmap::fromImage( image.scaled(size(), Qt::IgnoreAspectRatio) );
效果就是下面这个样子,earth.png被拉伸以充满整个屏幕:
直接使用QImage 显示
我们也可以直接使用QImage做显示,而不转换成QPixmap ,这要根据我们应用的具体需求来决定,如果需要的话我们可以这么写:
//correct, some times may be needed
QImage image;
image.load( “:/pics/earth.png” );
QPainter painter(this);
painter.drawImage(0,0, image);
下面是显示效果(当然我们也可以对其进行缩放之后再显示) 从图片可以看出来它是按照原始尺寸显示earth.png的:
2.Qt中的状态栏statusBar()
状态栏位于主窗口的最下方,提供一个显示工具提示等信息的地方。一般地,当窗口不是最大化的时候,状态栏的右下角会有一个可以调节大小的控制点;当窗口最大化的时候,这个控制点会自动消失。Qt提供了一个QStatusBar类来实现状态栏。
Qt具有一个相当成熟的GUI框架的实现——这一点感觉比Swing要强一些——Qt似乎对GUI的开发做了很多设计,比如QMainWindow类里面就有一个statusBar()函数,用于实现状态栏的调用。类似menuBar()函数,如果不存在状态栏,该函数会自动创建一个,如果已经创建则会返回这个状态栏的指针。如果你要替换掉已经存在的状态栏,需要使用QMainWindow的setStatusBar()函数。
在Qt里面,状态栏显示的信息有三种类型:临时信息、一般信息和永久信息。其中,临时信息指临时显示的信息,比如QAction的提示等,也可以设置自己的临时信息,比如程序启动之后显示Ready,一段时间后自动消失——这个功能可以使用QStatusBar的showMessage()函数来实现;一般信息可以用来显示页码之类的;永久信息是不会消失的信息,比如可以在状态栏提示用户Caps Lock键被按下之类。
QStatusBar继承自QWidget,因此它可以添加其他的QWidget。下面我们在QStatusBar上添加一个QLabel。
首先在class的声明中添加一个私有的QLabel属性:
private:
QAction *openAction;
QLabel *msgLabel;
然后在其构造函数中添加:
msgLabel = new QLabel;
msgLabel->setMinimumSize(msgLabel->sizeHint());
msgLabel->setAlignment(Qt::AlignHCenter);
statusBar()->addWidget(msgLabel);
这里,第一行创建一个QLabel的对象,然后设置最小大小为其本身的建议大小——注意,这样设置之后,这个最小大小可能是变化的——最后设置显示规则是水平居中(HCenter)。最后一行使用statusBar()函数将这个label添加到状态栏。编译运行,将鼠标移动到工具条或者菜单的QAction上,状态栏就会有相应的提示:
看起来是不是很方便?只是,我们很快发现一个问题:当没有任何提示时,状态栏会有一个短短的竖线:
这是什么呢?其实,这是QLabel的边框。当没有内容显示时,QLabel只显示出自己的一个边框。但是,很多情况下我们并不希望有这条竖线,于是,我们对statusBar()进行如下设置:
statusBar()->setStyleSheet(QString("QStatusBar::item{border: 0px}"));
四、代码实现
1.读入图片
void MainWindow::on_actionOpen_triggered()
{
// Automatically detects the current user's home directory
// And then wait the user to select one image
QString imagePath = QFileDialog::getOpenFileName(this, tr("Open image"), getUserPath() + "/Pictures",
tr("All Files (*);;"
"All Images (*.bpm *.gif *.jpg *.jpeg *.png *.ppm *.xbm *.xpm);;"
"Image BPM (*.bpm);;"
"Image GIF (*.gif);;"
"Image JPG (*.jpg);;"
"Image JPEG (*.jpeg);;"
"Image PNG (*.png);;"
"Image PPM (*.ppm);;"
"Image XBM (*.xbm);;"
"Image XPM (*.xpm);;"));
if (!imagePath.isEmpty())
{
QFile file(imagePath);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::critical(this, tr(WINDOW_CRITICAL),
tr("Unable to open image."));
return;
}
// delete previous image
cleanImage();
// upload image
info = new QFileInfo(imagePath);
QPixmap leftPixmap(imagePath);
leftPixmapItem = leftScene->addPixmap(leftPixmap);
leftScene->setSceneRect(QRectF(leftPixmap.rect()));
QPixmap rightPixmap(imagePath);
rightPixmapItem = rightScene->addPixmap(rightPixmap);
rightScene->setSceneRect(QRectF(rightPixmap.rect()));
qDebug()<<"depth:"<<rightPixmap.depth();
qDebug()<<"hasAlpha:"<<rightPixmap.hasAlpha();
// settings
this->setWindowTitle(info->fileName() + " - ImageQt");
setActionStatus(true);
size->setText(QString::number(leftPixmapItem->pixmap().width())
+ " x " + QString::number(leftPixmapItem->pixmap().height()));
}
}
2.关闭图片
void MainWindow::on_actionClose_triggered()
{
cleanImage();
}
3.图片另存为
void MainWindow::on_actionSave_As_triggered()
{
QString newPath = QFileDialog::getSaveFileName(this, tr("Save image"), QString(),
tr("All files (*);;"
"Image BPM (*.bpm);;"
"Image GIF (*.gif);;"
"Image JPG (*.jpg);;"
"Image JPEG (*.jpeg);;"
"Image PNG (*.png);;"
"Image PPM (*.ppm);;"
"Image XBM (*.xbm);;"
"Image XPM (*.xpm);;"));
if (!newPath.isEmpty()) {
QFile file(newPath);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::critical(this, tr(WINDOW_CRITICAL), tr("Unable to save image."));
return;
}
//Save image to new path
rightPixmapItem->pixmap().save(newPath);
// rightImage->save(newPath);
}
}
4.退出程序
void MainWindow::on_actionExit_triggered()
{
qApp->quit();
}
5.灰度处理
我们要学习的第一个技术就是将彩色图转换成灰度图,我们首先要明白的一点就是,其实标准的灰度图就是每个像素点的三个通道的值一样或者近似,我们的策略就是将每个像素的每个通道的值都调成一样,取R,G,B值为三者的算数平均数就可以了,比如原色是RGB(169,204,69), 那么最终的RGB就是(169+204+69)/3 = 147.
/*****************************************************************************
* Greyscale
* **************************************************************************/
QImage Tools::GreyScale(QImage origin)
{
QImage *newImage = new QImage(origin.width(), origin.height(),
QImage::Format_ARGB32);
QColor oldColor;
for (int x=0; x<newImage->width(); x++) {
for (int y=0; y<newImage->height(); y++) {
oldColor = QColor(origin.pixel(x,y));
int average = (oldColor.red()*299+oldColor.green()*587+oldColor.blue()*114+500)/1000;
newImage->setPixel(x,y,qRgb(average,average,average));
}
}
return *newImage;
}
6.色调调节
6.1 暖色调处理
当我们说一一幅暖色调的图片的时候通常是因为这张图色调偏黄。我们没有黄色的通道,但是红色和绿色混合起来就是黄色,所以我们增加这两个通道值,然后蓝色通道值不变就好了。
我们使用一个delta参数来决定增加红色和绿色通道的值。一张暖色的图片能够给人一种复古效果,如果是有沙子的图片,图片将会更加生动。
QImage Tools::Warm(int delta, QImage origin)
{
QImage *newImage = new QImage(origin.width(), origin.height(),
QImage::Format_ARGB32);
QColor oldColor;
int r, g, b;
for (int x=0; x<newImage->width(); x++)
{
for (int y=0; y<newImage->height(); y++)
{
oldColor = QColor(origin.pixel(x,y));
r = oldColor.red() + delta;
g = oldColor.green() + delta;
b = oldColor.blue();
// qDebug()<<r<<" "<<g<<""<<b;
// Check if the new values are between 0 and 255
r = qBound(0, r, 255);
g = qBound(0, g, 255);
newImage->setPixel(x,y, qRgb(r,g,b));
}
}
return *newImage;
}
6.2 冷色调处理
如果说暖色调的图片偏黄色,那么冷色调的图片应该就是偏蓝色了。在这个方法里面我们只增加蓝色通道的值,红色和绿色的值不变。
冷色调的图片可以联想到未来,死亡或者,冷。
QImage Tools::Cool(int delta, QImage origin)
{
QImage *newImage = new QImage(origin.width(), origin.height(),
QImage::Format_ARGB32);
QColor oldColor;
int r, g, b;
for (int x=0; x<newImage->width(); x++)
{
for (int y=0; y<newImage->height(); y++)
{
oldColor = QColor(origin.pixel(x,y));
r = oldColor.red();
g = oldColor.green();
b = oldColor.blue() + delta;
// Check if the new values are between 0 and 255
r = qBound(0, r, 255);
g = qBound(0, g, 255);
newImage->setPixel(x,y, qRgb(r,g,b));
}
}
return *newImage;
}
7.亮度调节
就如之前我们提到的,白色用RGB(255,255,255)表示,黑色用RGB(0,0,0)表示,所以如果我们需要提高图片的亮度(颜色接近白色),我们需要同时增加三个通道的数值,反之就是变暗。
在这里我们添加了一个函数参数来决定要提高多少亮度,如果参数是负数的话就是减少亮度了。在每个通道都加上delta值之后,需要做的就是让它不要低于0且不要高于255.
QImage Tools::Brightness(int delta, QImage origin)
{
QImage *newImage = new QImage(origin.width(), origin.height(),
QImage::Format_ARGB32);
QColor oldColor;
int r, g, b;
for (int x=0; x<newImage->width(); x++)
{
for (int y=0; y<newImage->height(); y++)
{
oldColor = QColor(origin.pixel(x,y));
r = oldColor.red() + delta;
g = oldColor.green() + delta;
b = oldColor.blue() + delta;
// Check if the new values are between 0 and 255
r = qBound(0, r, 255);
g = qBound(0, g, 255);
newImage->setPixel(x,y, qRgb(r,g,b));
}
}
return *newImage;
}
8. 镜像
8.1 水平镜像
主要思想是将图像的横坐标全部倒置一遍:
QImage Tools::Horizontal(const QImage &origin)
{
QImage *newImage = new QImage(QSize(origin.width(), origin.height()),
QImage::Format_ARGB32);
QColor tmpColor;
int r, g, b;
for (int x=0; x<newImage->width(); x++)
{
for (int y=0; y<newImage->height(); y++)
{
tmpColor = QColor(origin.pixel(x, y));
r = tmpColor.red();
g = tmpColor.green();
b = tmpColor.blue();
newImage->setPixel(newImage->width()-x-1,y, qRgb(r,g,b));
}
}
return *newImage;
}
8.2垂直镜像
主要思想是:
图像的纵坐标一次反转倒置一遍:
QImage Tools::Vertical(const QImage &origin)
{
QImage *newImage = new QImage(QSize(origin.width(), origin.height()),
QImage::Format_ARGB32);
QColor tmpColor;
int r, g, b;
for (int x=0; x<newImage->width(); x++)
{
for (int y=0; y<newImage->height(); y++)
{
tmpColor = QColor(origin.pixel(x, y));
r = tmpColor.red();
g = tmpColor.green();
b = tmpColor.blue();
newImage->setPixel(x, newImage->height()-y-1, qRgb(r,g,b));
}
}
return *newImage;
}
9.金属纹理
这个例子中我们会结合几种技术来获得一种效果。下面是处理的步骤:
1.调整图像的亮度,获得一个较暗的图片。
2.将图像转成灰度。
3.将灰度图绘制在金属的纹理上,透明度50%。
QImage Tools::Metal(QImage origin)
{
QImage *baseImage = new QImage(":/img/src/metal.png");
QImage darkImage = Tools::Brightness(-100, origin);
QImage greyImage = Tools::GreyScale(darkImage);
QPainter painter;
QImage newImage = baseImage->scaled(QSize(origin.width(),origin.height()));
painter.begin(&newImage);
painter.setOpacity(0.5);
painter.drawImage(0, 0, greyImage);
painter.end();
return newImage;
}
10.语言帮助
因为在linux系统中Qt没有中文输入法,所以一开始设计的时候采用的时英文输入,如果需要的话,可以点击”帮助”切换到中文。
void MainWindow::on_actionChinese_triggered()
{
QTranslator translator;
translator.load(":/language/cn.qm");
qApp->installTranslator(&translator);
ui->retranslateUi(this);
ui->actionChinese->setEnabled(true);
}
11.图像直方图绘制
基本思想:
由于图像像素数值范围在0-255,那么开一个大小是260的数组bwHstgrm[259],用bwHstgrm[256]记录总数值,bwHstgrm[257]记录暗分量的总数值,用bwHstgrm[258]记录亮部分的总数值。
再另开三个数组int redHstgrm[258];int greenHstgrm[258];int blueHstgrm[258];分别记录红色、绿色、蓝色三原色的0-255的像素值。
void MainWindow::on_actionHistogram_triggered()
{
QDialog * hstgrmDialog = new QDialog(this);
QScrollArea * scrollArea = new QScrollArea(hstgrmDialog);
Histogram * hstgrm = new Histogram(scrollArea);
hstgrm->computeHstgrm(rightPixmapItem->pixmap().toImage());
if (hstgrm == NULL)
return;
scrollArea->setWidget(hstgrm);
QHBoxLayout * layout = new QHBoxLayout;
layout->addWidget(scrollArea);
hstgrmDialog->setLayout(layout);
hstgrm->resize(800, 780);
hstgrmDialog->setFixedWidth(820);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->adjustSize();
hstgrmDialog->setWindowTitle("Histogram - ImageQt");
hstgrmDialog->show();
}
12.线性灰度变换
主要思想:
线性灰度变换的思想和普通灰度变换相类似,不过线性灰度变换是在普通灰度变换,即对各像素求平均值后,再把沿y方向的像素值按给出的线性关系增加相应的函数值。
QImage Tools::LinearLevelTransformation(const QImage &origin, double _a, double _b)
{
QImage *newImage = new QImage(origin.width(), origin.height(),
QImage::Format_ARGB32);
QColor oldColor;
int grayLevel = 0;
for (int x=0; x<newImage->width(); x++) {
for (int y=0; y<newImage->height(); y++) {
oldColor = QColor(origin.pixel(x,y));
grayLevel = (oldColor.red()*299+oldColor.green()*587+oldColor.blue()*114+500)/1000;
int _y = _a*grayLevel + _b;
// Make sure that the new values are between 0 and 255
_y = qBound(0, _y, 255);
newImage->setPixel(x,y,qRgb(_y,_y,_y));
}
}
// qDebug()<<"a:"<<_a<<"\tb:"<<_b;
return *newImage;
}
13.指数灰度变换
主要思想:
指数灰度变换中的函数绘制图像与线性变换中共用一个函数,有着比较良好的代码重用性,指数灰度变化的过程主要是在普通灰度变换上沿y轴的像素值以指定的指数函数的趋势增加,达到指数灰度变换的效果。
QImage Tools::ExpTransform(const QImage &origin, double b, double c, double a)
{
QImage *newImage = new QImage(origin.width(), origin.height(),
QImage::Format_ARGB32);
QColor oldColor;
int _x = 0;
for (int x=0; x<newImage->width(); x++) {
for (int y=0; y<newImage->height(); y++) {
oldColor = QColor(origin.pixel(x,y));
_x = (oldColor.red()*299+oldColor.green()*587+oldColor.blue()*114+500)/1000;
int _y =qPow(b, c*(_x-a));
// Make sure that the new values are between 0 and 255
_y = qBound(0, _y, 255);
newImage->setPixel(x,y,qRgb(_y,_y,_y));
}
}
return *newImage;
}
14.幂次灰度变换
_y=c*_x^r+b
主要思想:在普通灰度变换的基础上,沿y轴方向的像素值以指定的幂次函数的趋势增加,最终达到幂次灰度变换的效果。
QImage Tools::PowerGreyLevelTransformation(const QImage &origin, double c, double r, double b)
{
QImage *newImage = new QImage(origin.width(), origin.height(),
QImage::Format_ARGB32);
QColor oldColor;
int _x = 0;
for (int x=0; x<newImage->width(); x++) {
for (int y=0; y<newImage->height(); y++) {
oldColor = QColor(origin.pixel(x,y));
_x = (oldColor.red()*299+oldColor.green()*587+oldColor.blue()*114+500)/1000;
int _y =c*qPow(_x, r)+b;
// Make sure that the new values are between 0 and 255
_y = qBound(0, _y, 255);
newImage->setPixel(x,y,qRgb(_y,_y,_y));
}
}
return *newImage;
}
15. 对数灰度变换
主要思想:
对数灰度变换是在普通灰度变化的基础上,使沿y轴方向的像素值以指定的对数函数的趋势增加,最终实现对数灰度变换的效果。
QImage Tools::LogGreyLevelTransformation(const QImage &origin, double a, double b)
{
QImage *newImage = new QImage(origin.width(), origin.height(),
QImage::Format_ARGB32);
QColor oldColor;
int grayLevel = 0;
for (int x=0; x<newImage->width(); x++) {
for (int y=0; y<newImage->height(); y++) {
oldColor = QColor(origin.pixel(x,y));
grayLevel = (oldColor.red()*299+oldColor.green()*587+oldColor.blue()*114+500)/1000;
int _y = qLn(b+grayLevel)/qLn(a);
// Make sure that the new values are between 0 and 255
_y = qBound(0, _y, 255);
newImage->setPixel(x,y,qRgb(_y,_y,_y));
}
}
return *newImage;
}
16.双阈值灰度变换
首先设定两个阈值t1和t2,设置两个选项option1 和 option2 :
在option1中的像素变化范围是0-255-0,在此条件下,坐标x
QImage Tools::TwoThreshold(const QImage &origin, double t1, double t2, int option)
{
QImage *newImage = new QImage(origin.width(), origin.height(),
QImage::Format_ARGB32);
QColor oldColor;
int _x = 0;
int _y = 0;
if (option == 0)
{
for (int x=0; x<newImage->width(); x++) {
for (int y=0; y<newImage->height(); y++) {
oldColor = QColor(origin.pixel(x,y));
_x = (oldColor.red()*299+oldColor.green()*587+oldColor.blue()*114+500)/1000;
if (_x < t1 || _x > t2)
_y = 0;
else
_y = 255;
// Make sure that the new values are between 0 and 255
_y = qBound(0, _y, 255);
newImage->setPixel(x,y,qRgb(_y,_y,_y));
}
}
}
else
{
for (int x=0; x<newImage->width(); x++) {
for (int y=0; y<newImage->height(); y++) {
oldColor = QColor(origin.pixel(x,y));
_x = (oldColor.red()*299+oldColor.green()*587+oldColor.blue()*114+500)/1000;
if (_x>=t1 && _x<=t2)
_y = 0;
else
_y = 255;
// Make sure that the new values are between 0 and 255
_y = qBound(0, _y, 255);
newImage->setPixel(x,y,qRgb(_y,_y,_y));
}
}
}
return *newImage;
}
17.拉伸灰度变换
拉伸灰度变换的主要思想是使用一个分段函数,给出三个k值,每一段分别给一个k的函数值,在给定的三个函数范围内分别应用不同的函数进程灰度变换。
QImage Tools::StretchTransform(const QImage &origin,
int x1, int x2,
double k1, double k2, double k3,
double b2, double b3)
{
QImage *newImage = new QImage(origin.width(), origin.height(),
QImage::Format_ARGB32);
QColor oldColor;
int _x = 0;
int _y = 0;
for (int x=0; x<newImage->width(); x++) {
for (int y=0; y<newImage->height(); y++) {
oldColor = QColor(origin.pixel(x,y));
_x = (oldColor.red()*299+oldColor.green()*587+oldColor.blue()*114+500)/1000;
if ( _x<x1)
_y = k1*_x;
else if (_x < x2)
_y = k2*_x + b2;
else
_y = k3*_x + b3;
// Make sure that the new values are between 0 and 255
_y = qBound(0, _y, 255);
newImage->setPixel(x,y,qRgb(_y,_y,_y));
}
}
return *newImage;
}
18.简单平滑处理
(卷积滤波器)
这个效果相对于之前的有一点点复杂。我们会用到一个卷积滤波器,根据当前像素的颜色和相邻像素的颜色来获得一个新的颜色。同时还有一个kernel的矩阵来决定计算中相邻像素的影响程度。
原像素会在矩阵的中心,因此我们会使用基数行的行和列。我们不会修改边缘的像素点,因为那些点没有我们需要的相邻像素点,虽然我们也可以只使用有效的像素点。
举了例子,让我们来看看如何计算像素的RGB值。下面的三个举证代表着当前像素和邻接像素的RGB值,最中间的是当前像素。
R = 20 102 99
150 200 77
170 210 105
G = 22 33 40
17 21 33
8 15 24
B = 88 70 55
90 72 59
85 69 50
Kenel = 0 2 0
2 5 2
0 2 0
使用滤波器进行计算:
r = ( (102*2) + (150*2) + (200*5) + (77*2) + (210*2) ) / (2+2+5+2+2) = 159
g = ( (33*2) + ( 17*2) + (21*5) + (33*2) + (15*2) ) / (2+2+5+2+2) = 23
b = ( (70*2) + (90*2) + (72*5) + (59*2) + (69*2) ) / (2+2+5+2+2) = 72
由原始的RGB(200, 21, 72)得到了RGB(159, 23, 72). 发现最大的变化是红色的通道,因为红色通道的值差距最大。
在修改肖像照片的时候通常会使用到模糊的技术,它能后掩盖住皮肤的瑕疵。
QImage Tools::SimpleSmooth(const QImage &origin)
{
QImage *newImage = new QImage(origin);
int kernel[5][5] = {
{0,0,1,0,0},
{0,1,3,1,0},
{1,3,7,3,1},
{0,1,3,1,0},
{0,0,1,0,0}
};
int kernelSize = 5;
int sumKernel=27;
int r,g,b;
QColor color;
for(int x=kernelSize/2; x<newImage->width()-kernelSize/2; x++)
{
for (int y=kernelSize/2; y<newImage->height()-kernelSize/2; y++)
{
r = g = b = 0;
for (int i=-kernelSize/2; i<=kernelSize/2; i++)
{
for (int j=-kernelSize/2; j<=kernelSize/2; j++)
{
color = QColor(origin.pixel(x+i,y+j));
r += color.red()*kernel[kernelSize/2+i][kernelSize/2+j];
g += color.green()*kernel[kernelSize/2+i][kernelSize/2+j];
b += color.blue()*kernel[kernelSize/2+i][kernelSize/2+j];
}
}
r = qBound(0, r/sumKernel, 255);
g = qBound(0, g/sumKernel, 255);
b = qBound(0, b/sumKernel, 255);
newImage->setPixel(x,y,qRgb(r,g,b));
}
}
return *newImage;
}
19.高斯平滑处理
最原始的实现方法是,使用高斯函数生成高斯模板,然后使用该模板对图像做卷积(convolution)。该方法的算法复杂度是O(M*N*r^2),也就是,对每个像素而言,复杂度是O(r^2),与滤波器大小成quadratic关系。如果增大滤波器size,算法会明显减慢。
QImage Tools::GaussianSmoothing(const QImage &origin, int radius, double sigma)
{
GaussianBlur *blur = new GaussianBlur(radius, sigma);
QImage newImage = blur->BlurImage(origin);
return newImage;
}
20.中值滤波处理
中值滤波是一种非线性滤波方式,它也依靠模板来实现。与线性滤波不同,中值滤波可以完全消除孤立的脉冲而不对通过的理想边缘产生任何影响。
QImage Tools::MeidaFilter(const QImage &origin, int filterRadius)
{
int imageHeight = origin.height();
int imageWidth = origin.width();
MedianFilter medianFilter;
int* resImageBits = new int[imageHeight * imageWidth];
medianFilter.applyMedianFilter((int*)origin.bits(), resImageBits, imageHeight, imageWidth, filterRadius);
QImage destImage((uchar*)resImageBits, imageWidth, imageHeight, origin.format());
// QPixmap pixRes;
// pixRes.convertFromImage(destImage);
return destImage;
}
21. Laplace锐化
锐化能够处理模糊的照片,能够提升细节。
拉普拉斯锐化图像是根据图像某个像素的周围像素到此像素的突变程度有关,也就是说它的依据是图像像素的变化程度。我们知道,一个函数的一阶微分描述了函数图像是朝哪里变化的,即增长或者降低;而二阶微分描述的则是图像变化的速度,急剧增长下降还是平缓的增长下降。那么据此我们可以猜测出依据二阶微分能够找到图像的色素的过渡程度,例如白色到黑色的过渡就是比较急剧的。
四领域模板:
QImage Tools::LaplaceSharpen(const QImage &origin)
{
int width = origin.width();
int height = origin.height();
QImage newImage = QImage(width, height,QImage::Format_RGB888);
int window[3][3] = {0,-1,0,-1,4,-1,0,-1,0};
for (int x=1; x<width; x++)
{
for(int y=1; y<height; y++)
{
int sumR = 0;
int sumG = 0;
int sumB = 0;
//对每一个像素使用模板
for(int m=x-1; m<= x+1; m++)
for(int n=y-1; n<=y+1; n++)
{
if(m>=0 && m<width && n<height)
{
sumR += QColor(origin.pixel(m,n)).red()*window[n-y+1][m-x+1];
sumG += QColor(origin.pixel(m,n)).green()*window[n-y+1][m-x+1];
sumB += QColor(origin.pixel(m,n)).blue()*window[n-y+1][m-x+1];
}
}
int old_r = QColor(origin.pixel(x,y)).red();
sumR += old_r;
sumR = qBound(0, sumR, 255);
int old_g = QColor(origin.pixel(x,y)).green();
sumG += old_g;
sumG = qBound(0, sumG, 255);
int old_b = QColor(origin.pixel(x,y)).blue();
sumB += old_b;
sumB = qBound(0, sumB, 255);
newImage.setPixel(x,y, qRgb(sumR, sumG, sumB));
}
}
return newImage;
}
22.Sobel边缘检测
索贝尔算子(Sobel operator)主要用作边缘检测,在技术上,它是一离散性差分算子,用来运算图像亮度函数的灰度之近似值。在图像的任何一点使用此算子,将会产生对应的灰度矢量或是其法矢量
Sobel卷积因子为:
该算子包含两组3x3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如果以A代表原始图像,Gx及Gy分别代表经横向及纵向边缘检测的图像灰度值,其公式如下:
Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。
“`
QImage Tools::SobelEdge(const QImage &origin)
{
double *Gx = new double[9];
double *Gy = new double[9];
/* Sobel */
Gx[0] = 1.0; Gx[1] = 0.0; Gx[2] = -1.0;
Gx[3] = 2.0; Gx[4] = 0.0; Gx[5] = -2.0;
Gx[6] = 1.0; Gx[7] = 0.0; Gx[8] = -1.0;
Gy[0] = -1.0; Gy[1] = -2.0; Gy[2] = - 1.0;
Gy[3] = 0.0; Gy[4] = 0.0; Gy[5] = 0.0;
Gy[6] = 1.0; Gy[7] = 2.0; Gy[8] = 1.0;
QRgb pixel;
QImage grayImage = GreyScale(origin);
int height = grayImage.height();
int width = grayImage.width();
QImage newImage = QImage(width, height,QImage::Format_RGB888);
float sobel_norm[width*height];
float max = 0.0;
QColor my_color;
for (int x=0; x<width; x++)
{
for( int y=0; y<height; y++)
{
double value_gx = 0.0;
double value_gy = 0.0;
for (int k=0; k<3;k++)
{
for(int p=0; p<3; p++)
{
pixel=grayImage.pixel(x+1+1-k,y+1+1-p);
value_gx += Gx[p*3+k] * qRed(pixel);
value_gy += Gy[p*3+k] * qRed(pixel);
}
// sobel_norm[x+y*width] = sqrt(value_gx*value_gx + value_gy*value_gy)/1.0;
sobel_norm[x+y*width] = abs(value_gx) + abs(value_gy);
max=sobel_norm[x+y*width]>max ? sobel_norm[x+y*width]:max;
}
}
}
for(int i=0;i<width;i++){
for(int j=0;j<height;j++){
my_color.setHsv( 0 ,0, 255-int(255.0*sobel_norm[i + j * width]/max));
newImage.setPixel(i,j,my_color.rgb());
}
}
return newImage;
}
“`
bryce1010的图像处理课程设计的更多相关文章
- Bryce1010的操作系统课程设计
https://download.csdn.net/download/fire_to_cheat_/10221003 上面是课程设计的代码,下载需要一些积分. 1.作业调度 2.磁盘调度 常见的磁盘调 ...
- Bryce1010的linux课程设计
1.设计目的 2.软件环境 3.要求 4.需求分析 5.总体设计 6.详细设计 7.调试与测试 8.总结 思路整理: 1.如果要开始编译着手的准备 SQLite数据库的安装 gtk+的安装 (.... ...
- [课程设计]任务进度条&开发日志目录
任务进度条&开发日志目录 周期 时间 任务 Sprint One 11.14 ● Scrum团队分工及明确任务1.0 Sprint One 11.15 ● Scr ...
- 课程设计 --- 黑白棋中的 AI
原文链接:https://www.dreamwings.cn/reversi/3013.html 到了考试周了佯,可是偏偏这个时候迎来了很多很多的课程设计,幸好教授把C语言的课程设计提前发出了,不然都 ...
- Java课程设计——扫雷(winmine)
因为是我的课程设计,要是有冲突就不好了,转载注明出处!!! 程序很简单,毕竟我是搞acm的,我就只介绍一下闪光点. 中心空白搜索的时候,我用的DFS: 有一点是要注意的,就是JFrame不支持重画,还 ...
- [课程设计]Scrum 1.6 多鱼点餐系统开发进度
[课程设计]Scrum 1.6 多鱼点餐系统开发进度(点餐页面按钮添加&修复) 1.团队名称:重案组 2.团队目标:长期经营,积累客户充分准备,伺机而行 3.团队口号:矢志不渝,追求完美 4. ...
- [课程设计]Scrum 1.7 多鱼点餐系统开发进度
[课程设计]Scrum 1.7 多鱼点餐系统开发进度(点餐菜式内容添加及美化) 1.团队名称:重案组 2.团队目标:长期经营,积累客户充分准备,伺机而行 3.团队口号:矢志不渝,追求完美 4.团队选题 ...
- [课程设计]Scrum 1.3 多鱼点餐系统开发进度
[课程设计]Scrum 1.3 多鱼点餐系统开发进度 Scrum 1.3 多鱼点餐系统开发进度 1.团队名称:重案组 2.团队目标:长期经营,积累客户充分准备,伺机而行 3.团队口号:矢志不渝,追 ...
- [课程设计]Scrum团队分工及明确任务1.0 ----多鱼点餐
[课程设计]Scrum团队分工及明确任务1.0 ----多鱼点餐 一.开发背景 多鱼点餐系统是一套比较系统化的针对餐厅点餐服务的产品,从顾客进入餐厅点餐到用餐结束再到最后的结账买单,需要全面的.高效的 ...
随机推荐
- 搭建网络svn实战
工作中的问题(7) 转自:http://blog.csdn.net/xiaoting451292510/article/details/8562570 经常性我们和朋友写一些程序,大家在不同的城市确有 ...
- 【APUE】进程间通信之管道
管道是UNIX系统IPC最古老形式,并且所有UNIX系统都提供此种通信机制.管道由下面两种局限性: 1)历史上,它们是半双工的(即数据只能在一个方向上流动) 2)它们只能在具有公共祖先的进程之间使用. ...
- 转: java DES的算法片码
转自: https://www.zhihu.com/question/36767829 作者:郭无心链接:https://www.zhihu.com/question/36767829/answer/ ...
- 【安卓笔记】抽屉式布局----DrawerLayout
效果例如以下: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2hkamo=/font/5a6L5L2T/fontsize/400/fill/I0JBQk ...
- HDU 5301 Buildings(2015多校第二场)
Buildings Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others) Tota ...
- payload和formData有什么不同?
最近做项目的时候,在通过post请求向服务端发送数据的时候,请求失败了.错误信息如下: 返回的400(bad request)错误,说明客户端这边发送的请求是有问题的. 和通过jquery中的ajax ...
- 网络基础笔记——OSI七层模型
OSI七层模型 由于整个网络连接的过程相当复杂,包含硬件.软件数据封包与应用程序的互相链接等等.假设想要写一支将联网所有功能都串连在一块的程序.那么当某个小环节出现故障时,整仅仅程序都须要改写.所以我 ...
- 在 Ubuntu 开启 GO 程序编译之旅
本文将使用 putty 连接到一台阿里云 Ubuntu 16.04 服务器,在其上安装 go 语言的编译环境,旨在呈现从安装到"你好,世界!"涉及的方方面面,希望完成这个过程无须觅 ...
- vsCode 常用快捷键(mac 版)
光标多行显示: commond+Alt+topArrow/downArrow 查找:commond+F 查找并按顺序切换下一个:commond+G 跳转到某一行: ctrl+G 输入行号跳转 跳转到某 ...
- web 开发之js---ajax 中的两种返回状态 xmlhttp.status和 xmlhttp.readyState
(1)xmlhttp.status xmlHttp.status的值(HTTP状态表)0**:未被始化 1**:请求收到,继续处理 2**:操作成功收到,分析.接受 3**:完成此请求必须进一步处理 ...