图片直线检测,简易封装

在写工程时,经常会用到一些功能单一的函数。将其进行良好的封装,才便于下次使用。

设计思路

类对象的功能设计为:

  1. 在一张特定的图像中检测出若干条直线
  2. 每条直线有唯一id,方便外部储存和查找
  3. 支持通过某点查询到直线
  4. 可保存检测结果,便于二次免检测重用
  5. 检测参数可配置
  6. 使用opencv为基础库

代码细节

配置参数
  1. nModeValue设置输出的图像类型
  2. nCannyLowThreshold、nSobelKernelSize、hlThreshold、hlMinLineLength以及hlMaxLineGap,都是对应算法中的参数,相关意义参考其算法解释。
class ImgToCADConfig
{
public:
	ImgToCADConfig() :
		nModeValue(1), nCannyLowThreshold(50), nSobelKernelSize(1),
		hlThreshold(25), hlMinLineLength(3), hlMaxLineGap(3),
		maxSelectPixelDist(10)
	{}

	//bool load_config(const char* fnConfig);
	//bool save_config(const char* fnConfig);

	int nModeValue; //0为原图像,1为canny,2为sobel,3位Scharr,4为Laplacian,5为Laplacian增强对比度
	int nCannyLowThreshold; //canny边缘检测低阈值
	int nSobelKernelSize; //sobel算子内核大小

	int hlThreshold;	// 霍夫检测的阈值
	int hlMinLineLength; // 线段以像素为单位的最小长度,根据应用场景设置 
	int hlMaxLineGap;	// 同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段

	int maxSelectPixelDist;	// 通过定点选择直线时的容错误差
};
自定义直线类型(如果要在此基础上做更广阔的开发,应当独立结构)

使用opencv中的cv::Vec4i 结构,保存线段。

struct LineWrapper
{
	int id;		// 线段id
	float dist;	// 临时使用的变量,其实不该在这里的
	cv::Vec4i ln;	// 线段两端点

	cv::Point2f getVerticalPoint(int x, int y) const; // 求(x,y)到在线段上的垂点

	LineWrapper() : id(-1), dist(0.0), ln() {}
};
检测结构

只希望提供简洁易用的功能。

class DLL_API ImgToCAD
{
	// 几个友元函数,可以在外部进行自定义实现
	friend void on_ModeType(int, void *);
	friend void on_CannyType(int, void *);
	friend void on_SobelType(int, void *);
	friend void on_FindLine(int, void *);
	friend void on_Mouse(int event, int x, int y, int, void*);

public:
	ImgToCAD();
	ImgToCAD(const ImgToCADConfig& _cfg);

	// 对指定路径的图像进行处理,类中只保存最后一次的处理结果
	bool process_image(const char* fnImg);
	// 加载处理文件
	bool load_edge_file(const char* fnEdgeIndex, const char* fnEdgeMat);
	// 保存处理结果
	bool save_edge_file(const char* fnEdgeIndex, const char* fnEdgeMat);
	// 根据(x, y),选择一条直线,容错程度参考config.maxSelectPixelDist
	int select_line(int x, int y) const;
	// 根据id获得直线
	const LineWrapper* get_line(int id) const;

public:
	// 配置
	ImgToCADConfig cfg;
	// 检测完成后,可以显示出检测结果,默认为false
	bool showImg;

private:
	void FindLines(cv::Mat& matSrc);
	void DrawSelected(cv::Mat& matSrc, int x, int y);
	void Process();

private:
	cv::Mat srcImage, dstImage, grayImage;
	std::vector<LineWrapper> lineWrapper;
};
头文件
// stdafx.h
#pragma once

#ifdef _MSC_VER

#ifdef CAMERASETUPDLL_EXPORTS
#define DLL_API __declspec(dllexport)  
#else
#define DLL_API __declspec(dllimport)  
#endif

#else

#define DLL_API

#endif

// ImgToCAD.h
#ifndef _IMGTOCAD_H_
#define _IMGTOCAD_H_

#include <opencv2/opencv.hpp>

#include "stdafx.h"

class DLL_API ImgToCADConfig
{
public:
	ImgToCADConfig() :
		nModeValue(1), nCannyLowThreshold(50), nSobelKernelSize(1),
		hlThreshold(25), hlMinLineLength(3), hlMaxLineGap(3),
		maxSelectPixelDist(10)
	{}

	//bool load_config(const char* fnConfig);
	//bool save_config(const char* fnConfig);

	int nModeValue; //0为原图像,1为canny,2为sobel,3位Scharr,4为Laplacian,5为Laplacian增强对比度
	int nCannyLowThreshold; //canny边缘检测低阈值
	int nSobelKernelSize; //sobel算子内核大小

	int hlThreshold;
	int hlMinLineLength;
	int hlMaxLineGap;

	int maxSelectPixelDist;
};

struct DLL_API LineWrapper
{
	int id;
	float dist;
	cv::Vec4i ln;

	cv::Point2f getVerticalPoint(int x, int y) const;

	LineWrapper() : id(-1), dist(0.0), ln() {}
};

class DLL_API ImgToCAD
{
	friend void on_ModeType(int, void *);
	friend void on_CannyType(int, void *);
	friend void on_SobelType(int, void *);
	friend void on_FindLine(int, void *);
	friend void on_Mouse(int event, int x, int y, int, void*);

public:
	ImgToCAD();
	ImgToCAD(const ImgToCADConfig& _cfg);

	bool process_image(const char* fnImg);
	bool load_edge_file(const char* fnEdgeIndex, const char* fnEdgeMat);
	bool save_edge_file(const char* fnEdgeIndex, const char* fnEdgeMat);
	int select_line(int x, int y) const;
	const LineWrapper* get_line(int id) const;

public:
	ImgToCADConfig cfg;
	bool showImg;

private:
	void FindLines(cv::Mat& matSrc);
	void DrawSelected(cv::Mat& matSrc, int x, int y);
	void Process();

private:
	cv::Mat srcImage, dstImage, grayImage;
	std::vector<LineWrapper> lineWrapper;
};

#endif

源代码
#include "ImgToCAD.h"

#include <fstream>

//https://www.cnblogs.com/mfryf/p/3491777.html
static double GetPointDistance(cv::Point p1, cv::Point p2)
{
	return sqrt((p1.x - p2.x)*(p1.x - p2.x) + (p1.y - p2.y)*(p1.y - p2.y));
}

static float GetNearestDistance(cv::Point PA, cv::Point PB, cv::Point P3)
{
	float a, b, c;
	a = GetPointDistance(PB, P3);
	if (a <= 0.00001)
		return 0.0f;
	b = GetPointDistance(PA, P3);
	if (b <= 0.00001)
		return 0.0f;
	c = GetPointDistance(PA, PB);
	if (c <= 0.00001)
		return a;

	if (a*a >= b * b + c * c)
		return b;
	if (b*b >= a * a + c * c)
		return a;

	float l = (a + b + c) / 2;
	float s = sqrt(l*(l - a)*(l - b)*(l - c));
	return 2 * s / c;
}

ImgToCAD::ImgToCAD() : 
	cfg(), showImg(false), 
	srcImage(), dstImage(), grayImage()
{
}

ImgToCAD::ImgToCAD(const ImgToCADConfig& _cfg) :
	cfg(_cfg), showImg(false),
	srcImage(), dstImage(), grayImage()
{
}

bool ImgToCAD::process_image(const char* fnImg)
{
	srcImage = cv::imread(fnImg);
	dstImage.create(srcImage.size(), srcImage.type());

	Process();
	return true;
}

bool ImgToCAD::load_edge_file(const char* fnEdgeIndex, const char* fnEdgeMat)
{
	std::ifstream ifs(fnEdgeIndex);

	if (!ifs.is_open())
		return false;

	for (std::string line; std::getline(ifs, line); )
	{
		LineWrapper lw;

		char* strend = nullptr;
		lw.id = strtol(line.c_str(), &strend, 10);
		lw.ln[0] = strtol(strend + 1, &strend, 10);
		lw.ln[1] = strtol(strend + 1, &strend, 10);
		lw.ln[2] = strtol(strend + 1, &strend, 10);
		lw.ln[3] = strtol(strend + 1, &strend, 10);

		lineWrapper.push_back(lw);
	}

	return true;
}

bool ImgToCAD::save_edge_file(const char* fnEdgeIndex, const char* fnEdgeMat)
{
	FILE* fpEdgeIndex = fopen(fnEdgeIndex, "wb");
	for (auto& lw : lineWrapper)
	{
		char buf[100];
		size_t len = sprintfse(buf, "%d %d %d %d %d\n", lw.id, lw.ln[0], lw.ln[1], lw.ln[2], lw.ln[3]);
		fwrite(buf, 1, len, fpEdgeIndex);
	}
	fclose(fpEdgeIndex);

	FILE* fpEdgeMat = fopen(fnEdgeMat, "wb");
	fclose(fpEdgeMat);

	std::vector<int> compression_params;
	compression_params.push_back(cv::IMWRITE_PNG_COMPRESSION);
	compression_params.push_back(9);

	cv::imwrite(fnEdgeMat, dstImage, compression_params);

	return true;
}

int ImgToCAD::select_line(int x, int y) const
{
	if(showImg)
		std::cout << "x=" << x << ",y=" << y << std::endl;

	std::vector<LineWrapper> lineWrapperCp(lineWrapper);

	for (size_t i = 0; i < lineWrapperCp.size(); i++)
	{
		LineWrapper& lw(lineWrapperCp[i]);
		//lw.dist = getDist_P2L(cv::Point(x, y), cv::Point(lw.ln[0], lw.ln[1]), cv::Point(lw.ln[2], lw.ln[3]));
		lw.dist = GetNearestDistance(cv::Point(lw.ln[0], lw.ln[1]), cv::Point(lw.ln[2], lw.ln[3]), cv::Point(x, y));
	}

	//find near 0
	std::sort(lineWrapperCp.begin(), lineWrapperCp.end(), [](const LineWrapper& a, const LineWrapper& b) {
		return std::abs(a.dist) < std::abs(b.dist);
	});

	if (lineWrapperCp[0].dist < 0 || lineWrapperCp[0].dist > cfg.maxSelectPixelDist)
		return -1;

	return lineWrapperCp[0].id;
}

const LineWrapper* ImgToCAD::get_line(int id) const
{
	if (id < 0 || id >= lineWrapper.size())
		return nullptr;
	return &lineWrapper[id];
}

void ImgToCAD::FindLines(cv::Mat& matSrc)
{
	cv::cvtColor(matSrc, grayImage, cv::COLOR_RGB2GRAY);

	std::vector<cv::Vec4i> g_lines;
	lineWrapper.clear();

	//void HoughLinesP( InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength = 0, double maxLineGap = 0 );
	cv::HoughLinesP(grayImage, g_lines, 1, CV_PI / 180, cfg.hlThreshold, cfg.hlMinLineLength, cfg.hlMaxLineGap);
	for (size_t i = 0; i < g_lines.size(); i++)
	{
		cv::Vec4i ln = g_lines[i];
		cv::line(matSrc, cv::Point(ln[0], ln[1]), cv::Point(ln[2], ln[3]), cv::Scalar(0, 0, 255));

		LineWrapper lw;
		lw.id = i;
		lw.ln = ln;
		lineWrapper.push_back(lw);
	}
}

void ImgToCAD::DrawSelected(cv::Mat& matSrc, int x, int y)
{
	//cv::putText(matSrc, "123", cv::Point(10, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 255, 255));

	int lwid = select_line(x, y);
	if (lwid <= -1)
		return;

	cv::Vec4i ln = lineWrapper[lwid].ln;
	cv::line(matSrc, cv::Point(ln[0], ln[1]), cv::Point(ln[2], ln[3]), cv::Scalar(0, 255, 0), 2);
}

//定义回调函数
void ImgToCAD::Process()
{
	cv::Mat g_grayImage1, g_grayImage2, g_tmpImage, g_tempImage, g_tmpImage1, g_tempImage1;

	switch (cfg.nModeValue)
	{
	case 0://源图像
		srcImage.copyTo(dstImage);
		break;
	case 1://canny边缘检测算法
		cv::cvtColor(srcImage, grayImage, cv::COLOR_RGB2GRAY);
		cv::blur(grayImage, g_tmpImage, cv::Size(3, 3));//先用均值滤波器进行平滑去噪
		cv::Canny(g_tmpImage, g_tmpImage, cfg.nCannyLowThreshold, cfg.nCannyLowThreshold * 3);
		dstImage = cv::Scalar::all(0);
		srcImage.copyTo(dstImage, g_tmpImage);//将g_tmpImage作为掩码,来将原图像拷贝到输出图像中
		break;
	case 2://sobel边缘检测算法
		cv::Sobel(srcImage, g_tmpImage, CV_16S, 1, 0, (2 * cfg.nSobelKernelSize + 1), 1, 1, cv::BORDER_DEFAULT);//求x方向梯度
		cv::convertScaleAbs(g_tmpImage, g_tmpImage1);//计算绝对值,并将结果转换成8位
		cv::Sobel(srcImage, g_tempImage, CV_16S, 0, 1, (2 * cfg.nSobelKernelSize + 1), 1, 1, cv::BORDER_DEFAULT);//求y方向梯度
		cv::convertScaleAbs(g_tempImage, g_tempImage1);//计算绝对值,并将结果转换成8位
		cv::addWeighted(g_tmpImage1, 0.5, g_tempImage1, 0.5, 0, dstImage);
		break;
	case 3://Scharr函数
		cv::Scharr(srcImage, g_tmpImage, CV_16S, 1, 0, 1, 0, cv::BORDER_DEFAULT);//求x方向梯度
		cv::convertScaleAbs(g_tmpImage, g_tmpImage1);//计算绝对值,并将结果转换成8位
		cv::Sobel(srcImage, g_tempImage, CV_16S, 0, 1, 1, 0, cv::BORDER_DEFAULT);//求y方向梯度
		cv::convertScaleAbs(g_tempImage, g_tempImage1);//计算绝对值,并将结果转换成8位
		cv::addWeighted(g_tmpImage1, 0.5, g_tempImage1, 0.5, 0, dstImage);
		break;
	case 4://Laplacian边缘检测算法
		cv::GaussianBlur(srcImage, g_tmpImage, cv::Size(3, 3), 0, 0, cv::BORDER_DEFAULT);//高斯滤波消除噪声
		cv::cvtColor(g_tmpImage, g_grayImage1, cv::COLOR_RGB2GRAY);
		cv::Laplacian(g_grayImage1, g_tmpImage, CV_16S, (cfg.nSobelKernelSize * 2 + 1), 1, 0, cv::BORDER_DEFAULT);
		cv::convertScaleAbs(g_tmpImage, dstImage);
		break;
	case 5://利用原图像减去拉普拉斯图像增强对比度
		cv::GaussianBlur(srcImage, g_tmpImage, cv::Size(3, 3), 0, 0, cv::BORDER_DEFAULT);//高斯滤波消除噪声
		cv::cvtColor(g_tmpImage, g_grayImage2, cv::COLOR_RGB2GRAY);
		cv::Laplacian(g_grayImage2, g_tmpImage, CV_16S, (cfg.nSobelKernelSize * 2 + 1), 1, 0, cv::BORDER_DEFAULT);
		cv::convertScaleAbs(g_tmpImage, g_tmpImage1);
		dstImage = g_grayImage2 - g_tmpImage1;
		break;
	}

	FindLines(dstImage);

#ifdef _MSC_VER
	if (showImg)
		cv::imshow("EdgeDetector", dstImage);
#endif
}

// known: line ab, point c
// request: vertical point: d
// float abx = b.x - a.x;
// float aby = b.y - a.y;
// float acx = c.x - a.x;
// float acy = c.y - a.y;
// float f = (abx*acx + aby * acy) / (abx*abx + aby * aby);  // 注意ab必须是直线上的两个不同点
// d.x = a.x + f * abx;
// d.y = a.y + f * aby;

cv::Point2f LineWrapper::getVerticalPoint(int x, int y) const
{
	cv::Point2f d;

	float abx = ln[2] - ln[0];
	float aby = ln[3] - ln[1];
	float acx = x - ln[0];
	float acy = y - ln[1];
	float f = (abx*acx + aby * acy) / (abx*abx + aby * aby);  // 注意ab必须是直线上的两个不同点
	d.x = ln[0] + f * abx;
	d.y = ln[1] + f * aby;
	return d;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值