Win Form图形编程实践——打砖块
Win Form图形编程实践——打砖块
引言
本项目学习自际为软件事务所的C#实现一个打砖块游戏 Step By Step,特此鸣谢。
最初,博主只是在简单学习了Win Form之后写一个有图形化界面的小游戏来锻炼一下自己的图形化编程技巧,思前想后选择了打砖块这个游戏。相较于贪吃蛇一类的小游戏,打砖块还是很有难度的,因为要控制球的运动轨迹以及球和砖块、挡板的碰撞问题,设计起来较为繁琐。幸好有上文提到的教程,在OOP方面提供了一个很好的思路,再次感谢。
另:为避免版权问题,游戏中所有涉及到的图片及图标等均为个人绘制。
思路
在寻找教程之前我自己写了一个导图来整理这个游戏的大体思路。如下:
整合网络教程以及我个人的思路,实现这个打砖块游戏的具体过程如下:
- 设计开始界面,具体如导图所示
- 标题
- 操作提示
- [开始游戏]按钮
- 开始界面背景图
- 设计游戏界面,主要参照教程进行:
- 游戏界面背景图
- 绘制挡板以及实现挡板的移动
- 绘制小球以及实现小球的移动
- 绘制砖块以及实现墙体集合
- 重新使用双缓冲技术实现绘图操作
- 实现小球与砖块和挡板的碰撞检测
- 设计展示界面,具体如导图所示:
- 计分板
- 游戏进行时间
- 排行榜
- 设计游戏结束界面,具体如导图所示:
- 标题
- 判断分数
- 如果进入前三名:记录玩家姓名并更新排行榜后展示[结束游戏]按钮
- 如果没进入前三名:直接展示[结束游戏]按钮
- [结束游戏]按钮
实现过程
开始界面
设置窗口格式
将AutoSize设置为False,Size为450x620.
设置窗口图标。
将Locked设置为True。
设置窗口内默认字体,这样在添加Label或者TextBox时就不用重新设置字体了,但有可能仍需要重新调整字体大小。
设置背景图片
将图片文件夹放置在所建项目文件夹中的bin\Debug中便于检索文件。
将这个操作放置在[Form]BricksBreaker的Load事件中,具体代码如下:
private void BricksBreaker_Load(object sender, EventArgs e){Welcome();string backgroundImagePath = Application.StartupPath;backgroundImagePath += @"\imgs\Welcome\BackGround.png";this.BackgroundImage = Image.FromFile(backgroundImagePath);this.BackgroundImageLayout = ImageLayout.Stretch;}
Systems.Windows.Forms.Application.StartupPath是运行时.exe文件的位置,采用相对路径可以方便后续实现安装的操作。
Welcome()函数
最开始是为了便于操作主界面以及实现[再来一局]按钮创造的函数,将各个组件的初始化放在了这个函数中,但是进行到左后发现其实并不能简化而且难以在复杂的Welcome()函数中分离出适合于第二局游戏的语句,便搁置在此。
private void Welcome(){GameStart.Visible = true;GameTitle.Visible = true;Hint.Visible = true;GameBox.Visible = false;Suggestion.Visible = false;ScoreBoard.Visible = false;ScoreRec.Visible = false;PlayersTitle.Visible = false;BestPlayers.Visible = false;GameTime.Visible = false;GameOverTitle.Visible = false;NameRecTitle.Visible = false;NameRec.Visible = false;NameConfirm.Visible = false;Close.Visible = false;}
[Label]GameStart为[开始游戏]按钮,[Label]GameTitle为标题,[Label]Hint为操作提示。
其他为后续实现过程中逐渐添加的语句,故不在此叙述。
[Label]GameStart的Click事件
private void GameStart_Click(object sender, EventArgs e){GameStartFunc();this.GameBox.Refresh();}private void GameStartFunc(){GameStart.Visible = false;GameTitle.Visible = false;Hint.Visible = false;string gamePageImagePath = Application.StartupPath;gamePageImagePath += @"\imgs\GamePage\GamePage.png";GameBox.BackgroundImage = Image.FromFile(gamePageImagePath);GameBox.BackgroundImageLayout = ImageLayout.Stretch;GameBox.Visible = true;Suggestion.Visible = true;ScoreBoard.Visible = true;ScoreRec.Visible = true;PlayersTitle.Visible = true;BestPlayers.Visible = true;GameTime.Visible = true;objectList.Add(board);objectList.Add(ball);objectList.Add(bricks);string nameListPath = Application.StartupPath;nameListPath += @"\data\PlayersNameList.txt";System.IO.StreamReader streamReader = new System.IO.StreamReader(nameListPath);BestPlayers.Text = streamReader.ReadToEnd();streamReader.Close();players.Add(new Player(BestPlayers.Lines[0]));players.Add(new Player(BestPlayers.Lines[1]));players.Add(new Player(BestPlayers.Lines[2]));}
同样的GameStartFunc()也是为了方便[再来一局]按钮的事件而做出来的冗余函数(lll¬ω¬)。
部分语句为后续添加。
游戏界面
建立[PictureBox]GameBox
[PictureBox]GameBox的相关属性:
Dock: True
Size: 432x573
Locked: True
GameStartFunc()内相关初始化语句为:
string gamePageImagePath = Application.StartupPath; gamePageImagePath += @"\imgs\GamePage\GamePage.png"; GameBox.BackgroundImage = Image.FromFile(gamePageImagePath); GameBox.BackgroundImageLayout = ImageLayout.Stretch; GameBox.Visible = true;
选择PictureBox作为图形的载体,在[PictureBox]GameBox的Paint事件中置入各个组件的绘制语句。
private void GameBox_Paint(object sender, PaintEventArgs e){Bitmap bitmap = new Bitmap(GameBox.Width, GameBox.Height);foreach(Object @object in objectList){// @object.Draw(e.Graphics, this.GameBox);@object.Draw(Graphics.FromImage(bitmap), this.GameBox);}e.Graphics.DrawImage(bitmap, 0, 0);}
其中[List]objectList为各个Object的集合,通过foreach简化代码。
重绘[PictureBox]GameBox可以用GameBox.Refresh()实现。
(注释部分为直接绘制,未注释部分为双缓冲操作)
添加一个计时器[Timer]Action,利用其Tick事件来刷新图形以及控制刷新频率:
private void Action_Tick(object sender, EventArgs e){ball.Run(GameBox, board, bricks, ref score);if (ball.touchedBound){Action.Stop();GameOver();}GameBox.Refresh();}
同时Tick事件中还可以添加其他不同功能的语句,将会在后续过程中解释。
在[PictureBox]GameBox中绘制图形
Object类
在编写Board类和Ball类后将相似代码提取出来构造一个基类Object类。
class Object{public int xPos { get; set; }public int yPos { get; set; }public Rectangle rectangle { get; set; }public bool isDelete = false;public virtual void Draw(Graphics g, PictureBox GameBox) { }}
xPos, yPos为Ball类和Board类的相似变量,使用方法也大致相同。
另附:C#值Get、Set用法(作者:雨夜潇潇)
[Rectangle]rectangle为绘制图形时所需的变量
为Object类构建一个Draw虚方法之后可以在[PictureBox]GameBox的Paint事件中达到简化代码的作用,即不必每个组件依次使用一条语句来调用各自类中不同的Draw()方法。
Board类
Board类有几个基本参数:横坐标、纵坐标、宽度、高度以及移动速度。
class Board : Object{public int boardWidth, boardHeight;public int speedX { get; set; }public const double collsion_MaxAngleIncrement = 0.25;public enum BoardDirection{Left, Right, None}public Board(int x, int y, int width, int height, int speedx=8){this.xPos = x;this.yPos = y;this.speedX = speedx;boardWidth = width;boardHeight = height;}public override void Draw(Graphics g, PictureBox GameBox){SolidBrush solidBrush = new SolidBrush(Color.BurlyWood);Pen pen = new Pen(Color.SaddleBrown, 2);rectangle = new Rectangle(GameBox.Left + xPos, GameBox.Top + yPos, boardWidth, boardHeight);g.DrawRectangle(pen, rectangle);g.FillRectangle(solidBrush, rectangle);}public void Move(BoardDirection direction, PictureBox GameBox){switch (direction){case BoardDirection.Left:if(xPos - speedX > BricksBreaker.BorderWidth){xPos -= speedX;}else{xPos = BricksBreaker.BorderWidth;}break;case BoardDirection.Right:if(xPos + boardWidth + BricksBreaker.BorderWidth + speedX < GameBox.Width){xPos += speedX;}else{xPos = GameBox.Width - boardWidth - BricksBreaker.BorderWidth;}break;}}}
其中的Move()方法利用Board类中的枚举变量确定挡板的移动方向。BricksBreaker.BorderWidth为[Form]BricksBreaker中定义的常量,表示游戏界面中边框的宽度。
Move()方法的调用在[Form]BricksBreaker的KeyDown事件中:
private void BricksBreaker_KeyDown(object sender, KeyEventArgs e){if (GameBox.Visible){switch (e.KeyData){case Keys.A:if (gameStarted){board.Move(Board.BoardDirection.Left, GameBox);}break;case Keys.D:if (gameStarted){board.Move(Board.BoardDirection.Right, GameBox);}break;case Keys.Space:if (!gameStarted){gameStarted = true;Suggestion.Visible = false;Action.Interval = 100;Action.Start();}break;default:break;}// GameBox.Refresh();}}
同时这个KeyDown事件中还实现了[开始游戏]按钮的后续——按空格键发射小球,使用gameStarted判断游戏是否已经正式开始。
起初挡板的绘制语句是在KeyDown事件中的,在挡板坐标改变后直接在[PictureBox]GameBox中绘制挡板,在实现双缓冲时将语句移动到其Load事件中。
Ball类
Ball类的参数与Board类相似但略有不同:横坐标、纵坐标、半径、速度方向以及速度大小。
采用向量而不是坐标增量的方式表示速度有利于控制小球的方向。因为在此之前有设想过挡板的不同位置对小球的速度影响不同,即距离挡板中心越远速度偏离越大,若是使用坐标增量的方式表示速度可能计算比较复杂或者不好控制之类的,而使用向量的方式表示速度则可以直接在代表速度方向的变量上加减。
class Ball : Object{public const int radius = 8;public double speedAngle { get; set; }public int speedDis { get; set; }public bool touchedBound = false;private const int speedIncrement = 1;public Ball(int x, int y, double angle=0.5, int dis = 5){this.xPos = x;this.yPos = y;this.speedAngle = angle;this.speedDis = dis;}public void SpeedUpdate(){this.speedDis += speedIncrement;}public override void Draw(Graphics g, PictureBox GameBox){SolidBrush solidBrush = new SolidBrush(Color.LightGoldenrodYellow);Pen pen = new Pen(Color.SaddleBrown, 2);rectangle = new Rectangle(GameBox.Left + xPos - radius, GameBox.Top + yPos - radius, 2 * radius, 2 * radius);g.DrawEllipse(pen, rectangle);g.FillEllipse(solidBrush, rectangle);}public void Run(PictureBox GameBox, Board board, Bricks bricks, ref int score){int xDis = (int)(speedDis * Math.Cos(Math.PI * speedAngle));int yDis = -(int)(speedDis * Math.Sin(Math.PI * speedAngle));xPos += xDis;yPos += yDis;// Hit(board, bricks, ref score);if (xPos + radius + BricksBreaker.BorderWidth >= GameBox.Width){xPos = GameBox.Width - BricksBreaker.BorderWidth - radius;speedAngle = 1.0 - speedAngle;}if(xPos - radius <= BricksBreaker.BorderWidth){xPos = BricksBreaker.BorderWidth + radius;speedAngle = 1.0 - speedAngle;}if(yPos + radius >= board.yPos + board.boardHeight){yPos = board.yPos + board.boardHeight - radius;speedAngle = -speedAngle;this.touchedBound = true;}if(yPos - radius <= BricksBreaker.BorderWidth){yPos = BricksBreaker.BorderWidth + radius;speedAngle = -speedAngle;}}}
其中Ball类的Draw()方法与Board类类似,只是颜色有所改变。
SpeedUpdate()方法是为后续设计关卡准备的。
Run()方法为小球的基本移动方法,具体有坐标移动和检测与边界的碰撞。
在后面会实现Ball类与挡板以及砖块的碰撞检测方法。
Brick类/Bricks类
Brick类与Board类基本一致。
class Brick : Object{public const int brickWidth = 67, brickHeight = 20;public const int score = 5;public Brick() { }public Brick(int x, int y){xPos = x;yPos = y;}public override void Draw(Graphics g, PictureBox GameBox){SolidBrush solidBrush = new SolidBrush(Color.FromArgb(181, 99, 0));Pen pen = new Pen(Color.SaddleBrown, 4);rectangle = new Rectangle(GameBox.Left + xPos, GameBox.Top + yPos, brickWidth, brickHeight);g.DrawRectangle(pen, rectangle);g.FillRectangle(solidBrush, rectangle);}}
对砖块的集合单独创建一个Bricks类,以便在[List]objectList中调用Draw()方法。
初始化函数用来创建一个砖墙。
Draw()方法中分别调用每一个砖块的Draw()方法。
class Bricks : Object{private const int _width = 402+BricksBreaker.BorderWidth, _height = 140+BricksBreaker.BorderWidth;public const int width = 6, height = 7;public List<Brick> bricks { get; set; }public Bricks(){MakeBrickWall();}private void MakeBrickWall(){bricks = new List<Brick>();for (int y = 15; y < _height; y += Brick.brickHeight) {for (int x = 15;x < _width;x += Brick.brickWidth){Brick brick = new Brick(x, y);bricks.Add(brick);}}}public override void Draw(Graphics g, PictureBox GameBox){foreach(Brick brick in bricks){if(brick.isDelete){continue;}brick.Draw(g, GameBox);}}}
双缓冲技术
双缓冲技术:
即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。
双缓冲技术在C#中的实现过程:
先创建一个Bitmap实例bitmap,大小与[PictureBox]GameBox相同。
Bitmap bitmap = new Bitmap(GameBox.Width, GameBox.Height);
将本来要绘制在[PictureBox]GameBox中的图形绘制到[Bitmap]bitmap中。
foreach(Object @object in objectList){// @object.Draw(e.Graphics, this.GameBox);@object.Draw(Graphics.FromImage(bitmap), this.GameBox);}
用DrawImage将[Bitmap]bitmap绘制到[PictureBox]GameBox中。
整体代码如下:
private void GameBox_Paint(object sender, PaintEventArgs e){Bitmap bitmap = new Bitmap(GameBox.Width, GameBox.Height);foreach(Object @object in objectList){@object.Draw(Graphics.FromImage(bitmap), this.GameBox);}e.Graphics.DrawImage(bitmap, 0, 0);}
Ball类的碰撞检测
Ball类的碰撞检测分为两个部分,一是和挡板的碰撞,二是和砖块的碰撞。两种碰撞分别会产生不同的效果。
对砖块的碰撞会造成砖块的删除和分数的增加,同时小球方向改变。
对挡板的碰撞会使小球方向改变,且改变的多少随碰撞位置的变化而变化。
具体代码如下:
private void Hit(Board board, Bricks brickset, ref int score){for (int i = 0; i < brickset.bricks.Count; i++){if (brickset.bricks[i].isDelete){continue;}int leftBound = brickset.bricks[i].xPos;int rightBound = leftBound + Brick.brickWidth;int upBound = brickset.bricks[i].yPos;int downBound = upBound + Brick.brickHeight;if(xPos < leftBound && xPos + radius >= leftBound){if(yPos > upBound && yPos < downBound){xPos = leftBound - radius;speedAngle = 1.0 - speedAngle;brickset.bricks[i].isDelete = true;score += Brick.score;}if(yPos == downBound){xPos = leftBound - radius;speedAngle = 1.0 - speedAngle;brickset.bricks[i].isDelete = true;score += Brick.score;if (i + Bricks.width < brickset.bricks.Count && !brickset.bricks[i + Bricks.width].isDelete){brickset.bricks[i + Bricks.width].isDelete = true;score += Brick.score;}}}else if(xPos > rightBound && xPos - radius <= rightBound){if(yPos > upBound && yPos < downBound){xPos = rightBound + radius;speedAngle = 1.0 - speedAngle;brickset.bricks[i].isDelete = true;score += Brick.score;}if (yPos == downBound){xPos = rightBound + radius;speedAngle = 1.0 - speedAngle;brickset.bricks[i].isDelete = true;score += Brick.score;if (i + Bricks.width < brickset.bricks.Count && !brickset.bricks[i + Bricks.width].isDelete){brickset.bricks[i + Bricks.width].isDelete = true;score += Brick.score;}}}else if(yPos < upBound && yPos + radius >= upBound){if(xPos > leftBound && xPos < rightBound){yPos = upBound - radius;speedAngle = -speedAngle;brickset.bricks[i].isDelete = true;score += Brick.score;}if(xPos == rightBound){yPos = upBound - radius;speedAngle = -speedAngle;brickset.bricks[i].isDelete = true;score += Brick.score;if(i % Bricks.width < 5 && !brickset.bricks[i + 1].isDelete){brickset.bricks[i + 1].isDelete = true;score += Brick.score;}}}else if(yPos > downBound && yPos - radius <= downBound){if (xPos > leftBound && xPos < rightBound){yPos = downBound + radius;speedAngle = -speedAngle;brickset.bricks[i].isDelete = true;score += Brick.score;}if (xPos == rightBound){yPos = downBound + radius;speedAngle = -speedAngle;brickset.bricks[i].isDelete = true;score += Brick.score;if (i % Bricks.width < 5 && !brickset.bricks[i + 1].isDelete){brickset.bricks[i + 1].isDelete = true;score += Brick.score;}}}}if (yPos < board.yPos && yPos + radius >= board.yPos && xPos >= board.xPos && xPos <= board.xPos + board.boardWidth){yPos = board.yPos - radius - 1;speedAngle = -speedAngle;int dis = board.xPos + board.boardWidth / 2 - xPos;double angleIncrement = (double)dis / (double)board.boardWidth * Board.collsion_MaxAngleIncrement;speedAngle += angleIncrement;}}
判断过程比较繁琐,与对边界的碰撞判断相同,为了防止出现图像穿模的情况使用类似:
if(*** <= **){*** = **; }
的方式强制使图形不会越界。
展示界面
展示界面分别使用[Label]ScoreRec, [Label]GameTime, [TextBox]BestPlayers来分别表示当前分数,游戏时间,排行榜。
分数实时显示
分数的实时显示在[Timer]Action的Tick事件中实现:
timeScore = (timeScore + 1) % timeScoreLoop;
if(timeScore == 0)
{score++;
}ScoreRec.Text = score.ToString("D5");
其中score、timeScore和timeScoreLoop为[Form]BricksBreaker中定义的整型变量:
int score = 0;
private int timeScore = 0;
private const int timeScoreLoop = 15;
由于游戏规则设定为分数随游戏时间的增加和砖块数量的减少而增加,而每次如果调用Tick()事件时都使score增加会降低不少游戏难度,所以设置timeScoreLoop来控制score随游戏时间增加的速度。
游戏时间展示
游戏时间利用每次调用Tick事件时的绝对时间和游戏正式开始时的绝对时间做减法得到一个相对时间,最后将其转换为可以用来展示的格式。
具体实现过程如下:
在[Form]BricksBreaker中定义一个[DateTime]beginTime用来表示游戏正式开始时的绝对时间:
DateTime beginTime = new DateTime();
在KeyDown事件中,将[DateTime]beginTime设置为当前时间:
case Keys.Space:if (!gameStarted){gameStarted = true;Suggestion.Visible = false;beginTime = System.DateTime.Now;Action.Interval = 100;Action.Start();}break;
在Tick事件中,利用[DateTime]nowTime与[DateTime]beginTime的差值,与DateTime中的最小时间相加将[TimeSpan]gameTime转换为可以转换为表示时间的字符串的[DateTime]类型。
DateTime nowTime = System.DateTime.Now; TimeSpan gameTime = nowTime - beginTime; GameTime.Text = System.DateTime.MinValue.AddMilliseconds(gameTime.TotalMilliseconds).ToLongTimeString();
排行榜界面
在GameStartFunc函数中添加如下语句:
string nameListPath = Application.StartupPath;
nameListPath += @"\data\PlayersNameList.txt";
System.IO.StreamReader streamReader = new System.IO.StreamReader(nameListPath);
BestPlayers.Text = streamReader.ReadToEnd();
streamReader.Close();
其中nameListPath表示排行榜文件所在位置。
结束界面
当Tick事件中检测到[Ball]ball的touchedBound为True时,结束[Timer]Action的计时,并启动GameOver函数:
private void GameOver(){GameOverTitle.Visible = true;}
为了便于判断玩家是否进入排行榜,进行以下操作:
创建一个Player类并重载它的ToString()方法:
class Player{public string name;public int score;public Player(string line){string[] info = line.Split(' ');this.name = info[1];this.score = Convert.ToInt32(info[2]);}public Player(string nam, int scor){this.name = nam;this.score = scor;}public override string ToString(){return name + " " + score.ToString("D5");}}
在[Form]BricksBreaker中定义一个[List]players:
List<Player> players = new List<Player>();
在GameStartFunc函数中添加如下语句进行初始化:
players.Add(new Player(BestPlayers.Lines[0])); players.Add(new Player(BestPlayers.Lines[1])); players.Add(new Player(BestPlayers.Lines[2]));
在[Form]BricksBreaker中定义一个变量playerRank用来表示玩家的排名:
private int playerRank = -1;
在GameOver函数中添加如下语句:
int index = 0; for(;index < players.Count; index++) {if(score >= players[index].score){for(int i = players.Count - 1;i > index; i--){players[i] = players[i - 1];}playerRank = index;NameRecTitle.Visible = true;NameRec.Visible = true;NameConfirm.Visible = true;break;} } if(playerRank == -1) {GameOverButtons(); }
如果玩家没有进入排行榜则直接展示[结束游戏]按钮。
private void GameOverButtons(){NameRecTitle.Visible = false;NameRec.Visible = false;NameConfirm.Visible = false;Close.Visible = true;}
[结束游戏]按钮(即[Button]Close)的Click事件:
private void Close_Click(object sender, EventArgs e){this.Close();}
如果玩家进入排行榜则展示[Label]NameRecTitle、[TextBox]NameRec和[Button]NameConfirm,进行玩家名称的输入。
在[Button]NameConfirm的Click事件中添加如下语句:
private void NameConfirm_Click(object sender, EventArgs e){string playerName = NameRec.Text;players[playerRank] = new Player(playerName, score);string[] lines = new string[players.Count];for(int i = 0;i < players.Count; i++){lines[i] = (i + 1).ToString() + ". " + players[i].ToString();}string nameListPath = Application.StartupPath;nameListPath += @"\data\PlayersNameList.txt";System.IO.File.WriteAllLines(nameListPath, lines);System.IO.StreamReader streamReader = new System.IO.StreamReader(nameListPath);BestPlayers.Text = streamReader.ReadToEnd();streamReader.Close();GameOverButtons();}
将新输入的玩家名称保存到排行榜文件中同时更新排行榜界面。
制作安装包
利用Visual Studio 2019中的Microsoft Visual Studio Installer Projects拓展制作游戏安装包。
首先,新建一个Setup Wizard项目,项目名称即为最后生成的安装包的名称。
然后在Application Folder中添加所安装的应用程序以及相关文件,注意应和程序调用文件时使用的的相对路径保持一致。在添加应用程序文件之前可以使用Resource Hacker更换一下图标文件。
最后在解决方案资源管理器中右键项目,然后选择“生成”,便大功告成了!
Github
Github代码地址
Win Form图形编程实践——打砖块相关推荐
- 2020-2021学年——图像图形编程实践实验1_图像二值化
图像二值化 实验目的 1.熟练掌握在MATLAB中如何读取图像. 2.掌握如何利用MATLAB来获取图像的大小.颜色.高度.宽度等等相关信息. 3.掌握如何在MATLAB中按照指定要求存储一幅图像的方 ...
- 2020-2021学年——图像图形编程实践实验3_图像拉普拉斯锐化
图像拉普拉斯锐化 实验目的 学习如何用锐化处理技术来加强图像的目标边界和图像细节: 对图像进行梯度算子.拉普拉斯算子计算,使图像的某些特征(如边缘.轮廓等)得以进一步的增强及突出. 实验设备 PC机. ...
- 2020-2021学年——图像图形编程实践实验2_图像的基本运算
图像的基本运算 实验目的 1.了解图像的算术运算在数字图像处理中的初步应用; 2.体会图像算术运算处理的过程和处理前后图像的变化. 实验设备 PC机.matlab2018b 实验原理 图像的代数运 ...
- 2020-2021学年——图像图形编程实践实验4_Canny图像边缘检测
Canny图像边缘检测 实验目的 了解并掌握使用微分算子进行图像边缘检测的基本原理: 了解Canny边缘检测原理与实现,进一步理解图像锐化的实质. 实验设备 PC机.matlab2018b 实验原理 ...
- 2020-2021学年——图像图形编程实践实验5_彩色图像的切割
彩色图像的切割 实验目的 理解图像分割的基本概念: 理解彩色图像的基本概念: 掌握进行欧氏距离和绝对值距离进行图像分割的基本方法: 实验设备 PC机.matlab2018b 实验原理 指定的颜色区 ...
- VS2017下安装fltk库——C++程序设计原理与实践图形编程指南
VS2017下安装fltk库--C++程序设计原理与实践图形编程指南 前言 最近,我在学习<C++程序设计原理与实践>(原书第一版)遇到了安装图形库的问题,我花了两天时间,通过各种途径查找 ...
- 图形图像编程实践 课程报告
文章目录 图形图像编程实践 课程报告 实验环境 EX01 图像的二值化 问题描述 算法设计 结果分析 原图 灰度图 二值化结果图 EX02 图像的加减 问题描述 算法设计 结果分析 原图 灰度图 图片 ...
- 【编程实践】Linux / UNIX Shell编程极简教程
不同于一般的介绍Linux Shell 的文章,本文并未花大篇幅去介绍 Shell 语法,而是以面向"对象" 的方式引入大量的实例介绍 Shell 日常操作,"对象&qu ...
- winform chart 添加数据报错_C# win Form开发 构造指定格式数据表字段值
在进行Win Form开发时,需要实现对数据进行存储与管理,主要采用数据库管理系统SQL Server进行数据的存储.在项目教学过程中给出下面问题描述: 问题:数据表Competitor(参赛者)要求 ...
- 郑捷《机器学习算法原理与编程实践》学习笔记(第七章 预测技术与哲学)7.1 线性系统的预测...
7.1.1 回归与现代预测 7.1.2 最小二乘法 7.1.3 代码实现 (1)导入数据 def loadDataSet(self,filename): #加载数据集X = [];Y = []fr = ...
最新文章
- 好礼相送|CSDN云原生 Meetup 成都站报名热烈启动,12.18见!
- 在Python中计算一次性计算多个百分位数percentile、quantile
- 在Kubernetes集群上部署高可用Harbor镜像仓库
- Spring Boot整合Mybatis-Plus 增删改查+ 分页基本使用完整示例
- 博士申请 | 瑞典皇家理工学院分布式计算研究组招收NLP/ML全奖博士生
- 「Python-OpenCV」setMouseCallback传回选取的像素坐标
- CodeForces798cMike and gcd problem
- 【虚拟化实战】存储设计之一存储类型
- Android-构建不同环境的Apk
- 【算法】回溯法——0-1背包问题
- python 镜像_python测开平台使用dockerfile构建镜像
- ITSS服务管理体系建立流程(四个阶段)附:广东软件行业协会ITSS评估
- Collected errors: * opkg_conf_load: Could not lock /var/lock/opkg.lock: Resource temporarily unavail
- Android Interpolator属性 设置动画速度
- java本地Cache缓存的使用
- 没想到,还有小白不知道怎么比较数组是否相等以及检出不匹配项
- 损失函数及对应的任务(待续)
- 10秒钟执行一次计划任务
- 传奇如何读取服务器信息,传奇服务器修改之命令服务脚本详细使用方法介绍
- 免费邮箱排名用什么好?学校邮箱如何注册?邮件群发哪个多?