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) {
    
    
			return;
		}

		{
    
    
			//根据文字的大小和方向计算具体绘制的位置
			cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness, nullptr);
			switch (location) {
    
    
			case AxisTextLocationTop:
			{
    
    
				point1 = cv::Point2d(point1.x - textSize.width / 2, point1.y - gap);
			}
			break;
			case AxisTextLocationTopRight:
			{
    
    
				point1 = cv::Point2d(point1.x, point1.y - gap);
			}
			break;
			case AxisTextLocationRight:
			{
    
    
				point1 = cv::Point2d(point1.x + gap, point1.y + textSize.height / 2);
			}
			break;
			case AxisTextLocationBottomRight:
			{
    
    
				point1 = cv::Point2d(point1.x + gap, point1.y + textSize.height + gap);
			}
			break;

			case AxisTextLocationBottom:
			{
    
    
				point1 = cv::Point2d(point1.x - textSize.width / 2, point1.y + textSize.height + gap);
			}
			break;
			case AxisTextLocationBottomLeft:
			{
    
    
				point1 = cv::Point2d(point1.x - textSize.width - gap, point1.y + textSize.height + gap);
			}
			break;
			case AxisTextLocationLeft:
			{
    
    
				point1 = cv::Point2d(point1.x - textSize.width - gap, point1.y + textSize.height / 2);
			}
			break;
			case AxisTextLocationTopLeft:
			{
    
    
				point1 = cv::Point2d(point1.x - textSize.width - gap, point1.y + gap);
			}
			break;
			default:
				break;
			}
		}

		cv::putText(*m_psurface, text, point1, fontFace, fontScale, color, thickness, lineType);
	}

	/*
	 * @brief 将数学坐标系上的坐标映射到画布上的真实坐标
	 * @param 希望映射的xy坐标轴上的坐标
	 */
	cv::Point2d pointMap(const cv::Point2d &pt) {
    
    
		cv::Size2d canvSize(m_range.rightTop.x - m_range.leftBottom.x, m_range.rightTop.y - m_range.leftBottom.y);
		cv::Size2d matSize(m_size.width - 2 * m_border, m_size.height - 2 * m_border);

		cv::Point2d canvDistance(pt.x - m_range.leftBottom.x, pt.y - m_range.leftBottom.y);
		cv::Point2d matDistance(matSize.width * 1.0 / canvSize.width * canvDistance.x, matSize.height * 1.0 / canvSize.height * canvDistance.y);
		return cv::Point2d(m_border + matDistance.x, m_size.height - m_border - matDistance.y);
	}


private:
	cv::Size2d m_size = {
    
     720, 480};
	int32_t m_border = 30;
	AxisLineParam m_lineParam = {
    
     AxisColorBlack, cv::LINE_8, 3};
	AxisRange m_range = {
    
     {
    
     -128, -128}, {
    
     128, 128} };

	std::shared_ptr<cv::Mat> m_psurface;
	int32_t m_funcNumber = 0;
};

#endif //__AXIS_SURFACE_H__

猜你喜欢

转载自blog.csdn.net/GrayOnDream/article/details/126911040