🌌1. 初识模式识别
图形学技术是一门涉及计算机图形和图像处理的学科,其目标是通过算法和数学模型来创建、处理和呈现图形和图像。这项技术的应用范围非常广泛,涵盖了许多领域,包括计算机游戏、虚拟现实、计算机辅助设计(CAD)、医学图像处理、动画制作等。
以下是图形学技术的一些关键方面:
- 图形生成和渲染: 图形学技术用于生成和呈现视觉图像。这包括三维图形的创建、光照、阴影、颜色和纹理等方面的处理,以产生逼真的图形。
- 计算机辅助设计(CAD): 在工程学和设计领域,图形学技术被广泛用于创建和编辑数字化的设计图纸,促进设计过程的可视化和交互。
- 计算机游戏和虚拟现实: 图形学技术是游戏开发和虚拟现实领域的核心。它用于创建游戏中的角色、场景、特效以及虚拟现实环境,提供沉浸式的视觉体验。
- 医学图像处理: 在医学领域,图形学技术被用于处理和呈现医学图像,如CT扫描、MRI等,以协助医生进行诊断和手术规划。
- 动画制作: 图形学技术是制作动画的关键。通过在计算机上生成图形帧并进行渲染,动画制作得以实现。
- 图像处理: 图形学技术也包括对静态图像的处理,如图像编辑、滤镜应用、图像合成等。
在图形学技术的发展中,硬件加速、实时渲染、虚拟现实和增强现实等方面的创新不断推动着图形学的前沿。这门技术为数字世界的可视化和交互提供了强大的工具和方法。
🌌2. 开发环境的使用及基本图形生成
🌍2.1 开发环境及实现
- 语言: C++
- 平台: Microsoft Visual Studio 2022
🌍2.2 实验目的
- 熟悉开发环境。
- 生成基本图形如直线和二次曲线。
- 掌握计算机生成直线以及修改直线属性的方法。
🌍2.3 实验要求
- 熟悉 Microsoft Visual Studio 2022 开发环境。
- 使用 DDA 算法和 Bresenham 算法分别生成直线和圆。
🌍2.4 实验原理
🌕2.4.1 DDA算法画直线
DDA是数字微分分析式(Digital Differential Analyzer)的缩写。已知直线两端点(x1,y1)、(x2,y2),则斜率m为:
m = (y2-y1)/(x2-x1)= Dx/Dy;
直线中的每一点坐标都可以由前一点坐标变化一个增量(Dx, Dy)而得到,垠)育v1["vI~5,L,`?!_[d即表示为递归式:
xi+1=xi+Dx yi+1=yi+Dy
递归式的初值为直线的起点(x1, y1),这样,就可以用加法来生成一条直线。
🌕2.4.2 Bresenham算法画直线
本算法由Bresenham在1965年提出。设直线从起点(x1, y1)到终点(x2, y2)。直线可表示为方程y=mx+b。其中
b = y1 - m * x1,
m = (y2-y1)/(x2-x1)=dy/dx
我们的讨论先将直线方向限于1a象限在这种情况下,当直线光栅化时,x每次都增加1个单元,即
xi+1=xi+1。而y的相应增加应当小于1。为了光栅化,yi+1只可能选择如下两种位置之一。
yi+1的位置选择yi+1=yi 或者 yi+1=yi+1。选择的原则是看精确值y与yi及yi+1的距离d1及d2的大小而定。计算式为:
y=m(xi+1)+b
d1=y-yi
d2=yi+1-y
如果d1-d2>0,则yi+1=yi+1,否则yi+1=yi。因此算法的关键在于简便地求出d1-d2的符号。将式(2.1.1)、(2.1.2)、(2.1.3)代入d1-d2,得
d1-d2=2y-2yi-1=2(dy/dx) (xi+1)-2yi+2b-1
用dx乘等式两边,并以Pi=dx(d1-d2)代入上述等式,得
Pi=2xidy-2yidx+2dy+dx(2b-1)
d1-d2是我们用以判断符号的误差。由于在1a象限,dx总大于0,所以Pi仍旧可以用作判断符号的误差。Pi-1为:
Pi+1=Pi+2dy-2dx(yi+1-yi)
误差的初值P1,可将x1, y1,和b代入式(2.1.4)中的xi, yi而得到:
P1=2dy-dx
综述上面的推导,第1a象限内的直线Bresenham算法思想如下:
1.画点(x1, y2); dx=x2-x1; dy=y2-y1;
计算误差初值P1=2dy-dx; i=1;
2.求直线的下一点位置:
xi+1=xi+1;
if Pi>0 则yi+1=yi+1;
否则yi+1=yi;
3.画点(xi+1, yi-1);
4.求下一个误差Pi+1;
if Pi>0 则Pi+1=Pi+2dy-2dx;
否则Pi+1=Pi+2dy;
5.i=i+1; if i<dx+1则转2;否则结束。
🌕2.4.3 DDA算法画圆
假定某点Pi(xi, yi)已经是该圆弧上最接近实际圆弧的点,那么Pi的下一个点只可能是正右方的P1或右下方的P2两者之一
构造判别函数:
F(x, y)= x2 + y2 – R2
当F(x, y)= 0,表示点在圆上,当F(x, y)> 0,表示点在圆外,当F(x, y)< 0,表示点在圆内。如果M是P1和P2的中点,则M的坐标是(xi + 1, yi – 0.5),当F(xi + 1, yi – 0.5)< 0时,M点在圆内,说明P1点离实际圆弧更近,应该取P1作为圆的下一个点。同理分析,当F(xi + 1, yi – 0.5)> 0时,P2离实际圆弧更近,应取P2作为下一个点。当F(xi + 1, yi – 0.5)= 0时,P1和P2都可以作为圆的下一个点,算法约定取P2作为下一个点。
现在将M点坐标(xi + 1, yi – 0.5)带入判别函数F(x, y),得到判别式d:
d = F(xi + 1, yi – 0.5)= (xi + 1)2 + (yi –0.5)2 – R2
若d < 0,则取P1为下一个点,此时P1的下一个点的判别式为:
展开后将d带入可得到的递推关系:d’ = d + 2xi + 3
若d > 0,则取P2为下一个点,此时P2的下一个点的判别式为:
d’ = F(xi + 2, yi – 1.5)= (xi + 2)2 + (yi –1.5)2 – R
展开后将d带入可得到判别式的递推关系:d’ = d + 2(xi - yi) + 5
特别的,在第一个象限的第一个点(0, R)时,可以推倒出判别式d的初始值d0:
d0 = F(1, R – 0.5) = 1 – (R – 0.5)2 –R2 = 1.25 – R
🌕2.4.4 Bresenham算法画圆
Bresenham算法画圆生成算法思路如下:
①求误差初值,p0=3- 2r,i=1,画点(0,r)
②求下一个点的y坐标,其中xi+1=xi+1,如果pi<0则yi+1=yi,否则yi+1=yi-1
③画点(xi+1,yi+1)
④计算下一个误差,如果pi<0则pi+1=pi+4xi+6,否则pi+1=pi+4(xi-yi)+10
⑤i=i+1,如果x=y则结束,否则返回步骤②。
🌍2.5 实验步骤
(1) 在Microsoft Visual Studio 2022环境下创建名为BmpRead的MFC应用程序工程(单文档)
(2)编程实现DDA算法和算法画直线,同时利用两种算法画圆。
🌕2.5.1 DDA算法代码实现画直线
#include <gl\glut.h> #include <math.h> #include <stdio.h> #include <Windows.h> #include <conio.h> #include <easyx.h> void DDA(int X0, int Y0, int Xn, int Yn) { int dx = Xn - X0; int dy = Yn - Y0; int steps, direction; float xIncrement, yIncrement; float x = X0, y = Y0; if (abs(dx) > abs(dy)) { steps = abs(dx); direction = 0; } else { steps = abs(dy); direction = 1; } xIncrement = float(dx) / float(steps); yIncrement = float(dy) / float(steps); //画点 glBegin(GL_POINTS); for (int k = 0; k <= steps; ++k) { if (direction == 0) { glVertex2i(int(x), int(y + 0.5)); } else { glVertex2i(int(x + 0.5), int(y)); } x += xIncrement; y += yIncrement; } glEnd(); } void display() { glClear(GL_COLOR_BUFFER_BIT); DDA(0, 0, 800, 1000);//调用函数 glFlush(); } void draw_pixel(int ix, int iy) { glBegin(GL_POINTS); glVertex2i(ix, iy); glEnd(); } void myinit() { glClearColor(1.0, 0.8, 1.0, 1.0); glColor3f(0.0, 0.5, 0.5); glPointSize(1.0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0.0, 1000.0, 0.0, 1000.0); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(600, 500); glutInitWindowPosition(150.0, 150.0); glutCreateWindow("DDA画直线"); glutDisplayFunc(display); myinit(); glutMainLoop(); }
运行结果:
🌕2.5.2 Breasenham 算法实现画直线
#include <gl\glut.h> #include <math.h> #include <stdio.h> #include <Windows.h> #include <conio.h> #include <easyx.h> void Bresenham(int x0, int y0, int x1, int y1) { void draw_pixel(int, int); int dx = abs(x1 - x0), dy = abs(y1 - y0), p = 2 * dy - dx; int Dy2 = 2 * dy, Dx2 = 2 * dy - 2 * dx; int x, y; if (x0 > x1) { x = x1; y = y1; x1 = x0; } else { x = x0; y = y0; } draw_pixel(x, y); while (x < x1) { x++; if (p < 0) p += Dy2; else { y++; p += Dx2; draw_pixel(x, y); } } } void display() { glClear(GL_COLOR_BUFFER_BIT); Bresenham(0, 0, 800, 800);//调用函数 glFlush(); } void draw_pixel(int ix, int iy) { glBegin(GL_POINTS); glVertex2i(ix, iy); glEnd(); } void myinit() { glClearColor(1.0, 0.8, 1.0, 1.0); glColor3f(0.0, 0.5, 0.5); glPointSize(1.0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0.0, 1000.0, 0.0, 1000.0); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(600, 500); glutInitWindowPosition(150.0, 150.0); glutCreateWindow("Bresenham算法画直线"); glutDisplayFunc(display); myinit(); glutMainLoop(); }
运行结果:
🌕2.5.3 DDA算法代码实现画圆
#include<iostream> #include<graphics.h> #include<conio.h> #include<time.h> #include<math.h> #include<stdlib.h> #include<stdio.h> using namespace std; /*中点画圆*/ void MidpointCircle(int x0, int y0, int r, int color) { int x = 0, y = r; float d = 5.0 / 4 - r; while (x <= y) { putpixel(x0 + x, y0 + y, color); putpixel(x0 + x, y0 - y, color); putpixel(x0 - x, y0 + y, color); putpixel(x0 - x, y0 - y, color); putpixel(x0 + y, y0 + x, color); putpixel(x0 + y, y0 - x, color); putpixel(x0 - y, y0 + x, color); putpixel(x0 - y, y0 - x, color); if (d < 0) d += x * 2.0 + 3; else { d += 2.0 * (x - y) + 5; y--; } x++; } } void main() { int x0, y0, x1, y1; initgraph(640, 480); setbkcolor(YELLOW); cleardevice(); MidpointCircle(300, 200, 90, BLACK); _getch(); closegraph(); }
运行结果:
🌕2.5.4 Breasenham算法代码实现画圆
#include <GL/glut.h> #include<math.h> #include<iostream> #include <easyx.h> using namespace std; GLfloat pointsize = 1.0f; void Bresenham(int x0,int y0,GLint R) { int x1 = x0, y1 = y0; GLint a = 0; GLint y = (int)(R * 1.0 / (sqrt(2))); GLfloat d0 = 1.25 - R; GLfloat d; glPointSize(pointsize); GLint cx = 0, cy = R; glVertex2i(0, 0); while (a <= y) { glVertex2i(x1+a, y1+cy); glVertex2i(x1-a, y1-cy); glVertex2i(x1-a, y1+cy); glVertex2i(x1+a, y1-cy); glVertex2i(x1+cy,y1-a); glVertex2i(x1-cy,y1-a); glVertex2i(x1-cy, y1+a); glVertex2i(x1+cy, y1+a); a++; if (d0 <= 0) { d0 = d0 + 2 * a + 3; cy = cy; } else { d0 = d0 + 2 * (a - cy) + 5; cy = cy - 1; } } } void display() { glClearColor(1.0, 0.8, 1.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glColor3f(0.0, 0.0f, 0.0f); glBegin(GL_POINTS); Bresenham(0,100,100); //调用函数 glEnd(); glFlush(); } void draw_pixel(int ix, int iy) { glBegin(GL_POINTS); glVertex2i(ix, iy); glEnd(); } void myinit() { glClearColor(1.0, 0.8, 1.0, 1.0); glColor3f(0.0, 0.5, 0.5); glPointSize(1.0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0.0, 1000.0, 0.0, 1000.0); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowPosition(100, 100); glutInitWindowSize(600, 600); glutCreateWindow("Breasenham算法画圆"); glClearColor(1.0, 1.0, 1.0, 1.0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(-500.0, 500.0, -500.0, 500.0); glutDisplayFunc(display); glutMainLoop(); return 0; }
运行结果:
🌍2.6 研究体会
- 实验环境配置和头文件安装: 通过本次实验,我成功完成了所需的环境配置,并使用EasyX安装了相应的头文件。在Visual Studio 2022开发平台中,我顺利进行了C++编程,这包括了配置开发环境、安装必要的库和头文件等步骤。这为后续的图形学实验提供了一个稳定的基础。
- DDA算法和Bresenham算法的实现与比较: 在实验中,我分别实现了DDA算法和Bresenham算法用于生成直线和圆。我对这两种算法的效率和精度有了更深刻的理解。Bresenham算法相较于DDA算法在速度上更快,因为它避免了直线斜率的计算和浮点数运算,只使用整数。然而,DDA算法在精度上更高,因为它使用浮点数运算,但可能不如Bresenham算法快速。了解了它们的特点,我能够在选择算法时更好地权衡速度和精度。
- Visual Studio 2022开发平台和函数库的使用: 在实验中,我发现之前可用的
getch()
函数需要替换为_getch()
。通过查询,我了解到带下划线的函数一般是函数库内部的函数,而不带下划线的一般是提供给用户使用的函数。这是为了防止用户定义的函数和函数库的函数重名冲突。这个经验使我更加熟悉了Visual Studio 2022开发平台的使用,并对函数库的命名规范有了更清晰的认识。
📝总结
图形学领域宛如一片广阔而未被完全探索的创意海洋,邀请你勇敢踏足数字艺术和计算机图形学的神秘领域。这是一场富有创意和技术挑战的学习之旅,从基础概念到算法实现,逐步揭示更深层次的图形分析、渲染技术和智能图形识别的奥秘。