Canny算子的原理已经有很多博文在叙述了,在这里就不做过多介绍。以下是我将opencv的程序转为c++的结果。代码写的有点拉跨。发着玩。
第一部分bmp图像读写(如果是24位图进行灰度化)。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cmath>
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <math.h>
#include<time.h>
#include <iomanip>
#pragma warning(disable:4996)
//#define PI 3.1415926535
using namespace std;
BITMAPFILEHEADER bmpFileHeader; //位图文件头
BITMAPINFOHEADER bmpInfoHeader; //位图信息头
RGBQUAD* pColorTable; //颜色表,注:24位真彩色图无颜色表
unsigned char* pBmpData; //位图数据
unsigned char* pGrayData; //灰度图像数据
unsigned char* dp; //定义写入位图数据的指针
int height; //定义图像高度
int width; //定义图像宽度
int biBitCount; //图像类型,每像素所占的位数
int lineBytes; //定义变量,即图像每行像素所占的字节数(必须为4的倍数)
//********************定义读取位图函数**************************//
bool readBmp(char* fileName) {
FILE* fp = fopen(fileName, "rb"); //以二进制读方式打开
if (NULL == fp) {
cout << "File is opened failure!" << endl;
return FALSE;
}
//读取数据
fread(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, fp);
fread(&bmpInfoHeader, sizeof(BITMAPINFOHEADER), 1, fp);
height = bmpInfoHeader.biHeight;
width = bmpInfoHeader.biWidth;
//如果是8位灰度图像
if (bmpInfoHeader.biBitCount == 8) {
lineBytes = ((bmpInfoHeader.biWidth + 3) / 4) * 4;
bmpInfoHeader.biSizeImage = lineBytes * bmpInfoHeader.biHeight;
pGrayData = new unsigned char[bmpInfoHeader.biSizeImage]; //申请空间,大小为位图数据大小
//dp = new unsigned char[bmpInfoHeader.biSizeImage];
pColorTable = new RGBQUAD[256];//定义颜色表数据空间
fread(pColorTable, sizeof(RGBQUAD), 256, fp);//将fp数据读入颜色表指针空间
fread(pGrayData, 1, bmpInfoHeader.biSizeImage, fp);
}
//若是24位彩色图,则进行灰度化处理
if (bmpInfoHeader.biBitCount == 24) {
bmpFileHeader.bfOffBits += (sizeof(RGBQUAD) * 256); //原先的54字节加上颜色表
bmpInfoHeader.biBitCount = 8;
lineBytes = (bmpInfoHeader.biWidth + 3) / 4 * 4;
int oldLineBytes = (bmpInfoHeader.biWidth * 3 + 3) / 4 * 4;
int oldSize = bmpInfoHeader.biSizeImage; //原图数据大小
bmpInfoHeader.biSizeImage = lineBytes * bmpInfoHeader.biHeight;
//定义灰度图像的颜色表
pColorTable = new RGBQUAD[256];
for (int i = 0; i < 256; i++) {
(*(pColorTable + i)).rgbBlue = i;
(*(pColorTable + i)).rgbGreen = i;
(*(pColorTable + i)).rgbRed = i;
(*(pColorTable + i)).rgbReserved = 0;
}
//将RGB转换为灰度值
int red, green, blue;
pBmpData = new unsigned char[oldLineBytes * bmpInfoHeader.biHeight]; //申请空间,大小为位图数据大小
fread(pBmpData, 1, oldSize, fp);
pGrayData = new unsigned char[bmpInfoHeader.biSizeImage];
//dp = new unsigned char[bmpInfoHeader.biSizeImage];
memset(pGrayData, 0, bmpInfoHeader.biSizeImage); //为pGrayData赋初值
for (int i = 0; i < bmpInfoHeader.biHeight; i++) {
for (int j = 0; j < bmpInfoHeader.biWidth; j++) {
blue = *(pBmpData + i * oldLineBytes + 3 * j);
green = *(pBmpData + i * oldLineBytes + 3 * j + 1);
red = *(pBmpData + i * oldLineBytes + 3 * j + 2);
*(pGrayData + i * lineBytes + j) = (red * 0.299 + green * 0.587 + blue * 0.114);
}
}
delete[]pBmpData;
}
fclose(fp); //关闭文件
return TRUE;
}
//**********************写入位图****************************//
bool writeBmp(char* fileName, unsigned char* dp )
{
FILE* fp = fopen(fileName, "wb"); //以二进制写方式打开
if (NULL == fp) {
cout << "File is opened failure!" << endl;
return FALSE;
}
//写入数据
fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, fp);
fwrite(&bmpInfoHeader, sizeof(BITMAPINFOHEADER), 1, fp);
fwrite(pColorTable, sizeof(RGBQUAD), 256, fp);
fwrite(dp, bmpInfoHeader.biSizeImage, 1, fp);
fclose(fp);
//释放内存空间
delete[]pColorTable;
delete[]dp;
return TRUE;
}
第2部分高斯滤波
void MakeGauss(double sigma, double** gaus, int size)
{
int center;
double PI = 3.14159;
double dSum;
dSum = 0;
center = size / 2;
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
gaus[i][j] = (1 / (2 * PI * sigma * sigma)) * exp(-((i - center) * (i - center) + (j - center) * (j - center)) / (2 * sigma * sigma)); //二维高斯分布公式
dSum += gaus[i][j];
}
}
//归一化
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
gaus[i][j] /= dSum;
cout << gaus[i][j] << " ";
}
cout << endl << endl;
}
}
//******************高斯滤波*************************
//第一个参数pUnchImg是待滤波原始图像;
//第二个参数pUnchSmthdImg是滤波后输出图像;
//第三个参数gaus是一个指向含有N个double类型数组的指针;
//第四个参数size是滤波核的尺寸
//***********************************************************
///*******************************************二维高斯卷积*********************************************/
void GaussianSmooth(unsigned char* pUnchImg, int width, int height, double** gaus, int size, unsigned char* pUnchSmthdImg)
{
double *gausArray=new double[size * size];
for (int i = 0; i < size * size; i++)
{
gausArray[i] = 0; //赋初值,空间分配
}
int array = 0;
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
gausArray[array] = gaus[i][j]; //二维数组到一维 方便计算
array++;
}
}
//滤波
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int k = 0;
for (int l = -size / 2; l <= size / 2; l++)
{
for (int g = -size / 2; g <= size / 2; g++)
{
//以下处理针对滤波后图像边界处理,为超出边界的值赋值为边界值
int row = i + l;
int col = j + g;
row = row < 0 ? 0 : row;
row = row >= height ? height - 1 : row;
col = col < 0 ? 0 : col;
col = col >= width ? width - 1 : col;
//卷积和
*(pUnchSmthdImg + i * lineBytes + j) += gausArray[k] * *(pUnchImg + row * lineBytes + col);
k++;
}
}
}
}
delete[]gausArray;
gausArray = NULL;
}
第3部分sobel求梯度
//******************Sobel卷积因子计算X、Y方向梯度和梯度方向角********************
//第一个参数imageSourc原始灰度图像;
//第二个参数imageSobelX是X方向梯度图像;
//第三个参数imageSobelY是Y方向梯度图像;
//第四个参数pointDrection是梯度方向角数组指针(一维数据指针)
//*************************************************************
void SobelGradDirction(unsigned char* pUnchImg1, unsigned char* imageSobelX, unsigned char* imageSobelY, int width, int height, double*& pointDrection)
{
pointDrection = new double[(height - 1) * (width - 1)];
for (int i = 0; i < (height- 1) * (width - 1); i++)
{
pointDrection[i] = 0;
}
int* SobelX = new int[height * width];
int* SobelY = new int[height * width];
memset(SobelX, 0, height * width * sizeof(int)); //new和delete搭配 malloc和free搭配 分配内存与释放内存
memset(SobelY, 0, height * width * sizeof(int));
int k = 0;
int step = width;
for (int i = 1; i < (height - 1); i++)
{
for (int j = 1; j < (width - 1); j++)
{
//通过指针遍历图像上每一个像素
float gradY = pUnchImg1[(i - 1) * step + j + 1] + pUnchImg1[i * step + j + 1] * 2 + pUnchImg1[(i + 1) * step + j + 1] - pUnchImg1[(i - 1) * step + j - 1] - pUnchImg1[i * step + j - 1] * 2 - pUnchImg1[(i + 1) * step + j - 1];
SobelY[i * width + j] = (unsigned char)abs(gradY);
float gradX = pUnchImg1[(i + 1) * step + j - 1] + pUnchImg1[(i + 1) * step + j] * 2 + pUnchImg1[(i + 1) * step + j + 1] - pUnchImg1[(i - 1) * step + j - 1] - pUnchImg1[(i - 1) * step + j] * 2 - pUnchImg1[(i - 1) * step + j + 1];
SobelX[i * width + j] = (unsigned char)abs(gradX);
if (gradX == 0)
{
gradX = 0.00000000000000001; //防止除数为0异常
}
pointDrection[k] = atan(gradY / gradX) * 57.3;//弧度转换为度
pointDrection[k] += 90;
k++;
}
}
for (int i = 0; i < height * width; i++)
{
imageSobelX[i] = SobelX[i];
imageSobelY[i] = SobelY[i];
}
delete[]SobelX;
delete[]SobelY;
}
第4部分计算梯度幅值
void SobelAmplitude(unsigned char* imageGradX, unsigned char* imageGradY, int width, int wight, unsigned char* SobelAmpXY)
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
SobelAmpXY[i*width+j] = (unsigned char)sqrt(imageGradX[i*width+j] * imageGradX[i*width+j]+ imageGradY[i*width+j] * imageGradY[i*width+j]);
}
}
}
第5部分非极大值抑制
//******************局部极大值抑制*************************
//第一个参数imageInput输入的Sobel梯度图像;
//第二个参数imageOutPut是输出的局部极大值抑制图像;
//第三个参数pointDrection是图像上每个点的梯度方向数组指针
//*************************************************************
void LocalMaxValue(unsigned char* imageInput, double* pointDrection)
{
int k = 0;
for (int i = 1; i < height-1; i++)
{
for (int j = 1; j < width-1; j++)
{
int value00 = imageInput[(i - 1) * width + j - 1];
int value01 = imageInput[(i - 1) * width + j];
int value02 = imageInput[(i - 1) * width + j + 1];
int value10 = imageInput[(i) * width + j - 1];
int value11 = imageInput[(i) * width + j]; //当前像素点
int value12 = imageInput[(i) * width + j + 1];
int value20 = imageInput[(i + 1) * width + j - 1];
int value21 = imageInput[(i + 1) * width + j ];
int value22 = imageInput[(i + 1) * width + j + 1];
if (pointDrection[k] > 0 && pointDrection[k] <= 45)
{
if (value11 <= (value12 + (value02 - value12) * tan(pointDrection[i * width+ j])) || (value11 <= (value10 + (value20 - value10) * tan(pointDrection[i * width + j]))))
{
imageInput[i*width+j] = 0;
}
}
if (pointDrection[k] > 45 && pointDrection[k] <= 90)
{
if (value11 <= (value01 + (value02 - value01) / tan(pointDrection[i *height + j])) || value11 <= (value21 + (value20 - value21) / tan(pointDrection[i * height + j])))
{
imageInput[i*width+j] = 0;
}
}
if (pointDrection[k] > 90 && pointDrection[k] <= 135)
{
if (value11 <= (value01 + (value00 - value01) / tan(180 - pointDrection[i * height + j])) || value11 <= (value21 + (value22 - value21) / tan(180 - pointDrection[i * height + j])))
{
imageInput[i * width + j] = 0;
}
}
if (pointDrection[k] > 135 && pointDrection[k] <= 180)
{
if (value11 <= (value10 + (value00 - value10) * tan(180 - pointDrection[i * width + j])) || value11 <= (value12 + (value22 - value11) * tan(180 - pointDrection[i * width + j])))
{
imageInput[i * width + j] = 0;
}
}
k++;
}
}
}
第6部分双阈值迭代处理
void DoubleThresholdLink(unsigned char* imageInput, double lowThreshold, double highThreshold,int n)
{
for (int i = 1; i < height - 1; i++)
{
for (int j = 1; j < width - 1; j++)
{
if (imageInput[i * width + j] > lowThreshold && imageInput[i * width + j] < 255)
{
if (imageInput[(i-1) * width + (j-1)] == 255 || imageInput[(i - 1) * width + (j)] == 255 || imageInput[(i - 1) * width + (j +1)] == 255 ||
imageInput[(i) * width + (j - 1)] == 255 ||imageInput[(i) * width + (j)] || imageInput[(i) * width + (j +1)] == 255 ||
imageInput[(i + 1) * width + (j - 1)] == 255 || imageInput[(i + 1) * width + (j)] == 255 || imageInput[(i + 1) * width + (j + 1)] == 255)
{
imageInput[i * width + j] = 255;
if (n == 1)
{
break;
}
DoubleThresholdLink(imageInput, lowThreshold, highThreshold,n-1); //递归调用
}
else
{
imageInput[i * width + j] = 0;
}
}
}
}
}
第7部分主函数
int main()
{
//int width = 0;
//int height=0;
clock_t startTime, endTime;
startTime = clock();//计时开始
//读入指定BMP文件进内存
char readPath[] = "D:\\机器视觉学习工程\\lianxi\\3.bmp";
readBmp(readPath);
unsigned char* imgBuf = new unsigned char[lineBytes * height];
memset(imgBuf, 0, lineBytes * height);
int size = 5; //定义卷积核大小
double** gaus = new double* [size]; //卷积核数组
for (int i = 0; i < size; i++)
{
gaus[i] = new double[size]; //动态生成矩阵
}
MakeGauss(0.8, gaus, 5);
GaussianSmooth(pGrayData, width, height, gaus, 5, imgBuf);
unsigned char* imageSobelX = new unsigned char[lineBytes * height];
memset(imageSobelX, 0, lineBytes * height);
unsigned char* imageSobelY = new unsigned char[lineBytes * height];
memset(imageSobelY, 0, lineBytes * height);
unsigned char* SobelAmpXY = new unsigned char[lineBytes * height];
memset(SobelAmpXY, 0, lineBytes * height);
unsigned char* imageOutput = new unsigned char[lineBytes * height];
memset(imageOutput, 0, lineBytes * height);
double*pointDrection = new double[(height - 1) * (width - 1)];
for (int i = 0; i < (height - 1) * (width - 1); i++)
{
pointDrection[i] = 0;
}
SobelGradDirction(imgBuf, imageSobelX, imageSobelY, width, height, pointDrection); //梯度
SobelAmplitude(imageSobelX, imageSobelY, width, height, SobelAmpXY); //梯度幅值
LocalMaxValue(SobelAmpXY, pointDrection);
//高低阈值
DoubleThreshold(SobelAmpXY, 18, 31);
DoubleThresholdLink(SobelAmpXY, 18, 31,5);
//输出图像的信息
char writePath[] = "D:\\机器视觉学习工程\\lianxi\\3tidu3.bmp";
writeBmp(writePath, SobelAmpXY);
cout << "图像成功写入内存 " << endl;
ShellExecuteA(nullptr, "open", writePath, "", "", SW_SHOW); //打开写入的位图结果
endTime = clock();//计时结束
cout << "The run time is: " << (float)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
delete[]imgBuf;
//delete[]imageSobelX;
//delete[]imageSobelY;
//delete[]pBmpBuf;
if (biBitCount == 8)
delete[]pColorTable;
system("pause");
return 0;
}
二、实现结果
测试原图如下:
Canny边缘检测的结果如下:
文章来源原文:https://blog.csdn.net/dcrmg/article/details/52344902
最近整理了不少传统图像的算法及优化都是底层C++实现的。大部分都是照着opencv改的,看懂了改一改就行了,以后有时间更新吧。完事。