1. 学会Photoshop软件的基本操作,比如会看直方图、会使用直方图均衡化等。
2.在图像处理中,彩色到灰度转换公式为:𝑔𝑟𝑦 = 0.299 × 𝑟𝑒𝑑 + 0.587 × 𝑔𝑟𝑒𝑒𝑛 + 0.114 × 𝑏𝑙𝑢𝑒,请 使用C/C++编程把彩色图像H0201Rgb.bmp转成为灰度图像H0201Gry.bmp,分别使用查找表和不使 用查找表,分别使用Debug和Release编译,并各执行1000次,比较它们的时间花费(C/C++中的 时间函数为clock_t t=clock())。
MSVCx86 | 查找表 | 不使用查找表 |
---|---|---|
debug | 1898 | 1889 |
release | 334 | 486 |
MSVCx64 | ||
debug | 2059 | 3368 |
release | 248 | 526 |
单位clock tick
3. 使用C/C++编程,对题1得到的灰度图像H0201Gry.bmp进行均值方差规定化,自己设定3组不同的 均值和标准差,保存得到3幅结果图像,请比较它们的不同;在做完均值方差规定化后,再做灰度 范围的线性拉伸,你觉得有意义吗?
avg=128, stddev=100 |
---|
avg=64, stddev=100 |
avg=128, stddev=64 |
---|
当图像的方差较小的时候,整体的图像呈现灰蒙蒙的状态,大家的灰度值都比较接近。当方差变大的时候,亮的地方会特别亮,暗的地方会特别暗。均值体现了图像总体的亮度。
做完在做完均值方差规定化后,再做灰度范围的线性拉伸,没有特别的意义。均值方差规定化的本质就是线性拉伸。
4. 使用C/C++编程,对灰度图像H0202Plane.bmp和H0203Girl.bmp进行直方图均衡化,得到结果图像 H0202Plane_Res.bmp和H0203Girl_Res.bmp,请按照书中分析方法,分析H0202Plane_Res.bmp图 像 的 特 点 ; 比 较 你 做 的 结 果 和 Photoshop 软件做的结果的差异;如果先对灰度图像 H0202Plane.bmp或者H0203Girl.bmp做灰度范围的线性拉伸,再做直方图均衡化,你觉得有意义吗?
c++实现的 | PS的 |
---|---|
直方图 | |
飞机 | |
从小女孩那个直方图中可以看出,两边的直方图还是存在着一定的差别,特别是灰度值接近0那里,差别比较明显。上网查了一下,经过经典算法均衡化的图片,最亮的像素值总是255,因为最后一级色阶(255)的百分位一定是100%。而最暗的是由色阶0的数量决定的,像素值不一定是0。发现原来PS在直方图均衡化之后,又做了一步对比度拉伸。将原来区域的值映射回0-255。PS的方法感觉更好,人眼对于暗的东西更加敏感,所以将灰度值映射到0,有利于人更加好的分辨。不过实作了一下,两个直方图还是有一定的区别。PS的直方图拉得更加开,分布更加均匀。
对图像做线性拉伸,在做均衡化,有一定的意义,在一定的情况下。
- 对于一般情况,是没有意义的,直方图统计的是排名信息,线性变换并不会改变排名信息。
- 但是对于那些特别亮或者特别暗的像素,通过线性变换,可以让其变得跟比较亮的一样亮,因为线性变换可能让像素的亮度值超过255,最后大家都变成255。导致排名的信息丢失,可能会让整个图像的亮的部分都特别亮,其他不变。
- 线性变换经过离散化之后,可能会导致排名信息损失
效果图
5. 对彩色图像H0201Rgb.bmp进行直方图均衡化是什么效果,使用Photoshop尝试一下;使用C/C++ 编程,尝试编一个彩色图像直方图均衡化的程序,比较与Photoshop软件处理效果的差异,通过实 验猜想Photoshop软件是怎么做的。怎么修改成绩呢?
PS的效果
别分对三个通道进行均衡化 | 对三个通道一起做均衡化 | 用三个通道的亮度的和来做均衡化 |
---|---|---|
PS 可能是将三个通道一起做归一化。
6.式(2-17)是很多资料中常见的对数变换,但对数变换没有考虑图像中灰度最小值𝑔𝑚𝑖𝑛的问题, 需要在实际应用中进行考虑,你认为如何修改该公式更好?
目标是将像素的亮度值映射\(f\)到0-255的区间
(2-17)已经有\(f(g_{max})=255\)
还需要\(f(g_{min}) = 0\)
所以可以变成 \(G = f(g) = {255 \over{log(1+g_{max}- g_{min} +\epsilon)}}\cdot log(1 + g - g_{min})\)
其中\(\epsilon\)是一个小量防止除以0
7. 使用C/C++编程,对红外热像仪输出的14Bit的原始图像灰度H0204IR14bit.raw(宽度640,高度480, 每个像素的灰度值占2个字节,类型为short int)选择合适的方法转换为8bit的灰度图像,保存为 H0204IR8bit.bmp。(提示:线性拉伸、均值方差规定化、对数变换、直方图均衡化都可以,建议 使用直方图均衡化)
直方图 |
---|
对数变换 |
对数变换+均值方差规定化(mean=100, std=100) |
8.在算法2-3中是可以去掉变量A的,请考虑该如何修改一下语句?
void RmwHistogramEqualize(BYTE *pGryImg, int width, int height)
{
BYTE *pCur, *pEnd = pGryImg+width*height;
int histogram[256], LUT[256], A, g;
// step.1-------------求直方图--------------------------//
memset(histogram, 0, sizeof(int)*256);
for (pCur = pGryImg; pCur<pEnd;) histogram[*(pCur++)]++;
// step.2-------------求LUT[g]-------------------------//
//A = histogram[0];
LUT[0] = 255*histogram[0]/(width*height);
for (g = 1; g<256; g++)
{
// A += histogram[g];
histogram[g] += histogram[g-1]; // <-------用直方图来统计每个灰度值之前有多少像素
LUT[g] = 255*histogram[g]/(width*height);
}
// step.3-------------查表------------------------------//
for (pCur = pGryImg; pCur<pEnd;) *(pCur++) = LUT[*pCur];
// step.4-------------结束------------------------------//
return;
}
9. 假设在教学中,对于学生考试成绩的登记有2种方式,一种是原始分,一种是标准分。有一个班级 的成绩平均分是68,而学校要求成绩的均值为75、标准差为25,那么教师该怎么修改成绩呢?
先现将成绩归一化变成方差为1,均值为0,然后所有人成绩乘25,再+75。借用均值方差规定化的思想。
调试:
- Windows.h中自带的min,max宏定义令人作呕,于是使用 algorithm中的max , min使用方法(max)(a, b)
- 修复了一部分存取效率的问题,增加了bmpConverter的拷贝赋值
- 修复了一些低效率的运算
- 没有遇到特别的bug,调试耗费时间比较少
- #define BTYE BYTE 使用了这个无聊的宏来防止自己写错
- 新增homework.h来保存作业中每个题目的函数
- delete 指针,对于空指针没有效果,所以不需要判断空指针,直接delete
源代码
// homework.cpp
void hw2_time_cmp()
{
bmpConverter bmpCvt("./pic/Fig0201.bmp");
int epoch_num = 1000;
time_t start = clock();
while (epoch_num--)
{
bmpCvt.RGB2Gry(true, false);
}
time_t end = clock() - start;
cout << "时间: " << end << endl;
epoch_num = 1000;
start = clock();
while (epoch_num--)
{
bmpCvt.RGB2Gry(false, false);
}
end = clock() - start;
cout << "时间: " << end << endl;
bmpCvt.RGB2Gry(true, true);
bmpCvt.Img2Bmp("./pic/Fig0201Grey.bmp", 8);
}
void hw2_avg_std_conv()
{
bmpConverter bmpCvt("./pic/Fig0201Grey.bmp");
bmpCvt.P2avgstd8Bit(128, 64);
//bmpCvt.PAffine(1.5, 0);
bmpCvt.Img2Bmp("./pic/output.bmp");
}
void hw2_HistogramEqualize()
{
bmpConverter bmpCvt("./pic/Fig0212Plane.bmp");
bmpCvt.PHistogramEqualize8bit();
bmpCvt.Img2Bmp("./pic/Fig0212Plane_balanced.bmp");
bmpCvt.BmpFile2Img("./pic/Fig0216Girl.bmp");
//bmpCvt.PAffine(5, -50);
bmpCvt.PHistogramEqualize8bit();
bmpCvt.Img2Bmp("./pic/Fig0216Girl_balanced.bmp");
}
void hw2_24bitHist()
{
bmpConverter bmpCvt("./pic/H0201Rgb.bmp");
bmpCvt.PHistogramEqualize24bit2();
bmpCvt.Img2Bmp("./pic/H0201Rgb_balanced.bmp");
}
void hw2_14bit_convert()
{
bmpConverter bmpCvt;
bmpCvt.read14bitRaw("./pic/H0204IR14bit.raw");
puts("转化完成");
bmpCvt.Img2Bmp("./pic/H0204IR14bit.bmp", 8);
}
// bmpConverter.h
#pragma once
#include <Windows.h>
#include <cstring>
#include <cstdio>
#include "switcher.h"
#include <iostream>
class bmpConverter
{
public:
bmpConverter(const char * orgfile);
bmpConverter();
// 灰度图像反转
void InvertImg();
// 读取bmp
bool BmpFile2Img(const char * DstFile);
// 保存bmp
bool Img2Bmp(const char * DstFile, int bitCnt=0, char RGBMOD='\0');
// 读取14bit raw数据,已经写死
bool read14bitRaw(const char * DstFile);
//RGB转灰度图
void RGB2Gry(bool table_chk=true, bool inplace=true);
// 线性拉伸(仿射变换)
void PAffine(double k, double b);
// 均值标准差规定化
void P2avgstd8Bit(double mean = 128.0, double stddev = 1);
// 8bit直方图标准化实现
void PHistogramEqualize8bit();
// 24bit直方图标准化实现(三个通道分别实现)
void PHistogramEqualize24bit();
// 24bit直方图标准化实现(三个通道在一起实现)
void PHistogramEqualize24bit1();
// 24bit直方图标准化实现(三个通道的均值实现)
void PHistogramEqualize24bit2();
~bmpConverter();
bmpConverter(bmpConverter &);
bmpConverter(bmpConverter &&);
private:
void PHistogramEqualize14bit(short * pRawImg);
void PHistogramEqualize14bit2(short * pRawImg);
void getHistGram8bit(int * hist);
void getHistGram24bitavg(int * hist);
void getHistGram24bit(int * hist);
bool Img28bitBmp(const char * DstFile, char mod);
bool Img224bitBmp(const char * DstFile);
BYTE * pImg=nullptr;
long width, height;
BITMAPFILEHEADER FileHeader;
BITMAPINFOHEADER BmpHeader;
int channel;
};
//bmpConverter.cpp
//________homework2的代码__________________________
// 14bit直方图归一化
void bmpConverter::PHistogramEqualize14bit(short * pRawImg)
{
int hist[1 << 14], sum = width * height, A = 0;
pImg = new BYTE[sum];
BYTE LUT[1 << 14], * pCur = pImg;
// 得到直方图
short * spCur = pRawImg, *spDes = pRawImg + sum;
for (int i = 0; i < (1 << 14); i++) hist[i] = 0;
while (spCur < spDes) hist[*(spCur++)]++;
for (int i = 0; i < (1 << 14); i++)LUT[i] = 255 * (A += hist[i]) / sum;
for (spCur = pRawImg; spCur < spDes;)*(pCur++) = LUT[*(spCur++)];
}
// 14bit直方图归一化2
void bmpConverter::PHistogramEqualize14bit2(short * pRawImg)
{
int hist[1 << 14], sum = width * height, A = 0;
pImg = new BYTE[sum];
BYTE LUT[1 << 14], *pCur = pImg;
// 得到直方图
short * spCur = pRawImg, *spDes = pRawImg + sum;
for (int i = 0; i < (1 << 14); i++) hist[i] = 0;
while (spCur < spDes) hist[*(spCur++)]++;
int maxp = (1 << 14) - 1, minp = 0;// 求最小灰度和最大灰度
while (!hist[minp])minp++;
while (!hist[maxp])maxp--;
double c = 255 / log(1 + maxp - minp + 1e-8);
#ifdef DEBUG
printf("gmax : %d gmin : %d c %llf\n", maxp, minp, c);
#endif // DEBUG
for (int i = minp; i <= maxp; i++)LUT[i] = c * log(1 + i - minp);
for (spCur = pRawImg; spCur < spDes;)*(pCur++) = LUT[*(spCur++)];
P2avgstd8Bit(100, 100);
}
// 读取14bit raw数据,已经写死
bool bmpConverter::read14bitRaw(const char * DstFile)
{
delete pImg;
width = 640, height = 480, channel = 1;
BmpHeader.biBitCount = 8;
FILE *fp;
int err = fopen_s(&fp, DstFile, "rb"); // "./pic/H0204IR14bit.raw"
if (err)
{
printf("file open err %d \n", err);
return false;
}
short * pRawImg = new short[width*height];
fread(pRawImg, sizeof(short), width * height, fp);
fclose(fp);
PHistogramEqualize14bit2(pRawImg);
return true;
}
// RGB转灰度图
void bmpConverter::RGB2Gry(bool table_chk, bool inplace)
{
if (channel != 3 || !pImg)return;
int sum = width * height;
BYTE * temp_save = new BYTE[sum];
if (!temp_save)
{
puts("程序错误,内存申请失败");
return;
}
#ifdef DEBUG
//cout << (int)(temp_save) <<"113" <<sizeof(temp_save)<< endl;
////cout << (int)pImg << endl;
#endif // DEBUG
BYTE * p1 = temp_save, *p2 = pImg - 1;
if (table_chk)
{
BYTE r8[256], g8[256], b8[256];
int r_ratio = 0.299 * 1024, g_ratio = 0.587 * 1024;
for (int i = 0; i < 256; i++)
{
r8[i] = r_ratio * i >> 10;
g8[i] = g_ratio * i >> 10;
b8[i] = i - r8[i] - g8[i];
}
while (sum--)*(p1++) = (b8[*(++p2)] + g8[*(++p2)] + r8[*(++p2)]);//blue,green,red
}
else
{
while (sum--)*(p1++) = ((*(++p2)) * 0.114 + 0.587 * (*(++p2)) + 0.299 * (*(++p2)));//blue,green,red
}
if (inplace)
{
swap(temp_save, pImg);
channel = 1;
}
#ifdef DEBUG
//cout << int(temp_save) << endl;
//cout << (int)pImg << endl;
#endif // DEBUG
delete temp_save;
return;
}
// 线性拉伸(仿射变换)
void bmpConverter::PAffine(double k, double b)
{
if (!pImg)return;
BYTE LUT[256], * pCur = pImg, *pDes = pImg + width * height * channel;
for (int i = 0; i < 256; i++)LUT[i] = (std::min)(255.0, (std::max)(0.0, k * i + b));
while (pCur < pDes)*(pCur++) = LUT[*pCur];
}
// 8bit均值标准差规定化
void bmpConverter::P2avgstd8Bit(double mean, double stddev)
{
if (BmpHeader.biBitCount != 8)return;
int tot = width * height;
int hist[256];
getHistGram8bit(hist);
double avg, k ,b, sdev = 0;
if (tot > 16777210)
{
unsigned long long sum = 0;
for (int i = 0; i < 256; i++) sum += hist[i] * i;
avg = double(sum) / tot;
}
else
{
unsigned int sum = 0;
for (int i = 0; i < 256; i++) sum += hist[i] * i;
avg = double(sum) / tot;
}
for (int i = 0; i < 256; i++) sdev += hist[i] * (i - avg) * (i - avg);
k = stddev / sqrt(sdev / (tot - 1));
b = mean - avg * k;
PAffine(k, b);
return;
}
// 得到8bit的直方图
void bmpConverter::getHistGram8bit(int * hist)
{
for (int i = 0; i < 256; i++)hist[i] = 0;
for (BYTE * pCur = pImg, *pDest = pImg + width * height;pCur < pDest;)
hist[*(pCur++)]++;
}
// 8bit直方图标准化实现
void bmpConverter::PHistogramEqualize8bit()
{
if (BmpHeader.biBitCount != 8)
{
puts("图像位数错误"); return;
}
int hist[256], LUT[256], A = 0, sum = width * height;
getHistGram8bit(hist);
for (int i = 0, A = hist[1]; i < 256; i++)LUT[i] = 255 * (A += hist[i]) / sum;
for (BYTE * pCur = pImg, *pDest = pImg + width * height; pCur < pDest;)
*(pCur++) = LUT[*pCur];
}
// 得到24bit直方图
void bmpConverter::getHistGram24bit(int * hist)
{
for (int i = 0; i < 256*3; i++)hist[i] = 0;
for (BYTE * pCur = pImg, p = 0, *pDest = pImg + width * height * 3; pCur < pDest; p = (p + 1)%3)
hist[*(pCur++) * 3 + p]++;
}
// 24bit直方图标准化实现(三个通道分别实现)
void bmpConverter::PHistogramEqualize24bit()
{
if (BmpHeader.biBitCount != 24)
{
puts("图像位数错误"); return;
}
int hist[256 * 3], LUTR[256], LUTG[256], LUTB[256], sum = width * height, AR=0, AG=0, AB=0;
getHistGram24bit(hist);
for (int i = 0, p=0; i < 256; i++)
{
LUTB[i] = 255 * (AB += hist[p++]) / sum;//blue,green,red
LUTG[i] = 255 * (AG += hist[p++]) / sum;//blue,green,red
LUTR[i] = 255 * (AR += hist[p++]) / sum;//blue,green,red
}
for (BYTE * pCur = pImg, *pDest = pImg + width * height * channel; pCur < pDest;)
{
*(pCur++) = LUTB[*pCur];//blue,green,red
*(pCur++) = LUTG[*pCur];//blue,green,red
*(pCur++) = LUTR[*pCur];//blue,green,red
}
}
// 24bit直方图标准化实现(三个通道在一起实现)
void bmpConverter::PHistogramEqualize24bit1()
{
if (BmpHeader.biBitCount != 24)
{
puts("图像位数错误"); return;
}
int hist[256 * 3], LUTR[256], LUTG[256], LUTB[256], sum = width * height * channel, A = 0;
//for (; !hist[minp]; minp++);
//minp /= 3;
getHistGram24bit(hist);
for (int i = 0, p = 0; i < 256;i++)
{
LUTB[i] = 255 * (A += hist[p++]) / sum;//blue,green,red
//LUTB[i] = (LUTB[i] - minp) * 255 / (255 - minp);//blue,green,red
LUTG[i] = 255 * (A += hist[p++]) / sum;//blue,green,red
//LUTG[i] = (LUTG[i] - minp) * 255 / (255 - minp);
LUTR[i] = 255 * (A += hist[p++]) / sum;//blue,green,red
//LUTR[i] = (LUTR[i] - minp) * 255 / (255 - minp);
}
for (BYTE * pCur = pImg, *pDest = pImg + width * height * channel; pCur < pDest;)
{
*(pCur++) = LUTB[*pCur];//blue,green,red
*(pCur++) = LUTG[*pCur];//blue,green,red
*(pCur++) = LUTR[*pCur];//blue,green,red
}
}
// 得到24bit的平均直方图
void bmpConverter::getHistGram24bitavg(int * hist)
{
for (int i = 0; i < 256; i++)hist[i] = 0;
for (BYTE * pCur = pImg, *pDest = pImg + width * height * 3; pCur < pDest;)
hist[(*(pCur++) + *(pCur++) + *(pCur++)) / 3]++;
}
// 24bit直方图标准化实现(三个通道的均值实现)
void bmpConverter::PHistogramEqualize24bit2()
{
if (BmpHeader.biBitCount != 24)
{
puts("图像位数错误"); return;
}
int hist[256], LUT[256], sum = width * height * channel, A = 0;
getHistGram24bitavg(hist);
for (int i = 0; i < 256; i++)LUT[i] = 255 * (A += hist[i]) / sum;
for (BYTE * pCur = pImg, *pDest = pImg + width * height * channel; pCur < pDest;)
*(pCur++) = LUT[*pCur];
}