其实刚开始的时候,看很多的书和教程讲绘图和彩色图像等,但是我觉得还是先学会灰度直方图,因为灰度的dims是1,如果dims是3的就是彩色,同时知道前面将的彩色图像的像素访问,相信很快就可以迁移过去的。
一、换个角度认识图像(直方图)
第一个就是当我们面对图像的时候,我们面对的是抽象的矩阵,如下图,下面是0-255的灰度图像的表示,密密麻麻的
那么我们做的直方图,其实就是对这些像素值的统计,看下图,其中Bin是条数,数据和范围是对图的解释,一看就懂
二、准备知识
如果想绘制出来直方图,先要知道几个函数
(1) Point类数据结构表示了二维坐标系下的点
Point point=Point(1,2);
(2)calcHist()绘制直方图
void calcHist(const Mat* arrays, intnarrays, const int* channels, InputArray mask, OutputArray
hist, int dims,const int* histSize, const float** ranges, bool uniform=true, boolaccumulate=false );
参数解释:
- arrays:输入的图像的指针,可以是多幅图像,所有的图像必须有同样的深度(CV_8U orCV_32F)。同时一副图像可以有多个channes。
- narrays:输入的图像的个数。
- channels:用来计算直方图的channes的数组。比如输入是2副图像,第一副图像有0,1,2共三个channel,第二幅图像只有0一个channel,那么输入就一共有4个channes,如果int channels[3] = {3, 2, 0},那么就表示是使用第二副图像的第一个通道和第一副图像的第2和第0个通道来计算直方图。
- mask:掩码。如果mask不为空,那么它必须是一个8位(CV_8U)的数组,并且它的大小的和arrays[i]的大小相同,值为1的点将用来计算直方图。
- hist:计算出来的直方图
- dims:计算出来的直方图的维数。
- histSize:在每一维上直方图的个数。简单把直方图看作一个一个的竖条的话,就是每一维上竖条的个数。
- ranges:用来进行统计的范围。比如 float rang1[] = {0, 20};float rang2[] = {30, 40}; const float*rangs[] = {rang1, rang2};那么就是对0,20和30,40范围的值进行统计。
- uniform:每一个竖条的宽度是否相等。
- accumulate: 是否累加。如果为true,在下次计算的时候不会首先清空hist。
画直线,在图像img中画一条颜色为color,粗细为thickness,类型为lineType的直线
(3)line() rectangle()画出直方图
void line(Mat& img, Point pt1, Pointpt2, const Scalar& color, int thickness=1,
int lineType=8, int shift=0)
//两点确认一条直线。
//lineType:直线类型
//shift:坐标小数点维数
//画一个单一的实矩形
void rectangle(Mat& img, Point pt1,Point pt2, const Scalar& color, int thickness=1,
int lineType=8, int shift=0)
//一条对角线的两个顶点可确定一个矩形
//pt1和pt2互为对顶点
//thickness为负值表示矩形为实矩形
三、绘制一维灰度直方图
<span style="font-size:18px;">#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
using namespace cv;
using namespace std;
void Help()
{
printf("\n\n\t\t\t欢迎来到直方图的世界!\n");
printf("\n\n ----------------------------------------------------------------------------\n");
}
int main()
{
Mat srcImage = imread("lena.jpg", 0);
imshow("原图",srcImage);
if(!srcImage.data) {cout << "fail to load image" << endl; return 0;}
system("color 1F");
Help();
MatND dstHist; // 在cv中用CvHistogram *hist = cvCreateHist
int dims = 1;
float hranges[2] = {0, 255};
const float *ranges[1] = {hranges}; // 这里需要为const类型
int size = 256;
int channels = 0;
//计算图像的直方图
calcHist(&srcImage, 1, &channels, Mat(), dstHist, dims, &size, ranges); // cv 中是cvCalcHist
int scale = 1;
Mat dstImage(size * scale, size, CV_8U, Scalar(0));
//获取最大值和最小值
double minValue = 0;
double maxValue = 0;
minMaxLoc(dstHist,&minValue, &maxValue, 0, 0); // 在cv中用的是cvGetMinMaxHistValue
//绘制出直方图
int hpt = saturate_cast<int>(0.9 * size);
for(int i = 0; i < 256; i++)
{
float binValue = dstHist.at<float>(i); // 注意hist中是float类型
int realValue = saturate_cast<int>(binValue * hpt/maxValue);
//rectangle(dstImage,Point(i*scale, size - 1), Point((i+1)*scale - 1, size - realValue), Scalar(255));
line(dstImage,Point(i*scale,size-1),Point((i+1)*scale-1,size-realValue),Scalar(255));
}
imshow("一维直方图", dstImage);
waitKey(0);
return 0;
}</span>
其实我们有时候想改变量化,上面的表示是256,我们可以50 100都可以,这里用过滑块的知识,所以不想多讲,只是提供一个别人写的参考,尊重原作者,写的很好
<span style="font-size:18px;">#include "cv.h"
#include "highgui.h"
#include <stdio.h>
#include <ctype.h>
using namespace std;
using namespace cv;
IplImage *src = 0;
IplImage *histimg = 0;
CvHistogram *hist = 0;
int hdims = 50; // 划分HIST的初始个数,越高越精确
//滚动条函数
void HIST(int t)
{
float hranges_arr[] = {0,255};
float* hranges = hranges_arr;
int bin_w;
int bin_u;
float max;
int i;
char string[10];
CvFont font;
cvInitFont( &font, CV_FONT_HERSHEY_PLAIN,1, 1, 0, 1, 8);//字体结构初始化
if(hdims==0)
{
printf("直方图条数不能为零!\n");
}
else
{
hist = cvCreateHist( 1, &hdims, CV_HIST_ARRAY, &hranges, 1 ); // 创建直方图
histimg = cvCreateImage(cvSize(800,512),8,3);
cvZero( histimg );
cvCalcHist( &src, hist, 0, 0 ); // 计算直方图
cvGetMinMaxHistValue(hist,NULL,&max,NULL,NULL);//寻找最大值及其位置
//printf("max_val:%f \n",max_val);
cvZero( histimg );
double bin_w =(double) histimg->width / hdims; // hdims: 条的个数,则 bin_w 为条的宽度
double bin_u = (double)histimg->height/ max; max: 最高条的像素个数,则 bin_u 为单个像素的高度
// 画直方图
for(int i=0;i<hdims;i++)
{
CvPoint p0=cvPoint(i*bin_w,histimg->height);
int val=cvGetReal1D(hist->bins,i);
CvPoint p1=cvPoint((i+1)*bin_w,histimg->height-cvGetReal1D(hist->bins,i)*bin_u);
cvRectangle(histimg,p0,p1,cvScalar(0,255),1,8,0);
}
//画纵坐标刻度(像素个数)
int kedu=0;
for(int i=1;kedu<max;i++)
{
kedu=i*max/10;
itoa(kedu,string,10);//把一个整数转换为字符串
//在图像中显示文本字符串
cvPutText(histimg, string , cvPoint(0,histimg->height-kedu*bin_u), &font, CV_RGB(0,255,255));
}
//画横坐标刻度(像素灰度值)
kedu=0;
for(int i=1;kedu<256;i++)
{
kedu=i*20;
itoa(kedu,string,10);//把一个整数转换为字符串
//在图像中显示文本字符串
cvPutText(histimg, string , cvPoint(kedu*(histimg->width / 256),histimg->height), &font, CV_RGB(255,0,0));
}
cvShowImage( "Histogram", histimg );
}
}
int main( int argc, char** argv )
{
argc=2;
argv[1]="lena.jpg";
if( argc != 2 || (src=cvLoadImage(argv[1], 0)) == NULL) // force to gray image
return -1;
cvNamedWindow( "src", 1);
cvShowImage( "src", src);
cvNamedWindow( "Histogram", 1 );
cvCreateTrackbar( "hdims", "src", &hdims, 256, HIST );
HIST(0);
cvWaitKey(0);
cvDestroyWindow("src");
cvDestroyWindow("Histogram");
cvReleaseImage( &src );
cvReleaseImage( &histimg );
cvReleaseHist ( &hist );
return 0;
}</span>
三、彩色直方图
这里不想过多的介绍,以后讲彩色图像的时候会具体的说
四、matlab辅助
一个imhist()函数就搞定了
<span style="font-size:18px;">clear;
%%读入图像
a=imread('cameraman.tif');
imhist(a);
title('原始cameraman图像的直方图');</span>
图像识别算法交流 QQ群:145076161,欢迎图像识别与图像算法,共同学习与交流