博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
边缘检测:canny
阅读量:4100 次
发布时间:2019-05-25

本文共 6100 字,大约阅读时间需要 20 分钟。

canny实现边缘检测

主要流程见主函数
主要参考:https://blog.csdn.net/dcrmg/article/details/52344902
https://www.cnblogs.com/love6tao/p/5152020.html
不过有些细节有问题,我改过来了

#include 
#include
#include
using namespace std;using namespace cv;#define PI 3.141592653589793void conv(char *G, Mat pic, Mat res){
int i, j, i1, j1; int tmp; for (i = 1; i < pic.rows-1; i++) {
for (j = 1; j < pic.cols - 1; j++) {
tmp = 0; for (i1 = -1; i1 <= 1; i1++) {
for (j1 = -1; j1 <= 1; j1++) {
tmp += pic.at
(i + i1, j + j1)*G[(j1 + 1) * 3 + i1 + 1]; } } res.at
(i, j) = tmp; } }}void merge1(Mat resX, Mat resY, Mat &res, Mat &theta){
int tmp; double theTmp; for (int i = 0; i < res.rows; i++) {
for (int j = 0; j < res.cols; j++) {
tmp = abs(resX.at
(i, j)) + abs(resY.at
(i, j)); //tmp = sqrt(resX.at
(i, j)*resX.at
(i, j) + resY.at
(i, j)*resY.at
(i, j)); if (tmp > 255) tmp = 255; res.at
(i,j) = tmp; theTmp = atan2(resY.at
(i, j), resX.at
(i, j))*180/PI; if (theTmp < 0) theTmp += 360; theta.at
(i, j) = theTmp; } }}void mySobel(Mat pic, Mat &src, Mat &theta){ char Gx[3 * 3] = { -1, 0, 1, -2, 0, 2, -1, 0, 1}; char Gy[3 * 3] = { 1, 2, 1, 0, 0, 0, -1, -2, -1}; Mat resX = Mat::zeros(pic.rows, pic.cols, CV_16SC1); Mat resY = Mat::zeros(pic.rows, pic.cols, CV_16SC1); conv(Gx, pic, resX); conv(Gy, pic, resY); merge1(resX, resY, src, theta);}void BGR2Gray(Mat src, Mat &grayPic){ for (int i = 0; i < src.rows; i++) { for (int j = 0; j < src.cols; j++) { //Gray = R*0.299 + G*0.587 + B*0.114 grayPic.at
(i, j) = src.at
(i, j)[0] * 0.114 + src.at
(i, j)[1] * 0.587 + src.at
(i, j)[2] * 0.299; } }}void getGaussKer(double **K, int size, double sigma){ int center = size / 2; double sum = 0; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { K[i][j] = exp(((i - center)*(i - center) + (j - center)*(j - center)) / (-2 * sigma*sigma));///最后还是需要归一化加不加系数无所谓 sum += (K[i][j]); } } for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { K[i][j] /= sum; } }}void GaussFilter(Mat pic, Mat &src, double **K, int size){ int center = size / 2; for (int i = 0; i < pic.rows; i++) { for (int j = 0; j < pic.cols; j++) { double tmp = 0; int nowRow; int nowCol; for (int i1 = -size / 2; i1 <= size / 2; i1++) { for (int j1 = -size / 2; j1 <= size / 2; j1++) { nowRow = i + i1; if (nowRow < 0|| nowRow>=pic.rows) continue; nowCol = j + j1; if (nowCol < 0 || nowCol >= pic.cols) continue; tmp += K[center + i1][center + j1] * pic.at
(nowRow, nowCol); } } if (tmp < 0) tmp = 0; if (tmp > 255) tmp = 255; src.at
(i, j) = tmp; } }}void locMaxValue(Mat amp, Mat theta, Mat &src){ Mat output = amp.clone();深拷贝 for (int i = 1; i < amp.rows - 1; i++) { for (int j = 1; j < amp.cols - 1; j++)///判断每个像素点,最外一圈8领域不完整就不管了 { float thetaNow = theta.at
(i, j); float k=abs(tan(thetaNow)); float g1, g2, g3, g4; float dTmp1, dTmp2; float nowGray = amp.at
(i, j); if ((thetaNow >= 90 && thetaNow < 135) || (thetaNow >= 270 && thetaNow < 315)) { g1 = output.at
(i - 1, j-1); g2 = output.at
(i - 1, j); g3 = output.at
(i + 1, j); g4 = output.at
(i + 1, j + 1); dTmp1 = g1 / k + (1 - 1 / k)*g2; dTmp2 = g4 / k + (1 - 1 / k)*g3; } else if ((thetaNow >= 135 && thetaNow < 180) || (thetaNow >= 315 && thetaNow < 360)) { g1 = output.at
(i - 1, j - 1); g2 = output.at
(i, j - 1); g3 = output.at
(i, j + 1); g4 = output.at
(i + 1, j + 1); dTmp1 = g1*k + (1 - k)*g2; dTmp2 = g4*k + (1 - k)*g3; } else if ((thetaNow >= 45 && thetaNow < 90) || (thetaNow >= 225 && thetaNow < 270)) { g1 = output.at
(i - 1, j ); g2 = output.at
(i-1, j + 1); g3 = output.at
(i-1, j ); g4 = output.at
(i + 1, j + 1); dTmp1 = g2 / k + (1 - 1 / k)*g1; dTmp2 = g3 / k + (1 - 1 / k)*g4; } else if (thetaNow >= 0 && thetaNow < 45 || (thetaNow >= 180 && thetaNow < 225)) { g1 = output.at
(i - 1, j+1); g2 = output.at
(i , j + 1); g3 = output.at
(i , j-1); g4 = output.at
(i + 1, j - 1); dTmp1 = g1*k + (1 - k)*g2; dTmp2 = g4*k + (1 - k)*g3; } if (nowGray < dTmp1 || nowGray < dTmp2) { output.at
(i, j) = 0; } } } src = output.clone();}void traceLink(Mat &src, int minValue, int i, int j){ int xn[8] = { 1,1,0,-1,-1,-1,0,1 }; int yn[8] = { 0, 1, 1, 1, 0, -1, -1, -1 }; int xx = 0; int yy = 0; bool change = true; while (change) { change = false; for (int k = 0; k < 8; k++) { xx = i + xn[k]; yy = j + yn[k]; if (src.at
(xx,yy)!=255&&src.at
(xx, yy) > minValue) { change = true; src.at
(xx, yy) = 255; i = xx; j = yy; break; } } }}void doubleThreshold(Mat &src, int maxValue, int minValue){ for (int i = 1; i < src.rows-1; i++) { for (int j = 1; j < src.cols-1; j++) { int value = src.at
(i, j); //int t=value; if (src.at
(i, j) >= maxValue) { src.at
(i, j) = 255; traceLink(src, minValue, i, j); } } } for (int i = 1; i < src.rows-1; i++) { for (int j = 1; j < src.cols-1; j++) { if (src.at
(i, j) != 255) { src.at
(i, j) = 0; } } }}int main(){ int size = 3; double sigma = 1; double **k = new double*[size]; for (int i = 0; i < size; i++) { k[i] = new double[size]; } Mat pic = imread("C:\\Users\\Thinkpad\\Desktop\\c++work\\pic\\lena.jpg", 1); Mat grayPic=Mat::zeros(pic.rows, pic.cols, CV_8UC1); Mat gaussPic=Mat::zeros(pic.rows, pic.cols, CV_8UC1); Mat amplitude = Mat::zeros(pic.rows, pic.cols, CV_8UC1); Mat theta = Mat::zeros(pic.rows, pic.cols, CV_32FC1); Mat src; BGR2Gray(pic, grayPic);转成灰度图 getGaussKer(k, size, sigma);///计算高斯核 GaussFilter(grayPic, gaussPic, k, 3);///计算高斯模糊后的图 mySobel(gaussPic, amplitude, theta);///用sobel算子计算幅值和角度 locMaxValue(amplitude, theta, src);非极大抑制,梯度方向不是极大值的进行抑制 doubleThreshold(src, 100, 60);双阈值链接 imshow("1", src); imshow("0", amplitude); waitKey(); /* for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { cout << k[i][j] << ","; } cout << endl; }*/ for (int i = 0; i < size; i++) { delete[]k[i]; } delete[]k; return 0;}

这里简单介绍下梯度算子的方向:通常OpenCV打开的的图像坐标原点在左上角,x轴方向向下,y轴方向向右,但是在选择算子进行梯度计算时比如sobel算子,其“X”方向的计算是右边减去左边,“Y”方向的计算是上边减去下边,这种梯度的计算方式和我们正常左手系坐标系的计算方式相同,也就是x轴方向向右,y轴方向向上,而dx,dy的计算与坐标系选取无关,但是由dx,dy计算得到的角度是与“X”轴正方向的夹角,所以无论dx,dy是基于哪个坐标系算的,sobel算子计算的梯度方向都是以右为x轴正方向的左手系坐标系的夹角,下图是基于此,我进行非极大抑制的计算过程草图

在这里插入图片描述

你可能感兴趣的文章
《反本能》读后感(一) | 是什么阻止了我们成功
查看>>
Redis | 事务机制
查看>>
Elasticsearch | 安装(Linux 环境)
查看>>
Elasticsearch | Kibana 安装与使用
查看>>
SpringBoot 2.0 | SpringBoot 集成 Elasticsearch
查看>>
分布式的冰与火 | 分布式日志收集 ELK 搭建
查看>>
分布式的冰与火 | 分布式事务解决方案 LCN
查看>>
Spring Cloud Alibaba 极速通关 | 分布式系统的流量防卫兵 Sentinel
查看>>
面试必问的设计模式 | 外观模式
查看>>
面试必问的设计模式 | 状态模式
查看>>
Spring-Cloud-Finchley | 路由网关 GateWay
查看>>
Spring Cloud Alibaba 极速通关 | Sentinel 整合 Apollo 实现配置持久化
查看>>
面试必问的设计模式 | 模板方法模式
查看>>
Redis | Redis 主从复制
查看>>
面试必问的设计模式 | 代理模式
查看>>
面试必问的设计模式 | 观察者模式
查看>>
Redis | Redis 哨兵模式
查看>>
精通Spring源码 | BeanFactoryPostProcessor
查看>>
精通 Spring 源码 | InstantiationAwareBeanPostProcessor(2)
查看>>
精通 Spring 源码 | InstantiationAwareBeanPostProcessor(1)
查看>>