FPGA 图像加速处理之 canny 算法——C语言实现

本文偏重于算法原理的理解及C语言编程实现,最终输出的图像完全与OpenCV处理的一模一样,更详细的原理简介,可以参考网上其他文章

一 canny 原理简介

canny 算法是迄今为止最为优秀的边缘检查算法,它主要基于三个目标:

1、低错误率。所谓的低错误率就是所有的边缘都应该被找到,并且没有伪边缘。也就是检查到的边缘必须尽可能是真实的边缘。

2、边缘点应被很好第定位。也就是检查到的边缘点,必须尽可能的定位准确。应该更逼近真实边缘的中心。

3、单一的边缘点响应。也就是标记出来的边缘必须窄(单点连接而成)。不应该过宽。

要实现上面三个目标,可以使用一下方式来实现:

1、使用滞后阀值(双阀值)来处理,可以达到低错误率的要求。

2、使用非极大值抑制,可以实现边缘的单一响应及很好的定位。

所以 canny 算法使用了一下步骤:

二 canny 实现步骤

1、高斯滤波,一般用5x5的核进行滤波。

2、sobel 求出梯度幅值,同时求出梯度方向(0°,45°,90°,135°)

3、非极大值抑制

4、滞后阀值处理

三 代码

本代码基于C 语言实现。因为最后要把算法移植到FPGA进行加速,所以主要用于实现和理解算法逻辑,并非最优代码

本算法模块,默认输入图像已经被高斯平滑过,所以省去高斯滤波这一块。

 

#include"include.h"

/*****************************************************************
void fpga_canny(cv::Mat src, cv::Mat dst, uchar sel,uchar maxVal,uchar minVal)
功能:
	Canny 算法
参数:
	cv::Mat srcImg
		输入图像,已经过高斯处理
	cv::Mat dstImg
		输出图像
	uchar sel
		sobel 输出图像边缘 1=标准sobel,0=双边缘
	uchar maxVal
		最大阈值
	uchar minVal
		最小阈值
返回值:
无
****************************************************************/



void fpga_canny(cv::Mat src, cv::Mat dst, uchar sel, uchar maxVal, uchar minVal)
{

	unsigned int rows = src.rows;
	unsigned int cols = src.cols;
	unsigned int x1, x2, y1, y2, Gout;
	unsigned int x_abs, y_abs;
	unsigned int x_abs_dir, y_abs_dir;
	float tan_225 = 0.4142;
	float tan_675 = 2.4142;
	float tmp;
	unsigned char theta; // 0 = 0 度,1 = 45度,2 = 90度,3 = 135度
	unsigned char Gx_s;// Gx 符号 0= 正, 1= 负; 
	unsigned char Gy_s;// Gy 符号 0= 正, 1= 负;
	unsigned char buff3x3[9];
	unsigned char value;

	//cv::Mat nonMaxImg = cv::Mat::zeros (src.rows, dst.cols, CV_16UC1);//存放 nonMax
	cv::Mat nonMaxImg(src.rows, dst.cols, CV_16UC1);//存放 nonMax
	cv::Mat sobelImg(src.rows, dst.cols, CV_16UC1);//存放 sobel
	cv::Mat dirImg(src.rows, dst.cols, CV_8UC1);//存放梯度方向


	cv::Mat sobelImg_show;
	cv::Mat nonMaxImg_show;
	 
	
	// sobel 求梯度
	for (unsigned int y = 2; y< rows -1; y++)
	{

		for (unsigned int x = 2; x < cols -1 ; x++)
		{
			x1 = src.at<unsigned char>(y - 1, x - 1)
				+ src.at<unsigned char>(y, x - 1) * 2
				+ src.at<unsigned char>(y + 1, x - 1);

			x2 = src.at<unsigned char>(y - 1, x + 1)
				+ src.at<unsigned char>(y, x + 1) * 2
				+ src.at<unsigned char>(y + 1, x + 1);

			y1 = src.at<unsigned char>(y - 1, x - 1)
				+ src.at<unsigned char>(y - 1, x) * 2
				+ src.at<unsigned char>(y - 1, x + 1);

			y2 = src.at<unsigned char>(y + 1, x - 1)
				+ src.at<unsigned char>(y + 1, x) * 2
				+ src.at<unsigned char>(y + 1, x + 1);

			if (x1 > x2)
			{
				if (sel)
				{
					x_abs = 0;
				}
				else
				{
					x_abs = x1 - x2;
				}

				Gx_s = 1;
				x_abs_dir = x1 - x2;
				


			}
			else
			{
				x_abs = x2 - x1;

				Gx_s = 0;
				x_abs_dir = x2 - x1;
				
			}



			if (y1 > y2)
			{
				if (sel)
				{
					y_abs = 0;
				}
				else
				{
					y_abs = y1 - y2;
				}

				Gy_s = 1;
				y_abs_dir = y1 - y2;
			}
			else
			{
				y_abs = y2 - y1;

				Gy_s = 0;
				y_abs_dir = y2 - y1;
			}

			Gout = x_abs + y_abs;
			if (Gout > 255)
			{
				//Gout = 255;
			}


			sobelImg.at<unsigned short>(y, x) = Gout;

			// 求梯度方向
			tmp = (float)y_abs_dir / (float)x_abs_dir;

			if (tmp < tan_225)
			{
				theta = 0;
			}
			else if (tmp > tan_675)
			{
				theta = 2;
			}
			else
			{
				if (Gy_s == Gx_s)
				{
					theta = 1;
				}
				else
				{
					theta = 3;
				}

			}

			dirImg.at<unsigned char>(y, x) = theta;


		}

	}


	sobelImg.convertTo(nonMaxImg_show, CV_8UC1);
	cv::imshow("fpga_canny_sobel", sobelImg);
	cv::imwrite("fpga_canny_sobel.bmp", sobelImg);


	//非极大值抑制
	for (unsigned int y = 2; y< rows -1; y++)
	{
		for (unsigned int x = 2; x < cols -1; x++)
		{
			theta = dirImg.at<unsigned char>(y, x);

			switch (theta)
			{
			case 0:
				if ((sobelImg.at<unsigned short>(y, x - 1) < sobelImg.at<unsigned short>(y, x)) && (sobelImg.at<unsigned short>(y, x) >= sobelImg.at<unsigned short>(y, x + 1)))
				{
					nonMaxImg.at<unsigned short>(y, x) = sobelImg.at<unsigned short>(y, x);
					
				}
				else
				{
					nonMaxImg.at<unsigned short>(y, x) = 0;
					
				}
			
					
				break;
			case 3:

				if ((sobelImg.at<unsigned short>(y + 1, x - 1) < sobelImg.at<unsigned short>(y, x)) && (sobelImg.at<unsigned short>(y, x) > sobelImg.at<unsigned short>(y - 1, x + 1)))
				{
					nonMaxImg.at<unsigned short>(y, x) = sobelImg.at<unsigned short>(y, x);
				}
				else
				{
					nonMaxImg.at<unsigned short>(y, x) = 0;
				}


				break;
			case 2:
				if ((sobelImg.at<unsigned short>(y - 1, x) < sobelImg.at<unsigned short>(y, x)) && (sobelImg.at<unsigned short>(y, x) >= sobelImg.at<unsigned short>(y + 1, x)))
				{
					nonMaxImg.at<unsigned short>(y, x) = sobelImg.at<unsigned short>(y, x);
				}
				else
				{
					nonMaxImg.at<unsigned short>(y, x) = 0;
				}

				break;
			case 1:

				if ((sobelImg.at<unsigned short>(y - 1, x - 1) < sobelImg.at<unsigned short>(y, x)) && (sobelImg.at<unsigned short>(y, x) > sobelImg.at<unsigned short>(y + 1, x + 1)))
				{
					nonMaxImg.at<unsigned short>(y, x) = sobelImg.at<unsigned short>(y, x);
				}
				else
				{
					nonMaxImg.at<unsigned short>(y, x) = 0;
				}

				break;
			}
				 

		}

	}


	//unsigned int dis_y,dis_x;
	//dis_y = 216;
	//dis_x = 398;
	//printf("-----------src------------\r\n");
	//printf("%x,%x %x \r\n", src.at<uchar>(dis_y - 1, dis_x - 1), src.at<uchar>(dis_y - 1, dis_x), src.at<uchar>(dis_y - 1, dis_x + 1));
	//printf("%x,%x %x \r\n", src.at<uchar>(dis_y, dis_x - 1), src.at<uchar>(dis_y, dis_x), src.at<uchar>(dis_y, dis_x + 1));
	//printf("%x,%x %x \r\n", src.at<uchar>(dis_y + 1, dis_x - 1), src.at<uchar>(dis_y + 1, dis_x), src.at<uchar>(dis_y + 1, dis_x + 1));

	//printf("-----------sobelImg------------\r\n");
	//printf("%x,%x %x \r\n", sobelImg.at<unsigned short>(dis_y - 1, dis_x - 1), sobelImg.at<unsigned short>(dis_y - 1, dis_x), sobelImg.at<unsigned short>(dis_y - 1, dis_x + 1));
	//printf("%x,%x %x \r\n", sobelImg.at<unsigned short>(dis_y    , dis_x - 1), sobelImg.at<unsigned short>(dis_y    , dis_x), sobelImg.at<unsigned short>(dis_y    , dis_x + 1));
	//printf("%x,%x %x \r\n", sobelImg.at<unsigned short>(dis_y + 1, dis_x - 1), sobelImg.at<unsigned short>(dis_y + 1, dis_x), sobelImg.at<unsigned short>(dis_y + 1, dis_x + 1));
	//
	//printf("-----------dirImg------------\r\n");
	//printf("%x,%x %x \r\n", dirImg.at<uchar>(dis_y - 1, dis_x - 1), dirImg.at<uchar>(dis_y - 1, dis_x), dirImg.at<uchar>(dis_y - 1, dis_x + 1));
	//printf("%x,%x %x \r\n", dirImg.at<uchar>(dis_y, dis_x - 1), dirImg.at<uchar>(dis_y, dis_x), dirImg.at<uchar>(dis_y, dis_x + 1));
	//printf("%x,%x %x \r\n", dirImg.at<uchar>(dis_y + 1, dis_x - 1), dirImg.at<uchar>(dis_y + 1, dis_x), dirImg.at<uchar>(dis_y + 1, dis_x + 1));

	//printf("-----------nonMaxImg------------\r\n");
	//printf("%x,%x %x \r\n", nonMaxImg.at<unsigned short>(dis_y - 1, dis_x - 1), nonMaxImg.at<unsigned short>(dis_y - 1, dis_x), nonMaxImg.at<unsigned short>(dis_y - 1, dis_x + 1));
	//printf("%x,%x %x \r\n", nonMaxImg.at<unsigned short>(dis_y    , dis_x - 1), nonMaxImg.at<unsigned short>(dis_y    , dis_x), nonMaxImg.at<unsigned short>(dis_y    , dis_x + 1));
	//printf("%x,%x %x \r\n", nonMaxImg.at<unsigned short>(dis_y + 1, dis_x - 1), nonMaxImg.at<unsigned short>(dis_y + 1, dis_x), nonMaxImg.at<unsigned short>(dis_y + 1, dis_x + 1));





	
	nonMaxImg.convertTo(nonMaxImg_show, CV_8UC1);
	cv::imshow("nonMaxImg", nonMaxImg_show);
	cv::imwrite("fpga_canny_nonMaxImg.bmp", nonMaxImg_show);



	//边缘标记, 这里最优的办法应该用栈的操作来实现。这里只是为了便于理解
	cv::Mat flgImg(src.rows, src.cols, CV_8UC1);//存放标记 0=不是是边缘,1=可能是边缘,2=是边缘
	uchar *stack[65536];// 存放 边缘点的地址
	uchar **p_stack_top;
	uchar **p_stack_bottom;
	unsigned short *p_nonMax;//指向非极大值处理后的图像
	uchar *p_flgImg;

	memset(&stack[0], 0,sizeof(stack));
	p_stack_top = &stack[0];
	p_stack_bottom = &stack[0];

	p_nonMax = (unsigned short*)nonMaxImg.data;
	p_flgImg = flgImg.data ;


	for (unsigned int y = 2; y< rows - 1; y++)
	{
		for (unsigned int x = 2; x < cols - 1; x++)
		{

			if (*(p_nonMax + y*cols + x) <= minVal)					// 当前像素小于极大值
			{
				*(p_flgImg + y*cols + x) = 0;						// 不是边缘点
			}
			else if (*(p_nonMax + y*cols + x) > maxVal				// 当前像素大于极大值
					&& (*(p_nonMax + (y - 1)*cols + x) <= maxVal)	//上一个像素不是边缘点
					&& (*(p_nonMax + y*cols + x -1) <= maxVal)		// 前一个像素不是边缘点
					)	
			{
				
				*(p_flgImg + y*cols + x) = 2;						// 标记为边缘点(起点)
				*p_stack_top = (p_flgImg + y*cols + x);				
				p_stack_top++;

			}
			else
			{
				*(p_flgImg + y*cols + x) = 1;						// 可能是边缘点
			}
	
		}

	}

	cv::imshow("flgImg", flgImg);

	 更新标记,对可能是边缘的点进行处理(边缘追踪)
	uchar  *p_tmp;
	while (p_stack_top - p_stack_bottom)
	{
		p_stack_top--;
		p_tmp = *p_stack_top;
		
		if (*(p_tmp - cols - 1) == 1)
		{
			*p_stack_top = p_tmp - cols - 1;
			*(p_tmp - cols - 1) = 2;
			p_stack_top++;
			
		}
		if (*(p_tmp - cols) == 1)
		{
			*p_stack_top = p_tmp - cols ;
			*(p_tmp - cols) = 2;
			p_stack_top++;

		}

		if (*(p_tmp - cols + 1) == 1)
		{
			*p_stack_top = p_tmp - cols + 1;
			*(p_tmp - cols + 1) = 2;
			p_stack_top++;

		}
		if (*(p_tmp - 1) == 1)
		{
			*p_stack_top = p_tmp  - 1;
			*(p_tmp - 1) = 2;
			p_stack_top++;
		}

		if (*(p_tmp + 1) == 1)
		{
			*p_stack_top = p_tmp + 1;
			*(p_tmp + 1 ) = 2;
			p_stack_top++;
		}
		if (*(p_tmp + cols - 1) == 1)
		{
			*p_stack_top = p_tmp + cols - 1;
			*(p_tmp + cols - 1) = 2;
			p_stack_top++;
		}
		if (*(p_tmp + cols) == 1)
		{
			*p_stack_top = p_tmp + cols ;
			*(p_tmp + cols) = 2;
			p_stack_top++;
		}
		if (*(p_tmp + cols + 1) == 1)
		{
			*p_stack_top = p_tmp + cols + 1;
			*(p_tmp + cols + 1) = 2;
			p_stack_top++;
		}


	}

	生成边缘图像
	uchar *p_dstImg;
	p_dstImg = dst.data;
	for (unsigned int y = 2; y< rows - 1; y++)
	{
		for (unsigned int x = 2; x < cols - 1; x++)
		{

			if (*(p_flgImg + y*cols + x) == 2)
			{
				*(p_dstImg + y*cols + x) = 255;
			}
			else

			{
				*(p_dstImg + y*cols + x) = 0;
			}
		

		}

	}



	cv::imshow("fpga_canny_Dst", dst);
	cv::imwrite("fpga_canny_Dst.bmp", dst);



}
640x480原图
640x480 sobel 图像
640x480 非极大值抑制图
生成的 640x480 Canny 图

 

Opencv   的canny 图

 

 

 

因能力所限,难免有理解不到位之处,欢迎大家批评指正

更多分享,请关注微信公众号:FPGA历险记

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DG敲码人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值