type
Post
status
Published
date
Nov 24, 2022
slug
image_process_1
summary
图像是由各种各样的图形组成的,而图形的组成部分可以是汽车和街道、云朵和天空、原子和分子等,本章将要介绍基本图形的绘制方法,并将绘制方法通过Python代码实现。
tags
图像处理
Python
OpenCV
category
图像处理
icon
password
Property
Nov 24, 2022 12:24 PM
图像是由各种各样的图形组成的,而图形的组成部分可以是汽车和街道、云朵和天空、原子和分子等,本章将要介绍基本图形的绘制方法。

点的绘制

在描述点的几何要素时,只需要在坐标系中指定一个位置,这个世界坐标可以用向量来表示,即将指定坐标所在的像素标记为所绘制的点即可。

代码实现

def drawPoint(canvas,x,y): canvas[y,x] = 0

线的绘制

一个场景中的直线段由其两端的端点坐标来定义,根据直线段的几何特征可以使用笛卡尔坐标系中的截距方程来表示:
$$ y=m\cdot x+b $$
其中,$m$表示直线的斜率,$b$是y轴的截距。当给定线段的两端点$(x_0, y_0)$和$(x_p, y_p)$时,可以很容易地计算出斜率$m$和y轴截距$b$的值:
$$ m = \frac{y_p - y_0}{x_p - x_0} $$
$$ b = y_0 - m\cdot x_0 $$

Bresenham画线算法

  • a) 输入线段的两端点,并将左端点存储在$(x_0,y_0)$中;
  • b) 将 $(x_0,y_0)$ 装入帧缓存,画出第一个点;
  • c) 计算常量$\Delta x$、$\Delta y$、$2\Delta y$和$2\Delta y – 2\Delta x$,并得到决策参数的第一个值:
$$ p_0 = 2\Delta y - \Delta x $$
  • d) 从$k=0$开始,在沿线路径的每个$x_k$处,进行检测:
如果$p_k<0$,下一个要绘制的点是$(x_{k}+1,y_{k})$,并且
$$ p_{k+1} = p_k + 2\Delta y $$
否则,下一个要绘制的点是$(x_{k}+1,y_{k}+1)$,并且
$$ p_{k+1} = p_k + 2\Delta y - 2\Delta x $$
  • e) 重复步骤4,共$\Delta x-1$次。

代码实现

def drawLine(canvas,x1,y1,x2,y2): dx, dy = abs(x2 - x1), abs(y2 - y1) xi, yi = x1, y1 sx, sy = 1 if (x2 - x1) > 0 else -1, 1 if (y2 - y1) > 0 else -1 pi = 2*dy - dx while xi != x2 + 1: if pi < 0: pi += 2 * dy else: pi += 2 * dy - 2 * dx yi += 1 * sy drawPoint(canvas,xi,yi) xi += 1 * sx

园的绘制

圆是图形中最基本的图形元素,将圆的几何特征定义为所有距中心$(x_c, y_c)$为给定r的点的集合。对于任意圆点$(x, y)$用笛卡尔坐标系中勾股定理定义为
$$ (x-x_c)^2+(y-y_c)^2=r^2 $$
通过上述方程可以计算椭圆在x轴方向跨度上以单位步长计算对应的y值,得到圆周轨迹上每个点的位置:
$$ y=y_c\pm\sqrt{r^2-(x_c-x)^2} $$
但是这并不是一个好方法,这种方法在每计算一个点都会消耗很大的计算资源,而且画出来的像素位置间距中间密集两边稀疏。解决这个问题可以使用极坐标$r$和$\theta$方式计算,可以得到方程组:
$$ x=x_c+r\cos\theta\\ y = y_c+r\sin\theta $$

中点画圆法

  • a) 输入圆半径r和圆心$(x_c, y_c)$,并得到圆周(圆心在原点)上的第一个点:
$$ (x_0,y_0)=(0,r) $$
  • b) 计算决策参数的初始值:
$$ p_0=\frac{5}{4}-r $$
  • c) 在每个$x_k$位置,从$k=0$开始,完成下列测试:假如$p_k<0$,圆心在$(0, 0)$的圆的下一个点为$(x_{k}+1, y_k)$,并且
$$ p_{k+1}=p_k+2x_{k+1}+1 $$
否则,圆的下一个点是$(x_{k}+1, y_{k}-1)$,并且
$$ p_{k+1} = p_k+2x_{k+1}+1-2y_{k+1} $$
其中$2x_{k+1}=2x_k+2$且$2y_{k+1}=2y_k-2$。
  • d) 确定在其他七个八分圆中的对称点。
  • e) 将每个计算出的像素位置$(x, y)$移动到圆心在$(x_c, y_c)$的圆路径上,并画坐标值:
$$ x=x+x_c,y=y+y_c $$
  • f) 重复步骤3到步骤5,直至$x\geqslant y$。

代码实现

def drawCircle(canvas,x,y,r): x0, y0 = x, y xi = 0 yi = r pi = 5/4 - r while xi != yi+1: if pi < 0: pi += 2 * (xi + 1) + 1 else: pi += 2 * (xi + 1) + 1 - 2 * (yi - 1) yi -= 1 drawPoint(canvas,xi+x0,yi+y0) drawPoint(canvas,-xi+x0,yi+y0) drawPoint(canvas,xi+x0,-yi+y0) drawPoint(canvas,-xi+x0,-yi+y0) xi += 1 xi = r yi = 0 pi = 5/4 - r while not (xi == yi+1 or xi == yi): if pi < 0: pi += 2 * (yi + 1) + 1 else: pi += 2 * (yi + 1) + 1 - 2 * (xi - 1) xi -= 1 drawPoint(canvas,xi+x0,yi+y0) drawPoint(canvas,-xi+x0,yi+y0) drawPoint(canvas,xi+x0,-yi+y0) drawPoint(canvas,-xi+x0,-yi+y0) yi += 1

椭圆的绘制

椭圆上任意一点到两焦点的距离之和为一个常数,如果椭圆上任意一点p=(x, y)到两焦点的距离为d1和d2,那么椭圆的通用方程可以表示为
$$ d_1+d_2=常数 $$
用焦点坐标$f_1=(x_1,y_1)$和$f_2=(x_2,y_2)$来表示距离$d_1$和$d_2$,可以得到
$$ \sqrt{(x-x_1)^2+(y-y_1)^2}+\sqrt{(x-x_2)^2+(y-y_2)^2}=常数 $$
对方程求平方根,可以重写为通用椭圆方程:
$$ Ax^2+By^2+Cxy+Dx+Ey+F=0 $$
或者使用极坐标的方式表示:
$$ x=x_c+r_x\cos\theta\\ y=y_c+r_y\sin\theta $$

中点椭圆画法

  • a) 输入长轴rx、短轴ry和椭圆中心$(x_c, y_c)$,并得到第一个点:
$$ (x_0,y_0)=(0,r_y) $$
  • b) 计算区域1中决策参数的初始值:
$$ p1_0=r_y^2-r^2_xr_y+\frac{1}{4}r^2_x $$
  • c) 在区域1中的每个$x_k$位置,从$k=0$开始,完成下列测试:假如$p1_k<0$,沿中心在$(0,0)$的椭圆的下一个点$(x_{k}+1,y_k)$,并且
$$ p1_{k+1}=p1_k+2r^2_yx_{k+1}+r^2_y $$
否则,沿椭圆的下一个点为$(x_{k}+1,y_{k}-1)$,并且
$$ p1_{k+1}=p1_k+2r^2_yx_{k+1}-2r^2_xy_{k+1}+r^2_y $$
其中
$$ 2r^2_yx_{k+1}=2r^2_yx_k+2r^2_y,2r^2_xy_{k+1}=2r^2_xy_k-2r^2_x $$
并且直到$2r^2_yx\geqslant 2r^2_xy$
  • d) 使用区域1中计算的最后点$(x_0,y_0)$来计算区域2中参数的初始值:
$$ p2_0=r^2_y(x_0+\frac{1}{2})^2+r^2_x(y_0-1)^2-r_x^2r_y^2 $$
  • e) 在区域2的每个$y_k$位置处,从$k=0$开始,完成下列条件判断:如果$p2_k>0$,沿中心为$(0,0)$的椭圆的下一个点为$(x_k,y_{k}-1)$,并且
$$ p2_{k+1}=p2_k-2r_x^2y_{k+1}+r^2_x $$
否则,下一个点为$(x_{k}+1,y_{k}-1)$,并且
$$ p2_{k+1}=p2_k+2r^2_yx_{k+1}-2r_x^2y_{k+1}+r^2_x $$
使用与公式1中想用的x和y增量进行计算,知道$y=0$。
  • f) 确定其他三个象限中的对称点。
  • g) 将计算出的每个像素位置$(x, y)$移到中心在$(x_c, y_c)$的椭圆轨迹上,并按坐标值绘制点:
$$ x=x+x_c,y=y+y_c $$

代码实现

def drawEllipse(canvas,x,y,rx,ry): x0, y0 = x, y xi, yi = 0, ry rx2 = rx ** 2 ry2 = ry ** 2 p1i = ry2 - rx2 * ry + rx2 / 4 while 2*ry2*xi < 2*rx2*yi: print(p1i) if p1i < 0: p1i += 2 * ry2 * (xi + 1) + ry2 else: p1i += 2 * ry2 * (xi + 1) - 2* rx2 * (yi - 1) + ry2 yi -= 1 drawPoint(canvas,xi+x0,yi+y0) drawPoint(canvas,-xi+x0,yi+y0) drawPoint(canvas,xi+x0,-yi+y0) drawPoint(canvas,-xi+x0,-yi+y0) xi += 1 xi -= 1 p2i = ry2 * (xi + .5) ** 2 + rx2 * (yi - 1) ** 2 - rx2 * ry2 print(p2i,ry2 * (xi + .5) ** 2 , rx2 * (yi - 1) ** 2 , rx2 * ry2) while yi >= 0: if p2i > 0: p2i += -2 * rx2 * (yi - 1) + rx2 else: p2i += 2 * ry2 * (xi + 1) - 2 * rx2 * (yi - 1) + rx2 xi += 1 drawPoint(canvas,xi+x0,yi+y0) drawPoint(canvas,-xi+x0,yi+y0) drawPoint(canvas,xi+x0,-yi+y0) drawPoint(canvas,-xi+x0,-yi+y0) yi -= 1

参考文献

  • 徐文鹏. 计算机图形学基础(OpenGL版)[M]. 清华大学出版社, 2014.
【Ray Tracing】光线追踪——光线的定义Python图像处理专题——直方图的绘制