1. 绘制五角星

1.1 绘制分析
  1. 国旗的长宽比是3:2,假如长600px,高就是600*2/3=400px;
  2. 将旗面划分为4个等分长方形,每个长方形的尺寸(300*200);
  3. 将左上方长方形划分长宽15×10个方格,每个方格的尺寸(20*20),假如l=20px;
  4. opencv 以图片左上角为原点(0,0),向下为y的正轴,向右为x的正轴;
  5. 大五角星的中心位于该长方形(5l,5l),左上角位于(2l,2l);
  6. 大五角星外接圆的直径为6l,半径为3l;
  7. 四颗小五角星的中心点;
  8. 第一颗中心点位于(10l,2l),左上角位于(9l,l);
  9. 第二颗中心点位于(12l,4l),左上角位于(11l,3l);
  10. 第三颗中心点位于(12l,7l),左上角位于(11l,6l);
  11. 第四颗中心点位于(10l,9l),左上角位于(9l,8l);
  12. 每颗小五角星外接圆的直径均为2l,半径为l;
  13. 四颗小五角星均有一角尖正对大五角星的中心点;
  14. 国旗RGB16进制码:红色:#de2910;金色:#ffde00

2. 绘制步骤

  1. 绘制整体红色的背景图,设置图像的尺寸;
  2. 根据【1.2 绘制分析】的1,2,3步骤,绘制辅助线;
  3. 根据五角星的中心点和左上角的坐标,绘制五个五角星;
  4. 根据四个小五角星和大五角星的中心,计算四个小五角星的旋转角度;
  5. 将绘制的五角星根据OpenCV-Python学习(5)—— OpenCV 图像像素的读写操作,写入第一步绘制的红色图像中,此处使用坐标是五角星的左上角坐标。

3. 创建红色背景

3.1 实现代码
  1. 根据宽高,创建纯黑色尺寸的图像;
  2. 将图像的颜色修改为红色;
  3. 返回创建的国旗背景图像。
# 创建旗帜
def create_flag(width, height):
  img = np.zeros((height,width,3),dtype=np.uint8)
  img[::] = get_hex_to_bgr("#de2910")
  return img

4. 辅助线绘制

4.1 实现代码
  1. 根据【1.2 绘制分析】的1,2,3步骤,绘制辅助线;
  2. 将背景图像进行四等分;
  3. 将左上角长方形分割成15*10的方格;
  4. 根据五个五角星的中心点坐标,连线最后好计算旋转角度。
# 绘制辅助线
def draw_line(flag,width,height,half_width,half_height,l):
  color = (0,0,0)
  lineType = cv.LINE_8
  # 四等分
  cv.line(flag, (half_width,0), (half_width,height), color,lineType=lineType)
  cv.line(flag, (0,half_height), (width,half_height), color,lineType=lineType)
  # 四分之一位置辅助线
  for i in range(1,int(half_width / l)):
    cv.line(flag, (i * l,0), (i * l,half_height), color,lineType=lineType)
  for i in range(1,int(half_height / l)):
    cv.line(flag, (0,i * l), (half_width,i * l), color,lineType=lineType)
  # 五角星圆心链接
  o = (5*l,5*l)
  others = [(10*l,2*l),(12*l,4*l),(12*l,7*l),(10*l,9*l)]
  for point in others:
    cv.line(flag, o, point, color,lineType=lineType)
  return flag

5. 五角星绘制

5.1 五角星各个坐标点计算
# 以五角星的重心为原点,计算各点坐标
def get_star_point(r = 100):
  # 计算没一份的度数和内五边形的r
  pi_val = np.pi / 180
  min_r = r * np.sin(18 * pi_val) / np.cos(36 * pi_val)
  # 外五边形的坐标
  a = [0,r]
  b = [r * np.cos(18 * pi_val), r * np.sin(18 * pi_val)]
  c = [r * np.cos(54 * pi_val), - r * np.sin(54 * pi_val)]
  d = [- r * np.cos(54 * pi_val), - r * np.sin(54 * pi_val)]
  e = [- r * np.cos(18 * pi_val), r * np.sin(18 * pi_val)]
  # 内五边形的坐标
  in_a = [min_r * np.cos(54 * pi_val),min_r * np.sin(54 * pi_val)]
  in_b = [min_r * np.cos(18 * pi_val),- min_r * np.sin(18 * pi_val)]
  in_c = [0, - min_r]
  in_d = [- min_r * np.cos(18 * pi_val),- min_r * np.sin(18 * pi_val)]
  in_e = [- min_r * np.cos(54 * pi_val),min_r * np.sin(54 * pi_val)]
  return {
    "out": [a, b, c, d, e],
    "in": [in_a, in_b, in_c, in_d, in_e]
  }
5.2 坐标系坐标转换

将坐标系中五角星坐标点转换为opencv绘制中坐标点!

# 将坐标系中五角星坐标点转换为opencv绘制中坐标点
def handle_star_point(points = []):
  return list(map(lambda items: [int(items[0]),-int(items[1])], points))
5.3 绘制五角星
  1. 根据五角星半径创建长宽为2r,颜色为#de2910的背景图;
  2. 根据半径获取五角星的各个点的坐标;
  3. 将各个点的坐标进行opencv的坐标转换;
  4. 使用多边形图形绘制方法,绘制五角星;
  5. 注意: 五角星的圆心重新设置
# 创建五角星
def create_star(r):
  img = np.zeros((r * 2,r * 2,3),dtype=np.uint8)
  img[::] = get_hex_to_bgr("#de2910")
  points = get_star_point(r)
  [a,b,c,d,e] = handle_star_point(points.get("out"))
  [in_a,in_b,in_c,in_d,in_e] = handle_star_point(points.get("in"))
  # 设置多边形点
  pts = np.array([a,in_a,in_a,b,b,in_b,in_b,c,c,in_c,in_c,d,d,in_d,in_d,e,e,in_e,in_e,a])
  # 由于图片宽度 r * 2,设置原点(r,r)
  pts[:,:] += r
  cv.polylines(img, [pts], False, get_hex_to_bgr("#ffde00"),1,lineType=cv.LINE_AA)
  cv.fillPoly(img,[pts],get_hex_to_bgr("#ffde00"),lineType=cv.LINE_AA)
  return img
5.4 将绘制的五角星写入开始创建的背景中
  1. 通过传入国旗长的尺寸,计算各个辅助点的基础值;
  2. 创建红色旗帜;
  3. 绘制辅助线;
  4. 创建最大的五角星;
  5. 大五角星左上角坐标(2L,2L);
  6. 小五角星从上到下依次绘制;
  7. 小五角星一左上角坐标(9L,L);
  8. 小五角星二左上角坐标(11L,3L);
  9. 小五角星三左上角坐标(11L,6L);
  10. 小五角星四左上角坐标(9L,8L)。
def draw_flag(size = 600):
  # 计算辅助坐标点
  width, height,half_width,half_height,l = withdata_base_val(size)
  # 创建旗帜
  flag = create_flag(width,height)
  # 创建最大的五角星
  star_max = create_star(3*l)
  # 大五角星左上角坐标(2L,2L)
  flag[2*l:8*l,2*l:8*l] = star_max
  # 创建小五角星
  star_min = create_star(l)
  # 小五角星从上到下依次绘制
  # 小五角星一左上角坐标(9L,L)
  flag[l:3*l,9*l:11*l] = star_min
  # 小五角星二左上角坐标(11L,3L)
  flag[3*l:5*l,11*l:13*l] = star_min
  # 小五角星三左上角坐标(11L,6L)
  # flag[6*l:8*l,11*l:13*l] = star_min
  # 小五角星四左上角坐标(9L,8L)
  flag[8*l:10*l,9*l:11*l] = star_min

  # 绘制辅助线
  flag = draw_line(flag,width,height,half_width,half_height,l)

  cv.imshow("flag", flag)
  cv.waitKey(0)
  cv.destroyAllWindows()

6. 计算四个小五角星的旋转角度

  1. 大五角星的中心点(5l,5l);
  2. 四个小五角星的中心点(10l,2l),(12l,4l),(12l,7l),(10l,9l);
  3. 将四个小五角星的最上边一个角旋转到中心点的连线上;
  4. 旋转角度 np.arctan((5l-2l)/(10l-5l))计算的角度加90度,注意此处是np.arctan返回的弧度,需要通过np.degrees转为度第一个五角星;90 + np.degrees(np.arctan(3/5));
  5. 第二个五角星:90 + np.degrees(np.arctan(1/7));
  6. 第三个五角星:90 - np.degrees(np.arctan(2/7));
  7. 第四个五角星:90 - np.degrees(np.arctan(4/5))。
6.1 代码实现
def draw_flag(size = 600):
  # 计算辅助坐标点
  width, height,half_width,half_height,l = withdata_base_val(size)
  # 创建旗帜
  flag = create_flag(width,height)
  # 创建最大的五角星
  star_max = create_star(3*l)
  # 大五角星左上角坐标(2L,2L)
  flag[2*l:8*l,2*l:8*l] = star_max
  # 创建小五角星
  star_min = create_star(l)
  # 小五角星从上到下依次绘制
  # 计算旋转角度
  [angle1,angle2,angle3,angle4] = np.degrees(np.arctan([3/5,1/7,2/7,4/5]))
  # 小五角星一左上角坐标(9L,L)
  flag[l:3*l,9*l:11*l] = star_rotate(star_min,90 + angle1)
  # 小五角星二左上角坐标(11L,3L)
  flag[3*l:5*l,11*l:13*l] = star_rotate(star_min,90 + angle2)
  # 小五角星三左上角坐标(11L,6L)
  flag[6*l:8*l,11*l:13*l] = star_rotate(star_min,90 - angle3)
  # 小五角星四左上角坐标(9L,8L)
  flag[8*l:10*l,9*l:11*l] = star_rotate(star_min,90 - angle4)

  # 绘制辅助线
  flag = draw_line(flag,width,height,half_width,half_height,l)

  cv.imshow("flag", flag)
  cv.waitKey(0)
  cv.destroyAllWindows()

7. 完整代码

import sys
import numpy as np
import cv2 as cv

# 以五角星的重心为原点,计算各点坐标
def get_star_point(r = 100):
  # 计算没一份的度数和内五边形的r
  pi_val = np.pi / 180
  min_r = r * np.sin(18 * pi_val) / np.cos(36 * pi_val)
  # 外五边形的坐标
  a = [0,r]
  b = [r * np.cos(18 * pi_val), r * np.sin(18 * pi_val)]
  c = [r * np.cos(54 * pi_val), - r * np.sin(54 * pi_val)]
  d = [- r * np.cos(54 * pi_val), - r * np.sin(54 * pi_val)]
  e = [- r * np.cos(18 * pi_val), r * np.sin(18 * pi_val)]
  # 内五边形的坐标
  in_a = [min_r * np.cos(54 * pi_val),min_r * np.sin(54 * pi_val)]
  in_b = [min_r * np.cos(18 * pi_val),- min_r * np.sin(18 * pi_val)]
  in_c = [0, - min_r]
  in_d = [- min_r * np.cos(18 * pi_val),- min_r * np.sin(18 * pi_val)]
  in_e = [- min_r * np.cos(54 * pi_val),min_r * np.sin(54 * pi_val)]
  return {
    "out": [a, b, c, d, e],
    "in": [in_a, in_b, in_c, in_d, in_e]
  }

# 将坐标系中五角星坐标点转换为opencv绘制中坐标点
def handle_star_point(points = []):
  return list(map(lambda items: [int(items[0]),-int(items[1])], points))

# 将16进制颜色转成opencv可以使用BGR颜色值
def get_hex_to_bgr(hex):
  hex = hex[1:]
  r = int(hex[0:2],16)
  g = int(hex[2:4],16)
  b = int(hex[4:6], 16)
  bgr = (b,g,r)
  return bgr

# 计算基础值
def withdata_base_val(width):
  width = width
  # 由于宽高比是3:2,所以计算高度
  height = width * 2 / 3
  # 五角星所占用区域是左上角的四分之一位置,计算四分之一位置的宽高
  half_width = width / 2
  half_height = height / 2
  # 绘制四分之一网格宽15等分,高10等分
  l = half_width / 15
  return list(map(lambda val: int(val),[width,height,half_width,half_height,l]))

# 创建五角星
def create_star(r):
  img = np.zeros((r * 2,r * 2,3),dtype=np.uint8)
  img[::] = get_hex_to_bgr("#de2910")
  points = get_star_point(r)
  [a,b,c,d,e] = handle_star_point(points.get("out"))
  [in_a,in_b,in_c,in_d,in_e] = handle_star_point(points.get("in"))
  # 设置多边形点
  pts = np.array([a,in_a,in_a,b,b,in_b,in_b,c,c,in_c,in_c,d,d,in_d,in_d,e,e,in_e,in_e,a])
  # 由于图片宽度 r * 2,设置原点(r,r)
  pts[:,:] += r
  cv.polylines(img, [pts], False, get_hex_to_bgr("#ffde00"),1,lineType=cv.LINE_AA)
  cv.fillPoly(img,[pts],get_hex_to_bgr("#ffde00"),lineType=cv.LINE_AA)
  return img

# 创建旗帜
def create_flag(width, height):
  img = np.zeros((height,width,3),dtype=np.uint8)
  img[::] = get_hex_to_bgr("#de2910")
  return img

# 绘制辅助线
def draw_line(flag,width,height,half_width,half_height,l):
  color = (0,0,0)
  lineType = cv.LINE_8
  # 四等分
  cv.line(flag, (half_width,0), (half_width,height), color,lineType=lineType)
  cv.line(flag, (0,half_height), (width,half_height), color,lineType=lineType)
  # 四分之一位置辅助线
  for i in range(1,int(half_width / l)):
    cv.line(flag, (i * l,0), (i * l,half_height), color,lineType=lineType)
  for i in range(1,int(half_height / l)):
    cv.line(flag, (0,i * l), (half_width,i * l), color,lineType=lineType)
  # 五角星圆心链接
  o = (5*l,5*l)
  others = [(10*l,2*l),(12*l,4*l),(12*l,7*l),(10*l,9*l)]
  for point in others:
    cv.line(flag, o, point, color,lineType=lineType)
  return flag

def star_rotate(star,deg):
  # 图片的高度和宽度
  height,width = star.shape[:2]
  # 以图像中心作为旋转中心
  x, y = width//2, height//2  
  mar = cv.getRotationMatrix2D((x,y), deg, 1.0)
  img = cv.warpAffine(star, mar, (width, height), borderValue=get_hex_to_bgr("#de2910"))
  return img

def draw_flag(size = 600):
  # 计算辅助坐标点
  width, height,half_width,half_height,l = withdata_base_val(size)
  # 创建旗帜
  flag = create_flag(width,height)
  # 创建最大的五角星
  star_max = create_star(3*l)
  # 大五角星左上角坐标(2L,2L)
  flag[2*l:8*l,2*l:8*l] = star_max
  # 创建小五角星
  star_min = create_star(l)
  # 小五角星从上到下依次绘制
  # 计算旋转角度
  [angle1,angle2,angle3,angle4] = np.degrees(np.arctan([3/5,1/7,2/7,4/5]))
  # 小五角星一左上角坐标(9L,L)
  flag[l:3*l,9*l:11*l] = star_rotate(star_min,90 + angle1)
  # 小五角星二左上角坐标(11L,3L)
  flag[3*l:5*l,11*l:13*l] = star_rotate(star_min,90 + angle2)
  # 小五角星三左上角坐标(11L,6L)
  flag[6*l:8*l,11*l:13*l] = star_rotate(star_min,90 - angle3)
  # 小五角星四左上角坐标(9L,8L)
  flag[8*l:10*l,9*l:11*l] = star_rotate(star_min,90 - angle4)

  # 绘制辅助线
  flag = draw_line(flag,width,height,half_width,half_height,l)

  cv.imshow("flag", flag)
  cv.waitKey(0)
  cv.destroyAllWindows()

if __name__ == "__main__":
  val = sys.argv[1] if len(sys.argv) > 1 else 600
  size = int(val)
  draw_flag(size)

8. 总结

  1. 注意:五角星坐标点计算点的坐标系和opencv的坐标系不同,需要进行转换;
  2. 注意:五角星绘制的时候需要对坐标的中心点进行移动到中心;
  3. 旋转角度的计算时,需要注意np.arctan返回的是弧度,需要使用np.degrees转换为度;
  4. 国旗绘制完成,去掉辅助线函数,得到一个完整的国旗图像;
  5. 可以监听cv.waitKey(0) == ord(‘s’),进行图像的保存, cv.imwrite()。