初衷

今天有同学问我:怎么在灰度图上画一个圆?
我想:OpenCV不是直接有画圆的函数吗?哦,他是MATLAB。
我搜了一下好像没有内置的(没仔细找,可能没看到),别人手工写的算法都是遍历整副图像才可以画完的,这显然非常不优雅。
我之前有做过墨水屏时钟,需要在EPS32上使用SPI总线向墨水屏输出图像,众所周知,嵌入式设备的计算能力非常有限,我之前使用的库使用的实现方法比较取巧,他遍历360度,但步长是1度,遍历一圈画360个点,这样的办法在低分辨率的屏幕上完全没有问题,但如果要求画一个大圆、或者屏幕分辨率非常高,则会出现圆不连续的情况;前面说的遍历图像的方法中涉及到平方运算,即每个像素点计算一次判断其是否在圆上,这其中的平方计算需要两次乘法加一次加法,如若在整副图像上实施平方运算会非常消耗计算资源。以上两种实现都不尽人意,因此我想要借着这个机会做一个更加高效优雅的画圆方法。
下面是本实现的程序运行结果。

下面直接贴代码,没有注释,自行领悟。

代码(MATLAB)

代码分为四个文件,mainDrawing.mimgPaint.mdrawLine.mdrawCircle.m
其中,mainDrawing.m为主程序,主要内容为初始化图像矩阵、调用画圆、画线函数生成圆、线途经点,使用绘制函数绘制指定宽度的线条到图像上。
imgPaint.m为绘图函数,输入参数为图像矩阵、线条点、线条宽度,输出为绘制后图像。
drawLine.m为绘制直线函数,输入为起始X坐标、起始Y坐标、结束X坐标、结束Y坐标,输出为线条途经点。
drawCircle.m为绘制圆函数,输入为圆心X坐标、圆心Y坐标、圆半径长度(整数),输出为圆的线条途经点。

请注意,X轴对应图像第一维坐标,即行坐标,Y轴对应图像第二维,即列坐标,所有命名规则均按此执行。

mainDrawing.m

%% draw a circle in one iteration
clc;close all;clearvars;
%% parameter
picSize = 10000;
picXRes = picSize;
picYRes = picSize;
width = 10;
%% variable
picMat = zeros([picXRes,picYRes]);   % X为第一维宽度,Y为第二维宽度
%% draw and paint
tic;
circRadius = 2500;
circCenterX = 2501;
circCenterY = 2501;
circPts = drawCircle(circCenterX,circCenterY,circRadius);
picMat = imgPaint(picMat,circPts,width);
timeDraw = toc;
disp(strcat('画一个圆用时',num2str(timeDraw),'秒'));circRadius = 2500;
circCenterX = 7499;
circCenterY = 2501;
circPts = drawCircle(circCenterX,circCenterY,circRadius);
picMat = imgPaint(picMat,circPts,width);circRadius = 2500;
circCenterX = 2501;
circCenterY = 7499;
circPts = drawCircle(circCenterX,circCenterY,circRadius);
picMat = imgPaint(picMat,circPts,width);circRadius = 2500;
circCenterX = 7499;
circCenterY = 7499;
circPts = drawCircle(circCenterX,circCenterY,circRadius);
picMat = imgPaint(picMat,circPts,width);circRadius = round((sqrt(2)/2*10000-10000/2)/2)-2;
circCenterX = 5000;
circCenterY = 5000;
circPts = drawCircle(circCenterX,circCenterY,circRadius);
picMat = imgPaint(picMat,circPts,width);linePts = drawLine(1,1,10000,10000);
picMat = imgPaint(picMat,linePts,width);linePts = drawLine(1,10000,10000,1);
picMat = imgPaint(picMat,linePts,width);linePts = drawLine(5000,1,5000,10000);
picMat = imgPaint(picMat,linePts,width);linePts = drawLine(1,5000,10000,5000);
picMat = imgPaint(picMat,linePts,width);
%% plot
image(picMat);
axis('equal');
colormap('gray');
imwrite(picMat,'circle.png');

imgPaint.m

function img = imgPaint(img,drawPts,width)
% 绘图函数,根据输入有像素坐标列表drawPts在img上画点if nargin < 3seriesLen = size(drawPts,1);for ptIdx = 1:seriesLenimg(drawPts(ptIdx,1),drawPts(ptIdx,2)) = 255;endelseseriesLen = size(drawPts,1);for ptIdx = 1:seriesLenimg = pixelPaint(img,drawPts(ptIdx,1),drawPts(ptIdx,2),width);endend
endfunction img = pixelPaint(img,centerX,centerY,width)imgSizeX = size(img,1);imgSizeY = size(img,2);if (centerX > width)startX = centerX - width;elsestartX = centerX;endif (centerY > width)startY = centerY - width;elsestartY = centerY;endendX = centerX + width;endY = centerY + width;if (endX >= imgSizeX)endX = imgSizeX;endif (endY >= imgSizeY)endY = imgSizeY;endfor idxX = startX:endXfor idxY = startY:endYimg(idxX,idxY) = 255;endend
end

drawLine.m

function linePts = drawLine(startX,startY,endX,endY)
% 画线函数,输入起始坐标终止坐标,输出直线经过像素坐标列表,
% 第一维对应第几个点,第二维两个数字分别对应X、Y像素索引
%% data verifystartX = round(startX);startY = round(startY);endX = round(endX);endY = round(endY);
%% calculationlinePts = [];deltaX = abs(endX - startX);deltaY = abs(endY - startY);if (deltaX >= deltaY)sepDelta = 1;if startX > endXsepDelta = -1;endfor ptIdx = startX:sepDelta:endXlinePts = cat(1,linePts,[ptIdx,...(round((ptIdx - startX) * (endY - startY) / (endX - startX)) + startY)]);endelsesepDelta = 1;if startY > endYsepDelta = -1;endfor ptIdx = startY:sepDelta:endYlinePts = cat(1,linePts,...[(round((ptIdx - startY) * (endX - startX) / (endY - startY)) + startX),...ptIdx]);endend
end

drawCircle.m

function circPts = drawCircle(circCenterX,circCenterY,circRadius)
% 画圆函数,输入圆心X、Y坐标,和圆半径,输出圆经过的坐标
%% data verify
circCenterX = round(circCenterX);
circCenterY = round(circCenterY);
circRadius = round(circRadius);
%% variables
toDrawPtsQuarter = [];
circPts = [];
startPtX = circCenterX;
endPtX = circCenterX + circRadius;
xSeries = startPtX:1:endPtX;
ySeries = round(sqrt(circRadius.^2 - (xSeries - circCenterX).^2) + circCenterY);
seriesLen = length(xSeries);
%% calculation
for ptIdx = 1:seriesLentoDrawPtsQuarter = cat(1,toDrawPtsQuarter,[xSeries(1,ptIdx),ySeries(1,ptIdx)]);if (ptIdx <= (seriesLen - 1))isBeside = ifBeside(xSeries(1,ptIdx),ySeries(1,ptIdx),...xSeries(1,ptIdx+1),ySeries(1,ptIdx+1));if ~isBesidelinePts = drawLine(xSeries(1,ptIdx),ySeries(1,ptIdx),...xSeries(1,ptIdx+1),ySeries(1,ptIdx+1));toDrawPtsQuarter = cat(1,toDrawPtsQuarter,linePts);endend
end
%% draw the whole circle
toDrawPtsQuarter1 = zeros(size(toDrawPtsQuarter));
toDrawPtsQuarter1(:,1) = toDrawPtsQuarter(:,1);
toDrawPtsQuarter1(:,2) = -1 * toDrawPtsQuarter(:,2) + 2 * circCenterY;toDrawPtsQuarter2 = zeros(size(toDrawPtsQuarter));
toDrawPtsQuarter2(:,2) = toDrawPtsQuarter(:,2);
toDrawPtsQuarter2(:,1) = -1 * toDrawPtsQuarter(:,1) + 2 * circCenterX;toDrawPtsQuarter3 = zeros(size(toDrawPtsQuarter));
toDrawPtsQuarter3(:,1) = -1 * toDrawPtsQuarter(:,1) + 2 * circCenterX;
toDrawPtsQuarter3(:,2) = -1 * toDrawPtsQuarter(:,2) + 2 * circCenterY;circPts = cat(1,circPts,toDrawPtsQuarter);
circPts = cat(1,circPts,toDrawPtsQuarter1);
circPts = cat(1,circPts,toDrawPtsQuarter2);
circPts = cat(1,circPts,toDrawPtsQuarter3);
end
function isBeside = ifBeside(ptX1,ptY1,ptX2,ptY2)isBeside = true;if abs(ptX1 - ptX2) <= 1isBeside = isBeside && true;elseisBeside = isBeside && false;endif ((abs(ptY1 - ptY2) <= 1) && isBeside)isBeside = isBeside && true;elseisBeside = isBeside && false;end
end

性能

本程序以优化性能为目的,因此计算了一下画一个圆的时间,为0.21541秒,圆的线宽为10,半径为2500,大家可以试试有没有比我性能更好的,和我分享一下。

后话

本实现中调用了一些MATLAB中特有的实现,且其中的矩阵为便于编写采用了直接拼接的方法,这在C/C++中实现非常困难,另外MATLAB语法中索引的方式与C/C++不同,如果后续真的在嵌入式设备上实现,还需要针对性修改一下代码的部分内容。

一种简单高效的灰度图画圆方法——使用MATLAB实现每个像素点只计算一次就把圆画出来相关推荐

  1. 两种简单高效的“反人脸识别”方法

    Bakunov开始在工作时间,研究如何防止被人脸识别.日前,他公开表示自己已经与其他几个黑客一起,开发出了一种"反人脸识别算法". Grigory Bakunov是俄罗斯搜索巨头Y ...

  2. 一种简单快速有效的低照度图像增强方法

    一种简单快速有效的低照度图像增强方法 一.本文介绍的是一种比较实用并且去阴影效果很好的方法,选自2004年Tao的一篇论文,名称是<An Integrated Neighborhood Depe ...

  3. 一种简单的蒙特卡洛树搜索并行化方法

    监控未观察样本: 一种简单的蒙特卡洛树搜索并行化方法 Watch the Unobserved: a Sample Approach to Parallelizing Monte Carlo Tree ...

  4. matlab变压器紧耦合,一种紧耦合高效llc谐振变压器的制造方法

    一种紧耦合高效llc谐振变压器的制造方法 [专利摘要]本发明公开了一种紧耦合高效LLC谐振变压器,包括磁芯不带气隙的环形主变压器,主变压器的初级线圈一端与输入辅助电感连接:主变压器的第二次级线圈两端分 ...

  5. 一种简单易用的台球瞄准方法——对称瞄准法

    一种简单易用的台球瞄准方法 --对称瞄准法 台球运动是一种大众喜欢的休闲娱乐活动.在各类台球运动中,都需要使用主球把目标球击打进袋,在这个过程中,瞄准是击打进袋的重要一环. 台球瞄准的方法有很多种,比 ...

  6. 怎么给照片降噪?分享两种简单好用的图片降噪方法

    怎么给照片降噪呢?大家在使用数码摄影的时候,相机将光线作为接收信号接收并输出的过程中,会产生图片中粗糙的部分,这本是不该出现的外来像素,导致拍出来的照片或多或少会出现噪点,让图片看起来不美观.这时候我 ...

  7. 计算机房间墙壁的布置,11种简单且有特色的墙面布置方法

    你有试着自己装点过自己房间的墙面吗?最简单也最快速的装饰方法就是用相片以及海报,但是时间久了就会发现似乎也歇缺乏新意,以下介绍了11种简单且有特色的墙面布置方法,运用的素材从杂志内页到相框.甚至地图都 ...

  8. 一种相对高效的按键消抖方法

    按键软件消抖自我接触单片机开始就已经存在这个问题了,网上的办法无非是延时消抖和定时轮询.对于写裸机的我来说这两种方法都不可避免的会有资源浪费掉,今天突然有了灵感,想到了一种相对高效的办法来解决消抖问题 ...

  9. givemesomecredit数据_EasyEnsemble:一种简单的不平衡数据的建模方法(附测试代码)...

    摘要 虽然我这里洋洋洒洒写了2000字,但实际原理我一句话就能讲完,那就是"通过重复组合正样本与随机抽样的同样数量的负样本,训练若干数量分类器进行集成学习".但为了让大家对这个算法 ...

最新文章

  1. 2021最大看点AI for Science,在哪些领域有斩获?
  2. C++内存管理与分配方式
  3. [How TO]-ubuntu下快速搭建http
  4. android 自定义特效,Android自定义FloatingText仿点赞+1特效
  5. pandas string
  6. php抓娃娃机器,vue制作抓娃娃机 - osc_icwhzig7的个人空间 - OSCHINA - 中文开源技术交流社区...
  7. python开发基础作业02:三级菜单,使用字典dic及列表
  8. java比身高怎么做_D3 Y比例,y对比身高?
  9. 运动目标跟踪(四)--搜索算法优化搜索方向之Camshift
  10. data spring 指定时区_听说过spring-data-jdbc么?来个最佳实践
  11. h5游戏网站源码_从WEB前端角度看H5游戏开发
  12. 面向对象实现气缸吹气类的PLC逻辑
  13. 分布式中间件实践之路
  14. java base64图片计算图片大小
  15. SQL三个表关联查询
  16. Day 11 Contractions and Present Perfect
  17. react 使用dom-to-image 将html转为图片并保存
  18. 【VSCode】yarn : 无法加载文件 A:\yuke\node\node_gobal\yarn.ps1,因为在此系统上禁止运行脚本。
  19. VBS脚本统计红楼梦中贾宝玉出现的次数
  20. DameWare各种版本激活码-备份

热门文章

  1. chatGPT的体验,是不是真智能?
  2. web前端入门到实战:CSS实现雨滴动画效果
  3. 可是姑娘,你为什么要编程呢?
  4. P27 026字典:当索引不好用时1----20201005
  5. [Java基础] Java8的Function函数及其使用
  6. 「原谅宝」被热议的女友前科调查程序
  7. CAN矩阵、CAN网络、DBC、MDF关系
  8. antd-vue中的表格排序
  9. 《激情冰雪》MV首发 多位奥运和世界冠军联袂演绎
  10. Oracle数据泵Expdp/Impdp带查询条件导出/导入dmp包