计算机图形学、贝塞尔曲线及绘制方法、反走样问题的解决(附完整代码)

news2024/12/23 16:36:02

贝塞尔曲线

  • 1. 本次作业实现的函数及简单描述(详细代码见后)
  • 2. 与本次作业有关的基础知识整理
  • 3. 代码描述(详细)
  • 4. 完整代码
  • 5. 参考文献

(本篇为作者学习计算机图形学时根据作业所撰写的笔记, 如有同课程请勿Crtl+c/v

1. 本次作业实现的函数及简单描述(详细代码见后)

这一块主要介绍本次主要实现的几个函数及其功能

1.bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window)

可以绘制不止四个点的贝塞尔函曲线,并且处理了反走样问题
升级版: 利用距离比例,进行像素点的判别,解决贝塞尔曲线的反走样问题

2.recursive_bezier(const std::vector<cv::Point2f> &control_points, float t)

根据已有的点vector和给定的t值, 计算出来给定t值对应到贝塞尔曲线上的坐标点

2. 与本次作业有关的基础知识整理

这一部分主要介绍一些跟贝塞尔曲线有关的前置知识,以及贝塞尔曲线的画法

贝塞尔曲线:

Bézier curve(贝塞尔曲线) 是应用于二维图形应用程序的数学曲线。

贝塞尔曲线基础定义:

只要求一定经过起止点,起止点之间的若干控制点用于控制曲线弯曲的方向,最终形成一条经过起止点的光滑曲线被称为贝塞尔曲线。

贝塞尔曲线是线性插值的结果 : “选出两点之间的一个点”

P(t) = P0 + (P1−P0) = (1−t)P0 + t**P1, t∈[0,1] t=P0P1/P0P1

根据控制点的个数,贝塞尔曲线被分为一阶贝塞尔曲线(0个控制点)、二阶贝塞尔曲线(1个控制点)、三阶贝塞尔曲线(2个控制点)等等。(本次实验用到的是三阶贝塞尔曲线(2个控制点))

二阶贝塞尔曲线:

img

三阶贝塞尔曲线(本次作业需要实现的曲线)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

img

对于贝塞尔曲线,最重要的点是数据点和控制点

数据点: 指一条路径的起始点和终止点
控制点:控制点决定了一条路径的弯曲轨迹

贝塞尔曲线特点:

特点一: 曲线通过始点和终点,并与特征多边形首末两边相切于始点和终点,中间点将曲线拉向自己。
特点二: 平面离散点控制曲线的形状,改变一个离散点的坐标,曲线的形状将随之改变(点对曲线具有整体控制性)。
特点三: 曲线落在特征多边形的凸包之内,它比特征多边形更趋于光滑。

特点四: 起始点的方向为响应控制点的切线方向

逐段贝塞尔曲线:

控制点太多会影响控制点的效果的时候, 我们选择每四个控制点定义一条贝塞尔曲线,然后再将他们连接起来

img

常见应用场景

计算机辅助设计和计算机辅助制造应用(CAD/CAM),Adobe Illustrator, Photoshop, Inkscape, Gimp等等。还可以应用在一些图形技术中,像矢量图形(SVG),所以说在可视化学习的重要基础知识。

基本绘制方法: 德卡斯特里奥算法

  • 引入参数t (范围为 0~1)
  • 取b0到b1, b1到b2上t位置上的点b0’,b1’
  • 将b0’,b1’ 连接,取b0’到b1’上t位置上的点b0’’
  • 将所有t∈(0,1) 的点都便利一遍相连即可得到贝塞尔曲线
  • 若有n个控制点则将上面步骤进行递归操作直到找到最后bn’

解决反走样问题

程序在进行画线时是以点的形式,如果放大有较为明显的不连续,因此可以采用反走样来使其平滑过渡;

对于每一个点,其必然会包含在一个像素之中,可按照比例和着色进行插值,以求得一个较为平滑的过渡;

如下图所示,该点一定会在下图所示的黄色方框内部,而该点离像素点的最远距离为根号2,按照该点到其他四个点的距离进行插值;
在这里插入图片描述

3. 代码描述(详细)

这一部分主要介绍实现贝塞尔曲线的各个函数的功能,以及注意要点

de Casteljau的算法来计算贝塞尔曲线的点(步骤如下)

  • 计算点的总个数
  • 判断点的个数,只有一个点直接返回,多个点的话进行计算
  • 递归计算多个点t值对应的贝塞尔曲线上的点,直到计算到最后一个点
cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
{
    // TODO: Implement de Casteljau's algorithm
    int n = control_points.size();
    if (n == 1) return control_points[0];
    std::vector<cv::Point2f> res_control_points;

    for (int i = 0; i < n - 1; i++) {
        res_control_points.push_back(cv::Point2f(
            (1 - t) * control_points[i].x + t * control_points[i + 1].x,
            (1 - t) * control_points[i].y + t * control_points[i + 1].y));
    }
    return recursive_bezier(res_control_points, t);
}

基础版贝塞尔: (简单的将所给点画出)

  • 对每个t值循环计算出来对应的点然后进行绘制
void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
{
    // TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's 
    // recursive Bezier algorithm.
    for (double t = 0.0; t <= 1.0; t += 0.001)  // 循环得到点,并记录输出
    {
        cv::Point2f point = recursive_bezier(control_points, t); 
        window.at<cv::Vec3b>(point.y, point.x)[1] = 255;
    }
}

升级版贝塞尔(可以解决曲线的反走样问题)

  • 先找到根据t值计算出来的贝塞尔曲线上的点,其周围四个坐标
  • 计算该点到周围四个像素点的距离,距离越大,颜色应该越浅 {max-distance}
  • 按照distance/sum(distance)重新给四个点的像素赋值,并与原始色素的值进行比较,不能超过255.
  • 根据计算出来的点和对应的像素值进行重新绘制
void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
{
    // TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's 
    // recursive Bezier algorithm.
    for (float t = 0.0; t <= 1.0; t = t + 0.001)
    {
        cv::Point2f point = recursive_bezier(control_points, t);  //接收每一个t所求得的点
        //找到该点周围的四个点坐标,先找到一个右上的顶点,然后其他的通过这个点求出
        cv::Point2f point_1 = cv::Point2f(point.x - std::floor(point.x) < 0.5 ? std::floor(point.x) : std::ceil(point.x),   //右上的点
            point.y - std::floor(point.y) < 0.5 ? std::floor(point.y) : std::ceil(point.y));
        cv::Point2f point_2 = cv::Point2f(point_1.x - 1.0, point_1.y);
        cv::Point2f point_3 = cv::Point2f(point_1.x - 1.0, point_1.y - 1.0);
        cv::Point2f point_4 = cv::Point2f(point_1.x, point_1.y - 1.0);

        //用vector容器存储刚刚求得的四个像素点坐标;
        std::vector<cv::Point2f> distance_dot{ point_1,point_2,point_3,point_4 };
        float MaxDistance = sqrt(2.0);
        float SumDistance = 0.0f;
        float pi_distance = 0.0f;
        std::vector<float> distance_List = {};

        window.at<cv::Vec3b>(point.y, point.x)[1] = 255;//Mat.at<存储类型名称>(行,列)[通道]
        for (int i = 0; i < 4; i++)
        {
            cv::Point2f point_coordinate(distance_dot[i].x + 0.5, distance_dot[i].y + 0.5);//记录像素中心点;
            // 距离像素点越近的点 实际上和颜色占比应该越小 因此采用 max-实际距离  的方法进行数据预处理
            pi_distance = MaxDistance - sqrt(std::pow(point.x - point_coordinate.x, 2) + std::pow(point.y - (point_coordinate.y), 2));
            distance_List.push_back(pi_distance);
            SumDistance += pi_distance;       // 计算一个总的距离值
        }
        for (int i = 0; i < 4; i++)
        {
            float d = distance_List[i] / SumDistance;  // d是距离比例系数
            window.at<cv::Vec3b>(distance_dot[i].y, distance_dot[i].x)[1] = std::min(255.f, window.at<cv::Vec3b>(distance_dot[i].y, distance_dot[i].x)[1] + 255.f * d);
            //不超过255.f,要进行最小值比较
        }
    }
}

4. 完整代码

#include <chrono>
#include <iostream>
#include <opencv2/opencv.hpp>

std::vector<cv::Point2f> control_points;

void mouse_handler(int event, int x, int y, int flags, void *userdata) 
{
    if (event == cv::EVENT_LBUTTONDOWN) 
    {
        std::cout << "Left button of the mouse is clicked - position (" << x << ", "
        << y << ")" << '\n';
        control_points.emplace_back(x, y);
    }     
}

void naive_bezier(const std::vector<cv::Point2f> &points, cv::Mat &window) 
{
    auto &p_0 = points[0];
    auto &p_1 = points[1];
    auto &p_2 = points[2];
    auto &p_3 = points[3];

    for (double t = 0.0; t <= 1.0; t += 0.001) 
    {
        auto point = std::pow(1 - t, 3) * p_0 + 3 * t * std::pow(1 - t, 2) * p_1 +
                 3 * std::pow(t, 2) * (1 - t) * p_2 + std::pow(t, 3) * p_3;

        window.at<cv::Vec3b>(point.y, point.x)[2] = 255;
    }
}

cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) 
{
    // TODO: Implement de Casteljau's algorithm
    int n = control_points.size();
    if (n == 1) return control_points[0];
    std::vector<cv::Point2f> res_control_points;

    for (int i = 0; i < n - 1; i++) {
        res_control_points.push_back(cv::Point2f(
            (1 - t) * control_points[i].x + t * control_points[i + 1].x,
            (1 - t) * control_points[i].y + t * control_points[i + 1].y));
    }
    return recursive_bezier(res_control_points, t);
}

void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) 
{
    // TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's 
    // recursive Bezier algorithm.
    for (float t = 0.0; t <= 1.0; t = t + 0.001)
    {
        cv::Point2f point = recursive_bezier(control_points, t);  //接收每一个t所求得的点

        //找到该点周围的四个点坐标,先找到一个右上的顶点,然后其他的通过这个点求出
        cv::Point2f point_1 = cv::Point2f(point.x - std::floor(point.x) < 0.5 ? std::floor(point.x) : std::ceil(point.x),   //右上的点
            point.y - std::floor(point.y) < 0.5 ? std::floor(point.y) : std::ceil(point.y));
        cv::Point2f point_2 = cv::Point2f(point_1.x - 1.0, point_1.y);
        cv::Point2f point_3 = cv::Point2f(point_1.x - 1.0, point_1.y - 1.0);
        cv::Point2f point_4 = cv::Point2f(point_1.x, point_1.y - 1.0);

        //用vector容器存储刚刚求得的四个像素点坐标;
        std::vector<cv::Point2f> distance_dot{ point_1,point_2,point_3,point_4 };
        float MaxDistance = sqrt(2.0);
        float SumDistance = 0.0f;
        float pi_distance = 0.0f;
        std::vector<float> distance_List = {};

        window.at<cv::Vec3b>(point.y, point.x)[1] = 255;//Mat.at<存储类型名称>(行,列)[通道]
        for (int i = 0; i < 4; i++)
        {
            cv::Point2f point_coordinate(distance_dot[i].x + 0.5, distance_dot[i].y + 0.5);//记录像素中心点;
            // 距离像素点越近的点 实际上和颜色占比应该越小 因此采用 max-实际距离  的方法进行数据预处理
            pi_distance = MaxDistance - sqrt(std::pow(point.x - point_coordinate.x, 2) + std::pow(point.y - (point_coordinate.y), 2));
            distance_List.push_back(pi_distance);
            SumDistance += pi_distance;       // 计算一个总的距离值
        }
        for (int i = 0; i < 4; i++)
        {
            float d = distance_List[i] / SumDistance;  // d是距离比例系数
            window.at<cv::Vec3b>(distance_dot[i].y, distance_dot[i].x)[1] = std::min(255.f, window.at<cv::Vec3b>(distance_dot[i].y, distance_dot[i].x)[1] + 255.f * d);
            //不超过255.f,要进行最小值比较
        }
    }

}

int main() 
{
    cv::Mat window = cv::Mat(700, 700, CV_8UC3, cv::Scalar(0));
    cv::cvtColor(window, window, cv::COLOR_BGR2RGB);
    cv::namedWindow("Bezier Curve", cv::WINDOW_AUTOSIZE);

    cv::setMouseCallback("Bezier Curve", mouse_handler, nullptr);

    int key = -1;
    while (key != 27) 
    {
        window.setTo(0);
        for (auto &point : control_points) 
        {
            cv::circle(window, point, 3, {255, 255, 255}, 3);
        }

        if (control_points.size() >= 4) 
        {
            naive_bezier(control_points, window);   // 所有点的绘制的贝塞尔曲线用绿色表示
            bezier(control_points, window);   // 前四个点绘制的贝塞尔曲线用红色显示

            cv::imshow("Bezier Curve", window);
            cv::imwrite("my_bezier_curve.png", window);
            key = cv::waitKey(1);

        }

        cv::imshow("Bezier Curve", window);
        key = cv::waitKey(20);
    }

    return 0;
}

5. 参考文献

1. 贝塞尔曲线

2. opencv 处理鼠标事件的方法

3. 贝塞尔曲线简单介绍

4. 解决反走样问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1054630.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进程的状态与转换以及组织方式

1.进程的状态 三种基本状态&#xff1a;运行态&#xff0c;就绪态&#xff0c;阻塞态。 1.运行状态 如果一个进程此时在CPU上运行&#xff0c;那么这个进程处于“运行态”。 CPU会执行该进程对应的程序&#xff08;执行指令序列) 2.就绪状态 当进程创建完成后&#xff0c;…

【论文阅读】(CVPR2023)用于半监督医学图像分割的双向复制粘贴

目录 前言方法BCPMean-teacher and Traning StrategyPre-Training via Copy-PasteBidirectional Copy-Paste ImagesBidirectional Copy-Paste Supervisory Signals Loss FunctionTesting Phase 结论 先看这个图&#xff0c;感觉比较清晰。它整个的思路就是把有标签的图片和无标…

动态规划算法(1)--矩阵连乘

目录 一、动态数组 1、创建动态数组 2、添加元素 3、删除修改元素 4、访问元素 5、返回数组长度 6、for each遍历数组 二、输入多个数字 1、正则表达式 2、has.next()方法 三、矩阵连乘 1、什么是矩阵连乘&#xff1f; 2、动态规划思路 3、手推m和s矩阵 4、完…

AI伦理与机器道德:人工智能的道德挑战

文章目录 什么是AI伦理和机器道德&#xff1f;1. 隐私保护2. 歧视和不平等3. 透明度和解释性4. 安全性5. 社会影响 AI伦理和机器道德的重要性1. 保护个人权利2. 避免不平等和歧视3. 保持透明和责任4. 促进创新 AI伦理挑战和解决方案1. 隐私保护2. 歧视和不平等3. 透明度和解释性…

P1541 [NOIP2010 提高组] 乌龟棋(4维背包问题)

[NOIP2010 提高组] 乌龟棋 题目背景 小明过生日的时候&#xff0c;爸爸送给他一副乌龟棋当作礼物。 题目描述 乌龟棋的棋盘是一行 N N N 个格子&#xff0c;每个格子上一个分数&#xff08;非负整数&#xff09;。棋盘第 1 1 1 格是唯一的起点&#xff0c;第 N N N 格是…

Nginx简介与Docker Compose部署指南

Nginx是一款高性能的开源Web服务器和反向代理服务器&#xff0c;以其卓越的性能、可伸缩性和灵活性而闻名。它在全球范围内广泛用于托管Web应用程序、负载均衡、反向代理和更多场景中。在本文中&#xff0c;我们将首先介绍Nginx的基本概念&#xff0c;然后演示如何使用Docker C…

Apollo自动驾驶系统概述(文末参与活动赠送百度周边)

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

数据结构之美:如何优化搜索和排序算法

文章目录 搜索算法的优化1. 二分搜索2. 哈希表 排序算法的优化1. 快速排序2. 归并排序 总结 &#x1f389;欢迎来到数据结构学习专栏~数据结构之美&#xff1a;如何优化搜索和排序算法 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博客主页&#xff1a;IT陈寒的博客&#x…

阿里云ECS服务器无法发送邮件问题解决方案

这篇文章分享一下自己把项目部署在阿里云ECS上之后&#xff0c;登录邮件提醒时的邮件发送失败问题&#xff0c;无法连接发送邮箱的服务器。 博主使用的springboot提供的发送邮件服务&#xff0c;如下所示&#xff0c;为了实现异步的效果&#xff0c;新开了一个线程来发送邮件。…

【Vim 插件管理器】Vim-plug和Vim-vbundle的区别

- vundle是一款老款的插件管理工具 - vim-plug相对较新&#xff0c;特点是支持异步加载&#xff0c;相比vundle而言 Vim-plug 是一个自由、开源、速度非常快的、极简的 vim 插件管理器。它可以并行地安装或更新插件。你还可以回滚更新。它创建浅层克隆shallow clone最小化磁盘…

【数据结构初阶】六、线性表中的队列(C语言 -- 链式结构实现队列)

相关代码gitee自取&#xff1a; C语言学习日记: 加油努力 (gitee.com) 接上期&#xff1a; 【数据结构初阶】五、线性表中的栈&#xff08;C语言 -- 顺序表实现栈&#xff09;_高高的胖子的博客-CSDN博客 1 . 队列&#xff08;Queue&#xff09; 队列的概念和结构&#xf…

如何利用niceGUI构建一个流式单轮对话界面

官方文档 参考文档 import asyncio import time import requests from fastapi import FastAPI from nicegui import app, uiclass ChatPage:temperature: ui.slider Nonetop_p: ui.slider Noneapi_key: ui.input Nonemodel_name: ui.input Noneprompt: ui.textarea None…

番外5:下载+安装+配置Linux

任务前期工作&#xff1a; 01. 电脑已安装好VMware Workstation软件&#xff1b; 02.提前下载好Rhel-8.iso映像文件&#xff08;文件较大一般在9.4GB&#xff0c;建议采用迅雷下载&#xff09;&#xff0c;本人使用的以下版本&#xff08;地址ed2k://|file|rhel-8.4-x86_64-dvd…

Tomcat启动后的日志输出为乱码

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

实现三栏布局的十种方式

本文节选自我的博客&#xff1a;实现三栏布局的十种方式 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是MilesChen&#xff0c;偏前端的全栈开发者。&#x1f4dd; CSDN主页&#xff1a;爱吃糖的猫&#x1f525;&#x1f4e3; 我的博客&#xff1a;爱吃糖的猫&…

Mysql技术文档--之Mysql联查使用-快速了解联查看我这一篇就够了!国庆开卷!

阿丹&#xff1a; 开头先祝贺大家国庆快乐&#xff01;&#xff01;&#xff01; 在MySQL中&#xff0c;联结&#xff08;JOIN&#xff09;是用于将两个或多个表中的数据根据指定的条件进行关联查询的操作。通过联结&#xff0c;你可以从多个表中检索相关的数据&#xff0c;并…

市场调研的步骤与技巧:助你了解市场需求

在当今快速发展的市场中&#xff0c;进行有效的市场研究对于了解消费者的行为、偏好和趋势至关重要。适当的市场研究可以帮助公司获得对目标受众的有价值的见解&#xff0c;创造更好的产品和服务&#xff0c;并提高客户满意度。今天&#xff0c;小编和大家一起讨论一下怎么做市…

10.1 今日任务:select实现服务器并发

#include <myhead.h>#define ERR_MSG(msg) do{\fprintf(stderr, "__%d__:", __LINE__); \perror(msg);\ }while(0)#define PORT 8888 //端口号&#xff0c;范围1024~49151 #define IP "192.168.112.115" //本机IP&#xff0c;ifco…

[Linux] 4.常用初级指令

pwd&#xff1a;显示当前文件路径 ls:列出当前文件夹下有哪些文件 mkdir空格文件名&#xff1a;创建一个新的文件夹 cd空格文件夹名&#xff1a;进入文件夹 cd..&#xff1a;退到上一层文件夹 ls -a&#xff1a;把所有文件夹列出来 .代表当前文件夹 ..代表上层文件夹 用…

第 365 场 LeetCode 周赛题解

A 有序三元组中的最大值 I 参考 B B B 题做法… class Solution { public:using ll long long;long long maximumTripletValue(vector<int> &nums) {int n nums.size();vector<int> suf(n);partial_sum(nums.rbegin(), nums.rend(), suf.rbegin(), [](int x…