opencv建立数学坐标系绘制函数曲线

Posted 落樱弥城

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了opencv建立数学坐标系绘制函数曲线相关的知识,希望对你有一定的参考价值。

  周末闲来无事用opencv简单实现了一个可以在mat中绘制曲线的工具类,方便学习图像处理相关的内容。

  坐标系本身比较简单就是常规的数学坐标系,可以自由设置图像中坐标系的范围,内部会自动映射需要绘制的点到Mat上。因为注释比较全,下面只简单描述下使用方式不再进行详细的解释。

double mysqrt(double x) 
	return sqrt(x);


double mysqare(double x, int a, int b) 
	return x * x * a + b;


double myawx(double x, int a, int b) 
	return x * a + b;


void testSurface() 
	AxisSurface surface(cv::Size(960, 960), 10);
	int axisRange = 50;
	AxisRange range =  cv::Point2d(-1 * axisRange, -1 * axisRange), cv::Point2d(axisRange, axisRange) ;
	surface.setRange(range);
	
	auto func1 = std::bind(mysqrt, std::placeholders::_1);
	AxisFunc<decltype(func1)> axisFunc1 =  func1, -256, 256, 0.001, AxisColorRed, "f(x) = sqrt(x)";
	surface.draw(axisFunc1);

	auto func2 = std::bind(mysqare, std::placeholders::_1, 2, -30);
	AxisFunc<decltype(func2)> axisFunc2 =  func2, -256, 256, 0.001, AxisColorBlue, "f(x) = 2x^2 - 30" ;
	surface.draw(axisFunc2);
	
	auto func3 = std::bind(myawx, std::placeholders::_1, 1, 20);
	AxisFunc<decltype(func3)> axisFunc3 =  func3, -256, 256, 0.001, AxisColorBlue, "f(x) = x + 20" ;
	surface.draw(axisFunc3);

	cv::Mat ret = surface.getSurface();
	cv::namedWindow("");
	cv::imshow("", ret);
	cv::waitKey(0);


001.jpg

002.jpg

  具体实现

#ifndef __AXIS_SURFACE_H__
#define __AXIS_SURFACE_H__

/*
 * @brief 一个坐标系的实现,会将图像绘制到opencv的mat中,主要用来绘制函数曲线,函数曲线使用点来绘制
 */
#include <opencv/cv.h>
#include <opencv2/opencv.hpp>
#include <memory>

/*
 * @brief 一些预设的颜色和方位
 */
#define AxisColorBlack		cv::Scalar(0, 0, 0)
#define AxisColorWhite		cv::Scalar(255, 255, 255)
#define AxisColorGray(x)	cv::Scalar(int(x), int(x), int(x))
#define AxisColorRed		cv::Scalar(0, 0, 255)
#define AxisColorGreen		cv::Scalar(0, 255, 0)
#define AxisColorBlue		cv::Scalar(255, 0, 0)

enum AxisTextLocation : int32_t 
	AxisTextLocationTop,
	AxisTextLocationTopRight,
	AxisTextLocationRight,
	AxisTextLocationBottomRight,
	AxisTextLocationBottom,
	AxisTextLocationBottomLeft,
	AxisTextLocationLeft,
	AxisTextLocationTopLeft,
;

/*
 * @brief 坐标轴的边框的设置参数
 * @param color 坐标轴边框的颜色
 * @param type	绘制的线的类型
 * @param width 宽度
 */
struct AxisLineParam 
	cv::Scalar color;
	cv::LineTypes type;
	int32_t width;
;

/*
 * @brief 画布中坐标轴的范围,正常的xy坐标轴,内部会将坐标映射到画布中,比如希望创建[-10,-10]->[10,10]大小的画布,分别设置两个点为[-10,-10] [10,10]即可
 * @param leftBottom	坐标轴左下角坐标
 * @param rightTop		坐标轴右下角坐标
 */
struct AxisRange 
	cv::Point2d leftBottom;
	cv::Point2d rightTop;
;

/*
 * @brief 绘制的函数的参数,来描述希望绘制的函数曲线的具体参数,曲线使用点绘制的
 * @pram func		函数模板,使用时使用std::bind绑定函数即可
 * @param startx	希望绘制的x坐标范围[startx, endx]
 * @param endx		希望绘制的曲线的x坐标范围的结尾
 * @param step		绘制曲线时x坐标的步进,即相邻两个点之间横坐标的差
 * @param color		绘制的曲线的颜色
 * @param desc		绘制曲线的描述
 * @param radius	绘制曲线每个点的半径
 */
template<class Func>
struct AxisFunc 
	Func func;
	double startx;
	double endx;
	double step;
	cv::Scalar color;
	std::string desc;
	double radius;
;

/*
 * @brief 使用opencv绘制函数曲线,内部包含一个mat类,所有的内容都会绘制到mat中
 * @param m_size		当前画布的大小,即Mat的实际大小
 * @param m_border		坐标轴边界和mat边界之间的间距,四个边是等距的
 * @param m_lineParam	绘制边框的线的参数
 * @param m_range		当前画布中坐标轴的范围
 * @param m_psurface	当前绘制的画面的mat
 */
class AxisSurface
public:
	AxisSurface(const cv::Size size =  720, 480 , const int32_t border = 20, const AxisLineParam& lineParam =  AxisColorBlack, cv::LINE_8, 2 ) : m_size(size), m_border(border), m_lineParam(lineParam) 
		m_psurface = std::make_shared<cv::Mat>(m_size.height, m_size.width, CV_8UC3, AxisColorWhite);
		update(m_size, m_border, lineParam);
	

	~AxisSurface() 

public:

	void setRange(const AxisRange &range) 
		if (!(m_range.leftBottom == range.leftBottom && m_range.rightTop == range.rightTop)) 
			m_range = range;
			clear(m_size, m_border, m_lineParam);
		
	

	/*
	 * @brief 绘制函数曲线,以及函数的描述
	 */
	template<class Func>
	void draw(const AxisFunc<Func> func) 
		for (double x = func.startx; x <= func.endx; x += func.step) 
			double y = func.func(x);
			drawPoint(cv::Point2d(x, y), func.radius, func.color);
		

		cv::Size textSize = cv::getTextSize(func.desc, cv::FONT_HERSHEY_SIMPLEX, 0.4, 1, nullptr);
		double value = (textSize.height + 7) * (m_range.rightTop.y - m_range.leftBottom.y) / (m_size.height - 2.0 * m_border);
		drawText(func.desc, cv::Point2d(m_range.leftBottom.x, m_range.rightTop.y - value * m_funcNumber++), cv::FONT_HERSHEY_SIMPLEX, 0.4, func.color, AxisTextLocationBottomRight);
		update(m_size, m_border, m_lineParam);
	

	cv::Mat getSurface() 
		return (*m_psurface).clone();
	

	/**
	 * @brief 仅仅更新坐标轴的参数
	 */
	void updateSetting(const int border, const AxisLineParam& lineParam) 
		m_lineParam = lineParam;
		m_border = border;
		update(m_size, border, lineParam);
	
	
	/**
	 * @brief 更新当前已经绘制的mat的参数,如果尺寸和当前存在的mat不同会清空已经绘制的内容
	 */
	void updateSetting(const cv::Size size, const int border, const AxisLineParam& lineParam) 
		if (size.width == m_size.width && size.height == m_size.height) 
			updateSetting(border, lineParam);
		
		else 
			clear(size, border, lineParam);
		
	

	/*
	 * @brief 清空mat,参数为AxisSurface希望更新的参数
	 */
	void clear(const cv::Size size =  -1, -1 , const int border = -1, const AxisLineParam& lineParam =  AxisColorBlack, cv::LINE_8, 2 ) 
		if (!(size.width < 0 || size.height < 0 || border < 0)) 
			m_size = size;
			m_border = border;
		

		m_psurface = std::make_shared<cv::Mat>(m_size, CV_8UC3, AxisColorWhite);
		m_lineParam = lineParam;
		m_funcNumber = 0;
		update(m_size, border, lineParam);
	
private:

	/*
	 * @brief 绘制坐标轴,基本参数就是surface的参数,不详述
	 */
	void update(const cv::Size &size, const int border, const AxisLineParam& lineParam) 
		const int axisWidth = lineParam.width;
		cv::Scalar color = lineParam.color;
		cv::LineTypes lineType = lineParam.type;
		//绘制坐标轴
		double startx = m_range.leftBottom.x, endx = m_range.rightTop.x;
		double starty = m_range.leftBottom.y, endy = m_range.rightTop.y;

		
			int awidth = 1;
			drawLine(cv::Point2d(startx, 0), cv::Point2d(endx, 0), AxisColorGray(128), awidth, lineType);
			drawLine(cv::Point2d(0, starty), cv::Point2d(0, endy), AxisColorGray(128), awidth, lineType);
			drawCircle(cv::Point2d(0, 0), 3, AxisColorBlack, -1);
		

		
			//绘制边界
			cv::Point2d leftDown =  startx, starty , leftUp =  startx, endy , rightUp =  endx, endy , rightDown =  endx, starty ;
			//下方的线
			drawLine(leftDown, rightDown, color, axisWidth, lineType);
			//左侧的线
			drawLine(leftDown, leftUp, color, axisWidth, lineType);
			//右边的线
			drawLine(rightDown, rightUp, color, axisWidth, lineType);
			//顶部的线
			drawLine(rightUp, leftUp, color, axisWidth, lineType);
		

		
			float textSize = 0.5;
			cv::Scalar textColor = AxisColorBlack;
			int textFont = cv::FONT_HERSHEY_SIMPLEX;
			int gap = 5;
			drawText("0", cv::Point2d(0, 0), textFont, textSize, textColor, AxisTextLocationBottomRight, 2);
			drawText(std::to_string(int(endy)), cv::Point2d(0, endy), textFont, textSize, textColor, AxisTextLocationBottomRight, gap);
			drawText(std::to_string(int(startx)), cv::Point2d(startx, 0), textFont, textSize, textColor, AxisTextLocationBottomRight, gap);
			drawText(std::to_string(int(starty)), cv::Point2d(0, starty), textFont, textSize, textColor, AxisTextLocationTopRight, gap);
			if (endx != 0) 
				//防止m_range.rightTop的x坐标为0时,会绘制两个0
				drawText(std::to_string(int(endx)), cv::Point2d(endx, 0), textFont, textSize, textColor, AxisTextLocationBottomLeft, gap);
			
		
	

private:
	//绘制相关的函数,传入的最表示数学上常规的坐标系,x和y分别向右向上增长,如果绘制的坐标超过边界会略过
	/*
	 * @brief 绘制圆,基本参数和opencv相同,只是做了坐标映射
	 */
	void drawCircle(cv::Point2d center, int radius, const cv::Scalar& color, int thickness  = 1 , int lineType  = cv::LINE_8 , int shift  = 0 ) 
		cv::Point2d pt(pointMap(center));
		if (pt.x < m_border || pt.x > m_size.width - m_border || pt.y < m_border || pt.y > m_size.height - m_border) 
			return;
		

		cv::circle(*m_psurface, pt, radius, color, thickness, lineType, shift);
	

	/*
	 * @brief 绘制实心圆,基本参数和opencv相同,只是做了坐标映射
	 */
	void drawPoint(cv::Point2d center, int radius, const cv::Scalar& color, int lineType = cv::LINE_8, int shift = 0) 
		drawCircle(center, radius, color, -1, lineType, shift);
	

	/*
	 * @brief 绘制线,基本参数和opencv相同,只是做了坐标映射
	 */
	void drawLine(cv::Point2d pt1, cv::Point pt2, const cv::Scalar& color, int thickness = 1, int lineType = cv::LINE_8, int shift = 0) 
		cv::Point2d point1(pointMap(pt1)), point2(pointMap(pt2));

		if (point1.x < m_border || point1.x > m_size.width - m_border || point1.y < m_border || point1.y > m_size.height - m_border) 
			return;
		

		if (point2.x < m_border || point2.x > m_size.width - m_border || point2.y < m_border || point2.y > m_size.height - m_border) 
			return;
		

		cv::line(*m_psurface, point1, point2, color, thickness, lineType, shift);
	

	/*
	 * @brief 绘制文字,opencv默认绘制文字的坐标是文字的左下角,这里提供参数来配置文字具体绘制在哪个方向
	 * @param text		文字具体内容
	 * @param org		绘制文字的坐标
	 * @param fontFact	文字字体类型
	 * @param fontScale	文字大小
	 * @param color		文字颜色
	 * @param location	文字绘制位置相对于org的位置
	 * @param gap		绘制文字是x和y方向相对于org的距离
	 * @param thickness	文字的线的宽度
	 * @param lineType	文字的线的类型
	 */
	void drawText(const std::string &text, const cv::Point2d org, int fontFace, double fontScale, cv::Scalar color = AxisColorBlack, const AxisTextLocation location = AxisTextLocationTopRight, int gap= 5, int thickness = 1, int lineType = cv::LINE_8) 
		cv::Point2d point1(pointMap(org));
		if (point1.x < m_border || point1.x > m_size.width - m_border || point1.y < m_border || point1.y > m_size.height - m_border) 以上是关于opencv建立数学坐标系绘制函数曲线的主要内容,如果未能解决你的问题,请参考以下文章

opencv建立数学坐标系绘制函数曲线

matlabplot纵坐标要为正整数吗

android中这样的曲线要怎么绘制

(专题四)03 其它形式的二维曲线

opencv提取论文中曲线坐标重新拟合绘制

Matlab 绘图函数plot类