主要包含了图像的灰度直方图、灰度图像的信息熵、圆形度……等35个图像通用特征的提取;
Character.h
#include <vector>
#include <iterator>
#include <numeric>
#include <algorithm>
#include <functional>
using namespace std;
// 存储目标物体的椭圆结构特征
typedef struct _FitEllipse
{
float e; // 离心率
float height; // 长轴长
float width; // 短轴长
float fit; // 拟合度
float angle; // 椭圆倾角
float length; // 椭圆周长
float area; // 椭圆面积
} FitEllipse;
// 存储三个 double 类型的数组
typedef struct _Scalar
{
public:
inline _Scalar Scalar(double val0 = 0, double val1 = 0, double val2 = 0)
{
val[0] = val0;
val[1] = val1;
val[2] = val2;
}
public:
double val[3];
} Scalar;
class ImageProcess
{
public:
ImageProcess();
virtual ~ImageProcess();
public:
// load image
IplImage* loadImage(const char* fileName, unsigned colorMode = 1);
// binaryzation of image
IplImage* binaryImg(IplImage* img, unsigned threshold, int thresholdType);
public:
// get binary image
IplImage* getBinaryImg() { return m_binaryImg; }
private:
IplImage* m_binaryImg; // binary image
};
class Character : public ImageProcess
{
public:
Character();
~Character();
public:
// 查找轮廓
CvSeq* findImgContours(IplImage* img, CvMemStorage* storage);
// 计算目标区域的均值和方差
void calAvgSdv(IplImage* img, CvSeq* seq, Scalar *ave = NULL, Scalar *sdv = NULL);
// 计算直方图
template<typename _T>
void calHist(IplImage* img, CvSeq* seq, _T hist[]);
// 计算概率最大的灰度值
int calHistValue(IplImage* img, CvSeq* seq);
// 计算图像的熵值
double calEntropy(IplImage* img, CvSeq* seq);
// 计算图像的椭圆拟合度
FitEllipse calEllipseCharater(CvSeq* seq);
// 计算矩形度
double calRectFit(CvSeq* seq);
// 计算紧密度
double calHullFit(CvSeq* seq, CvMemStorage* storage = 0);
// 计算目标区域面积
int calROIArea(IplImage* img, CvSeq* seq);
// 计算重心
CvPoint calBaryCentre(IplImage* img, CvSeq* seq);
// 计算形状参数(紧凑性)
double calCompactness(CvSeq* seq);
// 计算偏心率
double calEccentricity(CvSeq* seq);
// 计算圆形性
double calCircularity(IplImage* img, CvSeq* seq);
// 计算 7个 hu 矩
CvHuMoments calHuMoments(CvSeq* seq);
// 获取面积最大的轮廓
CvSeq* getMaxAreaContour(CvSeq* seq);
private:
CvSeq* m_seq;
FitEllipse m_ellipse;
int m_nArea;
int m_nX;
int m_nY;
};
characer.cpp
#include "stdafx.h"
#include "Character.h"
ImageProcess::ImageProcess()
:m_binaryImg(NULL)
{
}
ImageProcess::~ImageProcess()
{
if (m_binaryImg != NULL)
{
cvReleaseImage(&m_binaryImg);
m_binaryImg = NULL;
}
}
IplImage* ImageProcess::loadImage(const char* fileName, unsigned colorMode /* = 1 */)
{
return cvLoadImage(fileName, colorMode);
}
IplImage* ImageProcess::binaryImg(IplImage* img, unsigned threshold, int thresholdType)
{
#ifdef _DEBUG
assert(img != NULL);
assert((img->nChannels == 1) || (img->nChannels == 3));
assert((threshold >= 0) && (threshold <= 255));
#endif
m_binaryImg = cvCreateImage(cvGetSize(img), img->depth, 1);
if (img->nChannels == 1)
{
cvThreshold(img, m_binaryImg, threshold, 255, thresholdType);
}
else
{
IplImage* rImg = cvCreateImage(cvGetSize(img), img->depth, 1);
cvSplit(img, NULL, NULL, rImg, NULL);
cvThreshold(rImg, m_binaryImg, threshold, 255, thresholdType);
if (rImg != NULL)
{
cvReleaseImage(&rImg);
}
}
IplConvKernel* element = cvCreateStructuringElementEx(5, 5, 2, 2, CV_SHAPE_ELLIPSE);
cvSmooth(m_binaryImg, m_binaryImg, CV_MEDIAN);
cvErode(m_binaryImg, m_binaryImg, element, 1);
cvDilate(m_binaryImg, m_binaryImg, element, 1);
cvReleaseStructuringElement(&element);
return m_binaryImg;
}
Character::Character()
: m_nArea(0)
{
}
Character::~Character()
{
}
CvSeq* Character::getMaxAreaContour(CvSeq* seq)
{
#ifdef _DEBUG
assert(seq != 0);
#endif
CvSeq* maxSeq = 0;
for (CvSeq* c = seq; c != NULL; c = c->h_next)
{
float area = fabs(cvContourArea(c, CV_WHOLE_SEQ)); // get area of contours
if (m_nArea < area)
{
maxSeq = c;
}
}
return maxSeq;
}
// 查找图像轮廓
CvSeq* Character::findImgContours(IplImage* img, CvMemStorage* storage)
{
if (img->nChannels != 1)
return NULL;
CvSeq *seq = 0;
cvFindContours(img, storage, &seq, sizeof(CvContour),
CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); // 检索轮廓
for (; seq != 0; seq = seq->h_next)
{
m_seq = getMaxAreaContour(seq);
}
return m_seq;
}
// 计算目标区域的均值和方差
void Character::calAvgSdv(IplImage* img, CvSeq* seq, Scalar *ave, Scalar *sdv)
{
IplImage* bitImg = getBinaryImg();
CvRect rect = { 0 };
for (; seq != 0; seq = seq->h_next)
{
// 画出二值图像的轮廓
cvDrawContours(bitImg, seq, cvScalarAll(255), cvScalarAll(255),
-1, CV_FILLED, 8);
rect = cvBoundingRect(seq, 0);
}
if (img->nChannels == 1) // 如果是单通道图像
{
vector<double> vec;
for (int i = rect.x; i < rect.x + rect.width; ++i)
{
for (int j = rect.y; j < rect.y + rect.height; ++j)
{
CvScalar s = cvGet2D(bitImg, j, i);
if (s.val[0] == 255)
{
s = cvGet2D(img, j, i);
vec.push_back(s.val[0]);
}
}
} // end for i
// 计算均值
if (ave != NULL)
{
if (!vec.empty())
{
ave->val[0] = (double)(accumulate(vec.begin(), vec.end(), 0))
/ vec.size();
}
}
// 计算方差
if (sdv != NULL)
{
if (! vec.empty())
{
double sum = 0.0;
for_each(vec.begin(), vec.end(), [&](const double d)
{
sum += pow((d - ave->val[0]), 2);
});
sdv->val[0] = sqrt(sum / vec.size());
}
}
}// end if img->nChannel == 1
if (img->nChannels == 3) // 如果图像为 3 通道图像
{
vector<double> vec[3];
for (int i = 0; i < rect.x + rect.width; ++i)
{
for (int j = 0; j < rect.y + rect.height; ++j)
{
CvScalar s = cvGet2D(bitImg, j, i);
if (s.val[0] == 255)
{
s = cvGet2D(img, j, i);
vec[0].push_back(s.val[0]);
vec[1].push_back(s.val[1]);
vec[2].push_back(s.val[2]);
}
}
}// end for i
// 计算均值
if (ave != NULL)
{
for (int i = 0; i < 3; ++i)
{
if (! vec[i].empty())
{
ave->val[i] = (double)(accumulate(vec[i].begin(), vec[i].end(), 0))
/ vec[i].size();
}
}
}// end ave
if (sdv != NULL)
{
for (int i = 0; i < 3; ++i)
{
if (!vec[i].empty())
{
double sum = 0.0;
for_each(vec[i].begin(), vec[i].end(), [&](const double d)
{
sum += pow(d - ave->val[i], 2);
});
sdv->val[i] = sqrt(sum / vec->size());
}
}
}// end if sdv
}// end if img->nChannels == 3
}
// 计算直方图
template<typename _T>
void Character::calHist(IplImage* img, CvSeq* seq, _T hist[])
{
IplImage* grayImg = NULL;
IplImage* bitImg = getBinaryImg();
grayImg = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
if (img->nChannels == 3)
cvCvtColor(img, grayImg, CV_RGB2GRAY);
else
cvCopy(img, grayImg, NULL);
CvRect rect = { 0 };
for (; seq != 0; seq = seq->h_next)
{
cvDrawContours(bitImg, seq, cvScalarAll(255), cvScalarAll(255),
-1, CV_FILLED, 8);
rect = cvBoundingRect(seq, 0);
}
for (int i = rect.x; i < rect.x + rect.width; ++i)
{
for (int j = rect.y; j < rect.y + rect.height; ++j)
{
CvScalar s = cvGet2D(bitImg, j, i);
if (s.val[0] == 255)
{
s = cvGet2D(grayImg, j, i);
int val = s.val[0];
++hist[val];
}
}
}
// 释放内存
cvReleaseImage(&grayImg);
}
// 利用直方图,统计出出现频率最高的灰度值
int Character::calHistValue(IplImage* img, CvSeq* seq)
{
int hist[256] = { 0 };
calHist(img, seq, hist);
int maxVal = 0;
int grayVal = 0;
for (int i = 0; i < 256; ++i)
{
if (maxVal < hist[i])
{
maxVal = hist[i];
grayVal = i; // 求出出现概率最大的灰度值
}
}
return grayVal;
}
// 计算图像的熵值
double Character::calEntropy(IplImage* img, CvSeq* seq)
{
// 计算直方图
double hist[256] = { 0.0 };
calHist(img, seq, hist);
int total = 0;
for (int i = 0; i < 256; ++i)
{
total += hist[i];
}
// 计算每个灰度值的概率
double temp[256] = { 0.0 };
for (int i = 0; i < 256; ++i)
{
temp[i] = hist[i] / total;
}
// 计算信息熵
double entropy = 0.0;
for (int i = 0; i < 256; ++i)
{
if (temp[i] == 0.0)
{
entropy = entropy;
}
else
{
entropy -= temp[i] * (log(temp[i]) / log(2.0));
}
}
return entropy;
}
// 提取椭圆特征
FitEllipse Character::calEllipseCharater(CvSeq* seq)
{
CvBox2D box = { 0 };
double area = 0;
for (; seq != 0; seq = seq->h_next)
{
// 计算拟合椭圆
box = cvFitEllipse2(seq);
area = fabs(cvContourArea(seq, CV_WHOLE_SEQ));
}
box.angle = 90 - box.angle;
m_ellipse.angle = box.angle; // 倾角
float height = box.size.height;
float width = box.size.width;
// 取较大的值为椭圆的长轴长,较小的值为短轴长
if (height > width)
{
m_ellipse.height = height;
m_ellipse.width = width;
}
else
{
m_ellipse.height = width;
m_ellipse.width = height;
}
float a = 0, b = 0;
a = m_ellipse.height; // 长轴长
b = m_ellipse.width; // 短轴长
// 周长
float l = 3.14f * b + 2 * (a - b);
m_ellipse.length = l;
float ellArea = 3.14f * a * b / 4; // 椭圆面积
m_ellipse.area = ellArea;
// 椭圆拟合度
float fit = area / ellArea;
m_ellipse.fit = fit;
// 离心率
float c = sqrt((a * a) - (b * b));
float e = c / a;
m_ellipse.e = e;
return m_ellipse;
}
// 计算矩形度
double Character::calRectFit(CvSeq* seq)
{
CvBox2D rect;
double area = 0.0;
for (; seq != 0; seq = seq->h_next)
{
rect = cvMinAreaRect2(seq); // 最小拟合矩形
area = fabs(cvContourArea(seq, CV_WHOLE_SEQ));
}
double rectArea = rect.size.height * rect.size.width;
double fit = area / rectArea; // 矩形度
return fit;
}
// 计算凸包
double Character::calHullFit(CvSeq* seq, CvMemStorage* storage)
{
CvSeq* hull = 0;
double hullArea = 0.0, area = 0.0;
for (; seq != 0; seq = seq->h_next)
{
// 最内外接凸多边形,多边形逼近
//hull = cvApproxPoly(seq, sizeof(CvContour), storage, CV_POLY_APPROX_DP, 3, 1);
// 凸包,最小外接凸多边形
hull = cvConvexHull2(seq, 0, CV_CLOCKWISE, 1);
hullArea = fabs(cvContourArea(hull, CV_WHOLE_SEQ));
area = fabs(cvContourArea(seq, CV_WHOLE_SEQ));
}
double fit = area / hullArea;
return fit;
}
// 计算目标区域面积
int Character::calROIArea(IplImage* img,CvSeq* seq)
{
if (img == NULL || img->nChannels != 1)
return 0;
CvRect rect = { 0 };
for (; seq != 0; seq = seq->h_next)
{
cvDrawContours(img, seq, cvScalarAll(255), cvScalarAll(255), -1, CV_FILLED, 8);
rect = cvBoundingRect(seq, 0);
}
for (int i = rect.x; i < rect.x + rect.width; ++i)
{
for (int j = rect.y; j < rect.y + rect.height; ++j)
{
CvScalar s = cvGet2D(img, j, i);
if (s.val[0] == 255)
{
++m_nArea;
}
}
}
return m_nArea;
}
// 计算图像重心
CvPoint Character::calBaryCentre(IplImage* img,CvSeq* seq)
{
if (img == NULL || img->nChannels != 1)
return cvPoint(0, 0);
double m00 = 0.0, m10 = 0.0, m01 = 0.0;
for (; seq != 0; seq = seq->h_next)
{
CvMoments moment;
cvMoments(img, &moment, 1);
m00 = cvGetSpatialMoment(&moment, 0, 0);
m10 = cvGetSpatialMoment(&moment, 1, 0);
m01 = cvGetSpatialMoment(&moment, 0, 1);
}
// 图像重心坐标
CvPoint pt = { 0 };
pt.x =(int)(m10 / m00);
pt.y =(int)(m01 / m00);
return pt;
}
// 计算形状参数(紧密度)
double Character::calCompactness(CvSeq* seq)
{
double area = 0, length = 0;
for (; seq != 0; seq = seq->h_next)
{
area = fabs(cvContourArea(seq, CV_WHOLE_SEQ));
length = cvArcLength(seq, CV_WHOLE_SEQ);
}
double f = (length * length) / (4 * 3.14 * area);
return f;
}
// 计算偏心率
double Character::calEccentricity(CvSeq* seq)
{
CvBox2D rect = { 0 };
for (; seq != 0; seq = seq->h_next)
{
rect = cvMinAreaRect2(seq); // 最小外接矩形
}
double f = 0.0;
(rect.size.height > rect.size.width)
? (f = rect.size.height / rect.size.width)
: (f = rect.size.width / rect.size.height);
return f;
}
// 计算圆形性
double Character::calCircularity(IplImage* img, CvSeq* seq)
{
if (img == NULL || img->nChannels != 1)
return 0.0;
double m00 = 0.0, m10 = 0.0, m01 = 0.0;
// 以下代码计算图像的圆形性
float UR = 0.f; // 从区域重心到边界点的平均距离
float PR = 0.f; // 从区域重心到边界点的距离的方差
for (; seq != 0; seq = seq->h_next)
{
CvMoments moment;
cvMoments(img, &moment, 1);
m00 = cvGetSpatialMoment(&moment, 0, 0);
m10 = cvGetSpatialMoment(&moment, 1, 0);
m01 = cvGetSpatialMoment(&moment, 0, 1);
// 图像重心坐标
int x = (int)(m10 / m00);
int y = (int)(m01 / m00);
float sum = 0;
int count = seq->total;
CvPoint* pt;
for (int i = 0; i < count; ++i)
{
pt = (CvPoint*)cvGetSeqElem(seq, i);
sum += sqrt(powf(pt->x - x, 2) + powf(pt->y - y, 2));
}
UR = sum / count;
float tmp = 0.f;
sum = 0;
for (int i = 0; i < count; ++i)
{
pt = (CvPoint*)cvGetSeqElem(seq, i);
tmp = sqrt(powf(pt->x - x, 2) + powf(pt->y - y, 2));
sum += powf(tmp - UR, 2);
}
PR = sum / count;
}
// 计算圆形性
double circularity = UR / PR;
return circularity;
}
// 计算 7 个 hu 不变矩
CvHuMoments Character::calHuMoments(CvSeq* seq)
{
CvHuMoments huMoments;
for (; seq != 0; seq = seq->h_next)
{
// 提取7个 hu 不变矩
CvMoments moments;
cvMoments(seq, &moments, 1);
cvGetHuMoments(&moments, &huMoments);
}
return huMoments;
}