前言
1、什么是数字图像
一幅图像可以定义为一个二维函数f(x,y),其中x和y是空间(平面)坐标,而f在任意坐标(x,y)处的幅度称为图像在该点处的亮度或灰度。当x、y和f的幅度值都是有限的离散值时,称该图像为数字图像。
数字图像由有限数量的元素组成,每个元素都有一个特定的位置和数值。这些元素称为图画元素,图像元素或像素。
2、数字图像的表示
由数值f(x,y)组成的一个阵列(矩阵)表示,可以用公式将一个M×N矩阵表示为
右边实数矩阵表示数字图像,每个元素称为像素。
图像数字化要求对M值、N值和离散灰度级数L进行判定。对于M和N,除必须取正整数外并无其他限制。然而,出于存储和量化硬件的考虑,灰度级数通常取2的整数次幂,即
式中,k是整数。假设离散灰度级是等间隔的,且它们是区间[0,L-1]内的整数。
特别篇:如何访问像素
访问(j,i)处像素
以8位(0~255)灰度图像和BGR彩色图像为例,用at可以访问图像像素:
//灰度图像
image.at<uchar>(j, i) //j为行数,i为列数
//BGR彩色图像 Vec3f或Vec3b
image.at<Vec3b>(j, i)[0] //B分量
image.at<Vec3b>(j, i)[1] //G分量
image.at<Vec3b>(j, i)[2] //R分量
灰度级越大,图像的颜色越偏向白色;灰度级越小,图像的颜色越偏向黑色。
基本操作
1.图像读取与显示
#include<iostream>
#include <opencv2/opencv.hpp> //汇总了图像处理相关的所有头文件(15个) 只要这一个就行
using namespace cv;
using namespace std;
int main()
{
//D:\\新桌面\\漫画人物壁纸\\1648229235164_1648228777745.jpg
//string path = R"(A005.png)"; //图片的路径名 R"(相对路径 文件粘贴在主文件目录下为文件名 或 绝对路径\\ 或 /)"
string path = "A005.png";
Mat img = imread(path); //将图片加载后赋值到图像变量img中
//Mat类是OpenCV库中用于表示图像或矩阵的一个核心数据结构,imread 函数在未能正确加载图片时返回的是一个空的 Mat 对象,因此你应该检查 img.empty() 而不是 path.empty()
//检查文件是否打开 没打开时执行打印语句
if (img.empty()) {
cout << "file not loaded" << endl;
return -1; //结束程序运行
}
namedWindow("Image", WINDOW_FREERATIO);//创建一个名为Image的可调节的窗口 以免图片显示不全
imshow("Image", img);//创建一个窗口来显示图像img
waitKey(0);//不断刷新图像
return 0;
}
/*
1、waitKey()函数的功能是不断刷新图像,频率为delay,单位是ms。
2、delay为0时,则会一直显示这一帧。
3、delay不为0时,则在显示完一帧图像后程序等待“delay"ms再显示下一帧图像。
*/
2.图像预处理
#include <iostream>
#include <opencv2/opencv.hpp> //汇总了图像处理相关的所有头文件(15个) 只要这一个就行
//#include<vector> //std::vector是一个动态数组,可以自动调整大小
using namespace cv;
using namespace std;
int main() {
string path = "A005.png";
Mat img = imread(path);
Mat imgGray, imgBlur, imgCanny, imgDil, imgErode; //创建空的数据矩阵
//将照片转换为灰度
cvtColor(img, imgGray, COLOR_BGR2GRAY);
//高斯模糊 (输入图像,输出图像,高斯内核(宽,高)【可以不同,必正奇数】,x反向偏差,y方向偏差(若y是0,则函数会自动使y与x相同,若x和y都是0,xy将由高斯内核的两个值计算而来))
GaussianBlur(imgGray, imgBlur, Size(3, 3), 3, 0);
//Canny边缘检测器 一般在使用Canny边缘检测器之前会做一些模糊处理 第3和第4个参数分别代表底阈值和高阈值,其中底阈值常取高阈值的1/2或1/3
Canny(imgBlur, imgCanny, 25, 75);
//创建一个可以使用膨胀的内核
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
//图像膨胀
dilate(imgCanny, imgDil, kernel);
//图像侵蚀
erode(imgDil, imgErode, kernel);
//结果呈现 WINDOW_FREERATIO和WINDOW_NORMAL是OpenCV中用于窗口显示的两种模式
namedWindow("Image", WINDOW_FREERATIO);//创建一个名为Image的可调节的窗口 以免图片显示不全
imshow("Image", img);
namedWindow("Image Gray", WINDOW_FREERATIO);//创建一个名为Image的可调节的窗口 以免图片显示不全
imshow("Image Gray", imgGray);
namedWindow("Image Blur", WINDOW_FREERATIO);//创建一个名为Image的可调节的窗口 以免图片显示不全
imshow("Image Blur", imgBlur);
namedWindow("Image Canny", WINDOW_FREERATIO);//创建一个名为Image的可调节的窗口 以免图片显示不全
imshow("Image Canny", imgCanny);
namedWindow("Image Dilation", WINDOW_FREERATIO);//创建一个名为Image的可调节的窗口 以免图片显示不全
imshow("Image Dilation", imgDil);
namedWindow("Image Erode", WINDOW_FREERATIO);//创建一个名为Image的可调节的窗口 以免图片显示不全
imshow("Image Erode", imgErode);
waitKey(0);
return 0;
}
3.图像裁剪
#include <iostream>
#include <opencv2/opencv.hpp> //汇总了图像处理相关的所有头文件(15个) 只要这一个就行
using namespace cv;
using namespace std;
void main() {
string path = "A005.png";
Mat img = imread(path);
Mat imgResize, imgCrop;
//调整图像大小
cout << img.size() << endl; //查看原图像的大小
//resize(img, imgResize, Size(640, 480)); //按自定义的宽度与高度缩放
resize(img, imgResize, Size(), 0.5, 0.5); //按比例缩放
//图像裁剪
Rect roi(200, 100, 300, 300);
//前面两个参数为距左上原点的x方向与y方向的距离,后两个参数为延伸的x,y长度
imgCrop = img(roi);
imshow("Image", img);
imshow("Image Resize", imgResize);
imshow("Image Crop", imgCrop);
waitKey(0);
}
4.图像堆叠拼接
/*
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
int main()
{
//图片深度要一样
cv::Mat image = cv::imread("B1.png");
cv::Mat image2 = cv::imread("B2.png");
cv::Mat output;
cv::hconcat(image, image2, output);
// 创建一个窗口并显示图像 WINDOW_FREERATIO和WINDOW_NORMAL是OpenCV中用于窗口显示的两种模式
cv::namedWindow("hcontact", cv::WindowFlags::WINDOW_NORMAL);
cv::imshow("hcontact", output);
cv::waitKey(0);
return 0;
}
*/
//把多张图片水平拼接
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
int main()
{
#if 0
//不知道为啥拼接是黑的看不到图片
cv::Mat image = cv::imread("tubiao/setting.png");
cv::Mat image2 = cv::imread("tubiao/save.png");
cv::Mat image3 = cv::imread("tubiao/picture.png");
#else
cv::Mat image = cv::imread("B1.png");
cv::Mat image2 = cv::imread("B1.png");
cv::Mat image3 = cv::imread("B1.png");
#endif
std::vector<cv::Mat> images;
images.push_back(image);
images.push_back(image2);
images.push_back(image3);
cv::Mat output2;
cv::hconcat(images, output2);
// 创建一个窗口并显示图像 WINDOW_FREERATIO和WINDOW_NORMAL是OpenCV中用于窗口显示的两种模式
cv::namedWindow("hcontact2", cv::WindowFlags::WINDOW_NORMAL);
cv::imshow("hcontact2", output2);
cv::waitKey(0);
return 0;
}
5.绘制形状和添加文本
#include <iostream>
#include <opencv2/opencv.hpp> //汇总了图像处理相关的所有头文件(15个) 只要这一个就行
using namespace cv;
using namespace std;
//绘制形状和添加文本
void main() {
//创建空白图像 img(宽度,高度,BGR彩色图像,初始化每个像素的值(白色)) 黑色(0,0,0)
Mat img(512, 512, CV_8UC3, Scalar(255, 255, 255));
//绘制圆形 线条宽度为10
circle(img, Point(256, 256), 155, Scalar(0, 69, 255),10);
//circle(img, Point(256, 256), 155, Scalar(0, 69, 255), FILLED,10);
//函数参数分别是 输出到图像img,圆心,半径,颜色,厚度(FILLED 表示填满)
//绘制矩形
rectangle(img, Point(130, 226), Point(382, 286), Scalar(255, 255, 255), FILLED);
//函数参数分别是 输出到图像img,矩形左上角顶点坐标,右下角顶点坐标,颜色,厚度
//绘制线段
line(img, Point(130, 296), Point(382, 296), Scalar(255, 255, 255), 2);
//函数参数分别是 输出到图像img,两个端点坐标,颜色,厚度
//添加文本 OpenCV支持多种Hershey字体,例如FONT_HERSHEY_SIMPLEX, FONT_HERSHEY_PLAIN, FONT_HERSHEY_DUPLEX等。
putText(img, "Murtaza's Workshop", Point(137, 262), FONT_HERSHEY_DUPLEX, 0.75, Scalar(0, 69, 255), 2);
//函数参数分别是 输出到图像img,文本内容,左下角起点,字体,大小,颜色,厚度
imshow("Image", img);
waitKey(0);
}
6.透视变换
#include <iostream>
#include <opencv2/opencv.hpp> //汇总了图像处理相关的所有头文件(15个) 只要这一个就行
using namespace cv;
using namespace std;
float w = 250, h = 350;
Mat matrix, imgWarp; //matrix将用于存储透视变换矩阵,imgWarp将用于存储变换后的图像。
// 透视变换
void main() {
string path = "card.png";
Mat img = imread(path);
//包含4个Point2f类型元素的数组,表示源图像上的四个点。 Point2f是一个二维点,由两个浮点数表示,这里是四个点的坐标
//Point2f src[4] = { {529,142},{771,190},{405,395},{674,457} };
/*
* 可以利用Halcon放大器标点读值 像素的位置
* Q的坐标
* (322,9) (276,279)
* (628,35) (567,342)
* J的坐标
* (106,715) (83,953)
* (352,781) (327,1050)
* 9的坐标
* (383,677) (434,957)
* (700,586) (753,912)
*/
//用Halcon 放大镜找出Q的四点位置(行,列) (322,9) (276,279) (628, 35) (567, 342)
//Point2f src[4] = { {9,322},{279,276},{35,628},{342,567} };
//J的四点位置
Point2f src[4] = {
{
715,106},{
953,83},{
781,352},{
1050,327} };
//9的四点位置
//Point2f src[4] = { {677,383},{957,434},{586,700},{912,753} };
Point2f dst[4] = {
{
0.0f,0.0f},{
w,0.0f},{
0.0f,h},{
w,h} }; //这四个点定义了透视变换后新图像的坐标位置
matrix = getPerspectiveTransform(src, dst);//获取原图像四边形到目标图像四边形的透视变换矩阵
//src为源图像四边形顶点坐标,dst为目标图像对应的四边形顶点坐标
warpPerspective(img, imgWarp, matrix, Point(w, h));
//参数分别为 输入图像,输出图像,透视变换矩阵,图像大小(宽,高)
for (int i = 0; i < 4; i++)
{
circle(img, src[i], 10, Scalar(0, 0, 255), FILLED);
}//在原图像中标记目标顶点
imshow("Image", img);
imshow("Image Warp", imgWarp);
waitKey(0);
}
7.颜色变换
#include <iostream>
#include <opencv2/opencv.hpp> //汇总了图像处理相关的所有头文件(15个) 只要这一个就行
using namespace cv;
using namespace std;
Mat imgHSV, mask;
int hmin = 0, smin = 0, vmin = 0;
int hmax = 179, smax = 255, vmax = 255;
void main() {
string path = "海绵宝宝01.jpg";
Mat img = imread(path);
//色彩空间转换函数-cvtColor
cvtColor(img, imgHSV, COLOR_BGR2HSV);
//HSV颜色空间 H(色调):0~180 S(饱和度):0~255 V(亮度):0~255
namedWindow("Trackbars", (640, 200));//创建一个名为Trackbars的窗口,大小为640*200
createTrackbar("Hue Min", "Trackbars", &hmin, 179);
createTrackbar("Hue Max", "Trackbars", &hmax, 179);
createTrackbar("Sat Min", "Trackbars", &smin, 255);
createTrackbar("Sat Max", "Trackbars", &smax, 255);
createTrackbar("Val Min", "Trackbars", &vmin, 255);
createTrackbar("Val Max", "Trackbars", &vmax, 255);
//createTrackbar函数是创建轨迹条,
//4个参数分别是 轨迹条名字,输出的窗口,一个指向整数的指针来表示当前的值,可到达的最大值
while (true)
{
//检测我们所要的颜色 设置一个遮罩 在范围内的颜色
Scalar lower(hmin, smin, vmin);//HSV范围最低值
Scalar upper(hmax, smax, vmax);//HSV范围最高值
inRange(imgHSV, lower, upper, mask);//输入,低值,高值,输出
//inRange是将在阈值区间内的像素值设置为白色(255),而不在阈值区间内的像素值设置为黑色(0)
imshow("Image", img);
imshow("Image HSV", imgHSV);
imshow("Image Mask", mask);
waitKey(1);
}
}
8.形状检测和轮廓检测
#include <iostream>
#include <opencv2/opencv.hpp> //汇总了图像处理相关的所有头文件(15个) 只要这一个就行
using namespace cv;
using namespace std;
Mat imgGray, imgBlur, imgCanny, imgDil, imgErode;
//定义一个轮廓处理函数 其目的是从经过预处理的二值图像中提取轮廓,并根据轮廓的特性对其进行分类和标注
void getContours(Mat imgDil, Mat img) {
//定义了一个二维向量contours,用于存储提取的轮廓信息。每个内部向量表示一个轮廓,而每个轮廓是由一系列的Point组成的。
vector<vector<Point>> contours; //例如{ {Point(20,30),Point(50,60)},{}, {}} 三个轮廓,第一个轮廓有两个点,第二、三个为空
//定义了一个向量hierarchy,它包含了多个Vec4i类型的元素。Vec4i是一个包含4个整数的向量。这个hierarchy向量用于存储轮廓之间的层次信息,例如哪个轮廓是外部轮廓,哪个是内部轮廓
vector<Vec4i>hierarchy;
//使用OpenCV的findContours函数从膨胀后的图像中提取轮廓。RETR_EXTERNAL表示只检测最外层轮廓,CHAIN_APPROX_SIMPLE是对轮廓进行压缩,只保留端点。
findContours(imgDil, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
//使用drawContours函数在图像img上绘制所有的检测到的轮廓。轮廓将以品红色(由Scalar(255, 0, 255)指定)和2像素的厚度绘制
//drawContours(img, contours, -1, Scalar(255, 0, 255), 2);
//定义了一个新的二维向量conPoly,其大小与检测到的轮廓数量相同。这个向量将用于存储每个轮廓的近似多边形
vector<vector<Point>>conPoly(contours.size());
//定义了一个新的向量boundRect,其大小与检测到的轮廓数量相同。这个向量将用于存储每个轮廓的边界矩形(bounding rectangle)
vector<Rect> boundRect(contours.size());
for (int i = 0; i < contours.size(); i++)
{
//计算轮廓的面积,并根据面积进行过滤,去除可能的噪点
int area = contourArea(contours[i]);
cout << area << endl;//需要正确过滤的面积(过滤噪点)
//定义一个字符串变量objectType,用于存储检测到的形状的名称
string objectType;
//判断形状 根据轮廓的面积和顶点数,对形状进行分类(如三角形、矩形、正方形和圆形)。对于矩形和正方形,还计算长宽比以确定其确切的形状。
if (area > 1000) //过滤面积较小的轮廓,这些轮廓可能代表噪声或不重要的细节
{
float peri = arcLength(contours[i], true); //计算当前轮廓的周长,并将结果存储在变量peri中
//找到当前轮廓的一个近似多边形,这个多边形将用于后续的形状分类。多边形的精度由参数0.02 * peri控制
approxPolyDP(contours[i], conPoly[i], 0.02 * peri, true);
cout << conPoly[i].size() << endl; //输出近似多边形的顶点数,这有助于了解多边形的大致形状
boundRect[i] = boundingRect(conPoly[i]);//计算近似多边形的边界矩形,并将其存储在boundRect向量中
int objCor = (int)conPoly[i].size(); //将近似多边形的顶点数转换为整数,并存储在变量objCor中
//如果多边形有3个顶点,则将其分类为三角形,并将objectType设置为"Tri"
if (objCor == 3) {
objectType = "Tri"; }
//如果多边形有4个顶点,则进一步检查其长宽比以确定它是正方形还是矩形
if (objCor == 4) {
float aspRatio = (float)boundRect[i].width / (float)boundRect[i].height;
cout << aspRatio << endl;
if (aspRatio > 0.95 && aspRatio < 1.05) {
objectType = "Square"; }
else {
objectType = "Rect";
}
}
//如果多边形有超过4个顶点,则将其分类为圆形,并将objectType设置为"Circle"
if (objCor > 4) {
objectType = "Circle"; }
//使用drawContours函数在原始图像img上绘制轮廓。它绘制的是conPoly中的第i个轮廓(即当前正在处理的轮廓)。
drawContours(img, conPoly, i, Scalar(255, 0, 255), 2);//描绘计数轮廓 轮廓会以品红色(RGB值为(255, 0, 255))和2像素的厚度进行绘制
//使用rectangle函数在原始图像img上绘制一个矩形,该矩形是围绕当前正在处理的轮廓的边界矩形。boundRect[i].tl()返回边界矩形的左上角点,而boundRect[i].br()返回右下角点。
rectangle(img, boundRect[i].tl(), boundRect[i].br(), Scalar(0, 255, 0), 5);//绘制边界矩形 矩形会以绿色(RGB值为(0, 255, 0))和5像素的厚度进行绘制
//打印图形的名字 在原始图像上的边界矩形的上方标注形状的名称(如"Tri"、"Square"、"Rect"或"Circle")
// 文本会标注在边界矩形的左上角,具体位置是矩形的x坐标不变,y坐标减去5个像素。
// 使用的字体是FONT_HERSHEY_PLAIN,字体大小为1,文本颜色为蓝紫色(RGB值为(0, 69, 255)),并且文本线的厚度为2像素。
putText(img, objectType, {
boundRect[i].x,boundRect[i].y - 5 }, FONT_HERSHEY_PLAIN, 1, Scalar(0, 69, 255), 2);
}
}
}
void main() {
//从指定路径读取一个名为"contours.png"的图像
//string path = "contours.png";
string path = "xz.png";
Mat img = imread(path);
//图像的预处理
//1.将照片转换为灰度图像,有助于简化图像处理过程,因为灰度图像只有一个通道
cvtColor(img, imgGray, COLOR_BGR2GRAY);
//2.高斯模糊,以减少图像中的噪声
GaussianBlur(imgGray, imgBlur, Size(3, 3), 3, 0);
//3.Canny边缘检测,使用Canny算法检测图像中的边缘
Canny(imgBlur, imgCanny, 25, 75);
//4.创建一个可以使用膨胀的内核 定义了一个3x3的结构元素(用于形态学操作),然后使用该元素对Canny边缘检测的结果进行膨胀
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
//5.图像膨胀 在进行轮廓检测之前,对Canny边缘检测的结果进行膨胀操作,可以增强轮廓并去除噪声。
dilate(imgCanny, imgDil, kernel);
//获取并显示轮廓
getContours(imgDil, img);
// 显示处理后的图像
imshow("Image", img);
//imshow("Image Gray", imgGray);
//imshow("Image Blur", imgBlur);
//imshow("Image Canny", imgCanny);
//imshow("Image Dilation", imgDil);
waitKey(0);
}
9.人脸识别
#include <iostream>
#include <opencv2/opencv.hpp> //汇总了图像处理相关的所有头文件(15个) 只要这一个就行
using namespace cv;
using namespace std;
void main() {
//string path = "B3.jpg";
string path = "person.jpg";
Mat img = imread(path);
CascadeClassifier faceCascade;//创建级联分类器
//载入训练模型 这是OpenCV库中一个常用于面部检测的模型 在sources/data/haarcascades_cuda文件夹中 D:/OpenCV - 4.8.1/opencv/sources/data/haarcascades_cuda/haarcascade_frontalface_default.xml
faceCascade.load("haarcascade_frontalface_default.xml");
if (faceCascade.empty()) {
cout << "XML file not loaded" << endl; }
//检查文件是否打开 没打开时执行打印语句
vector<Rect>faces;//创建人脸存放的vector
// faceCascade.detectMultiScale(img, faces, scaleFactor, minNeighbors);
// faces:这是一个输出向量,用于存储检测到的人脸的矩形区域 scaleFactor:通常设置为1.1 minNeighbors:指定每一个目标至少被检测到几次才算是真的目标。通常设置为3-6。
faceCascade.detectMultiScale(img, faces, 1.1, 10);
//detectMultiScale函数可以检测出图片中所有的人脸,并用vector保存各个人脸的坐标、大小
//使用try-catch语句捕获异常,并打印出更详细的错误信息
try {
faceCascade.detectMultiScale(img, faces, 1.1, 10);
}
catch (cv::Exception& e) {
std::cerr << e.what() << std::endl;
}
//在原图像中画出人脸矩形边框
for (int i = 0; i < faces.size(); i++)
{
rectangle(img, faces[i].tl(), faces[i].br(), Scalar(255, 0, 255), 3);
}
imshow("Image", img);
waitKey(0);
}
图像增强
一、灰度变换
灰度变换可使图像动态范围增大,图像对比度扩展,图像变清晰,特征明显。
图像反转就是线性变换的一种
①线性变换
线性变换:原理:线性变换是一种简单的亮度和对比度调整方法,通过对每个像素的灰度级别应用线性变换公式来实现。对每个像素应用公式 output_pixel = input_pixel * alpha + beta,其中 alpha 控制对比度,beta 控制亮度。增大 alpha 值可以增加对比度,增大 beta 值可以增加亮度。
/*
线性变换(亮度和对比度调整):
原理:线性变换是一种简单的亮度和对比度调整方法,通过对每个像素的灰度级别应用线性变换公式来实现。
对每个像素应用公式 output_pixel = input_pixel * alpha + beta,其中 alpha 控制对比度,beta 控制亮度。增大 alpha 值可以增加对比度,增大 beta 值可以增加亮度。
*/
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image1, output_image, image1_gray; //定义输入图像,输出图像,灰度图像
image1 = imread("person.jpg"); //读取图像;
if (image1.empty())
{
cout << "读取错误" << endl;
return -1;
}
cvtColor(image1, image1_gray, COLOR_BGR2GRAY); //灰度化
imshow(" image1_gray", image1_gray); //显示灰度图像
output_image = image1_gray.clone();
for (int i = 0; i < image1_gray.rows; i++)
{
for (int j = 0; j < image1_gray.cols; j++)
{
output_image.at<uchar>(i, j) = 1.5 * double(image1_gray.at<uchar>(i, j)) + 30; //线性变换 s=1.5r+30
}
}
normalize(output_image, output_image, 0, 255, NORM_MINMAX); //图像归一化,转到0~255范围内
convertScaleAbs(output_image, output_image); //数据类型转换到CV_8U
imshow(" output_image", output_image); //显示变换图像
waitKey(0); //暂停,保持图像显示,等待按键结束
return 0;
}
/*
灰度反转:将图像亮暗对调,可以增强图像中暗色区域细节
s = T(r) = L - 1 - r
其中L为图像灰度级,0~255灰度图像的灰度级为256.
*/
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image1, output_image, image1_gray; //定义输入图像,输出图像,灰度图像
image1 = imread("person.jpg"); //读取图像;
if (image1.empty())
{
cout << "读取错误" << endl;
return -1;
}
cvtColor(image1, image1_gray, COLOR_BGR2GRAY); //灰度化
imshow(" image1_gray", image1_gray); //显示灰度图像
output_image = image1_gray.clone();
for (int i = 0; i < image1_gray.rows; i++)
{
for (int j = 0; j < image1_gray.cols; j++)
{
output_image.at<uchar>(i, j) = 256 - 1 - image1_gray.at<uchar>(i, j); //灰度反转
}
}
imshow("output_image", output_image); //显示反转图像
waitKey(0); //暂停,保持图像显示,等待按键结束
return 0;
}
②对数变换
对数变换:原理:对数变换通过应用对数函数对图像的每个像素值进行修改。这种变换适用于增强图像的低灰度级别,因为它拉伸了低灰度级别之间的差异。公式为 output_pixel = c * log(1 + input_pixel),其中 c 是缩放常数。
/*
对数变换:
原理:对数变换通过应用对数函数对图像的每个像素值进行修改。这种变换适用于增强图像的低灰度级别,因为它拉伸了低灰度级别之间的差异。
公式为 output_pixel = c * log(1 + input_pixel),其中 c 是缩放常数。
*/
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image1, output_image, image1_gray; //定义输入图像,输出图像,灰度图像
image1 = imread("person.jpg"); //读取图像;
if (image1.empty())
{
cout << "读取错误" << endl;
return -1;
}
cvtColor(image1, image1_gray, COLOR_BGR2GRAY); //灰度化
imshow(" image1_gray", image1_gray); //显示灰度图像
output_image = image1_gray.clone();
for (int i = 0; i < image1_gray.rows; i++)
{
for (int j = 0; j < image1_gray.cols; j++)
{
output_image.at<uchar>(i, j) = 6 * log((double)(image1_gray.at<uchar>(i, j)) + 1); //对数变换 s=6*log(r+1)
}
}
normalize(output_image, output_image, 0, 255, NORM_MINMAX); //图像归一化,转到0~255范围内
convertScaleAbs(output_image, output_image); //数据类型转换到CV_8U
imshow(" output_image", output_image); //显示变换图像
waitKey(0); //暂停,保持图像显示,等待按键结束
return 0;
}
③伽马变换
伽马变换:原理:伽马变换通过应用幂函数对图像的每个像素值进行修改。伽马校正可以用于调整图像的对比度和亮度。公式为 output_pixel = c * (input_pixel ^ gamma),其中 c 是缩放常数,gamma 是伽马值。增大 gamma 值可以增加对比度。
//uchar用于单通道灰度图像,Vec3f和Vec3b用于三通道彩色图像,但前者使用浮点数表示颜色值,后者使用字节表示。
/*
伽马校正:
原理:伽马校正通过应用幂函数对图像的每个像素值进行修改。伽马校正可以用于调整图像的对比度和亮度。
公式为 output_pixel = c * (input_pixel ^ gamma),其中 c 是缩放常数,gamma 是伽马值。增大 gamma 值可以增加对比度。
*/
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image1, output_image, image1_gray; //定义输入图像,输出图像,灰度图像
image1 = imread("person.jpg"); //读取图像;
if (image1.empty())
{
cout << "读取错误" << endl;
return -1;
}
cvtColor(image1, image1_gray, COLOR_BGR2GRAY); //灰度化
imshow(" image1_gray", image1_gray); //显示灰度图像
output_image = image1_gray.clone();
for (int i = 0; i < image1_gray.rows; i++)
{
for (int j = 0; j < image1_gray.cols; j++)
{
output_image.at<uchar>(i, j) = 6 * pow((double)image1_gray.at<uchar>(i, j), 0.5); //幂律变换 s=6*r^0.5
}
}
normalize(output_image, output_image, 0, 255, NORM_MINMAX); //图像归一化,转到0~255范围内
convertScaleAbs(output_image, output_image); //数据类型转换到CV_8U
imshow(" output_image", output_image); //显示变换图像
waitKey(0); //暂停,保持图像显示,等待按键结束
return 0;
}
#include<iostream>
#include <opencv2/opencv.hpp>
#include <cmath>
// 使用给定的伽马值对彩色图像进行伽马变换
cv::Mat gammaTransformation(const cv::Mat& image, double gamma) {
cv::Mat transformedImage;
// 将图像转换为浮点型,以避免整数溢出
image.convertTo(transformedImage, CV_32F);
// 手动应用伽马变换公式
for (int y = 0; y < transformedImage.rows; ++y) {
for (int x = 0; x < transformedImage.cols; ++x) {
for (int c = 0; c < transformedImage.channels(); ++c) {
// 获取当前像素的值
float pixelValue = transformedImage.at<cv::Vec3f>(y, x)[c];
// 应用伽马变换:先将像素值归一化到[0, 1],然后应用伽马变换,最后再映射回[0, 255]
pixelValue = std::pow(pixelValue / 255.0, gamma) * 255.0;
// 将处理后的像素值存回图像中
transformedImage.at<cv::Vec3f>(y, x)[c] = pixelValue;
}
}
}
// 将浮点图像转换回8位无符号整数格式
transformedImage.convertTo(transformedImage, CV_8U);
return transformedImage;
}
int main() {
// Load an image
cv::Mat image = cv::imread("person.jpg");
if (image.empty()) {
std::cerr << "Could not open or find the image" << std::endl;
return -1;
}
// Apply gamma transformation
double gamma = 2.0; // Example gamma value
cv::Mat gammaCorrectedImage = gammaTransformation(image, gamma);
// Save the gamma-corrected image
//cv::imwrite("gamma_corrected_image.jpg", gammaCorrectedImage);
cv::imshow("origin_image", image);
cv::imshow("gamma_corrected_image", gammaCorrectedImage);
cv::waitKey(0); //暂停,保持图像显示,等待按键结束
return 0;
}
线性变换,对数变换和伽马变换通常需要进行归一化,尤其是在将变换后的像素值映射回原始图像的数据范围(通常是[0, 255])时。归一化的目的是确保变换后的像素值落在有效的显示和存储范围内。
cv::Mat src; // 假设这是一个浮点类型的图像
cv::Mat dst; // 用于存储归一化后的图像
// 将浮点图像进行最小-最大归一化到0-255范围,并转换为8位无符号整数图像
cv::normalize(src, dst, 0, 255, cv::NORM_MINMAX, CV_8U);
二、灰度直方图
灰度直方图反映的是一幅图像中灰度级与各灰度级像素出现的频率之间的关系。以灰度级为横坐标,纵坐标为灰度级的频率,绘制频率同灰度级的关系就是灰度直方图。频率计算公式为
式中: 是图像中灰度为i的像素数,n为图像总像素数。
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
Mat image, image_gray, hist; //定义输入图像,灰度图像, 直方图 img/red_panda.jpg img/white_panda.jpg img/orange.jpg img/football_ball.jpg img/baseball_ball.png
image = imread("cat.jpg"); //读取图像;cat.jpg LenaNoise.png img/hand.jpg img/black_to_white.jpg img/early_1800.jpg A013.jpg person.jpg
if (image.empty())
{
cout << "读取错误" << endl;
return -1;
}
cvtColor(image, image_gray, COLOR_BGR2GRAY); //灰度化 关联 HSV(色相,饱和度和明度)化是:COLOR_BGR2HSV
//namedWindow("image_gray", WINDOW_FREERATIO);//创建一个可调节的窗口 以免图片显示不全
imshow("image_gray", image_gray); //显示灰度图像
//获取图像直方图
int histsize = 256;
float ranges[] = {
0,256 };
const float* histRanges = {
ranges };
calcHist(&image_gray, 1, 0, Mat(), hist, 1, &histsize, &histRanges, true, false);
//创建直方图显示图像
int hist_h = 300;//直方图的图像的高
int hist_w = 512; //直方图的图像的宽
int bin_w = hist_w / histsize;//指定每个等级直方图条形的宽度,2
Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));//黑色图片上绘制直方图显示的图像
//绘制并显示直方图
normalize(hist, hist, 0, hist_h, NORM_MINMAX, -1, Mat());//归一化直方图
for (int i = 1; i < histsize; i++)
{
// 直方图是以条形图的形式从图像的底部开始绘制的,每个条形的顶部对应着该直方图条目的值,但实际左上角才是图像原点
// hist_h - cvRound(hist.at<float>(i - 1)) 这个表达式计算的是每个直方图条目的顶部在图像中的y坐标
line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(hist.at<float>(i - 1))),
Point((i)*bin_w, hist_h - cvRound(hist.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0); //BGR 蓝色
}
namedWindow("histImage", WINDOW_FREERATIO);//创建一个名为Image的可调节的窗口 以免图片显示不全
imshow("histImage", histImage);
waitKey(0); //暂停,保持图像显示,等待按键结束
return 0;
}
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//显示彩色图像三通道直方图
void showHist(Mat& img, Mat& dst)
{
//1、创建3个矩阵来处理每个通道输入图像通道。
//我们用向量类型变量来存储每个通道,并用split函数将输入图像划分成3个通道。
vector<Mat>bgr;
split(img, bgr);
//2、定义变量范围并创建3个矩阵来存储每个直方图
int histsize = 256;
float range[] = {
0,256 };
const float* histRange = {
range };
Mat b_hist, g_hist, r_hist;
//3、使用calcHist函数计算直方图
calcHist(&bgr[0], 1, 0, Mat(), b_hist, 1, &histsize, &histRange);
calcHist(&bgr[1], 1, 0, Mat(), g_hist, 1, &histsize, &histRange);
calcHist(&bgr[2], 1, 0, Mat(), r_hist, 1, &histsize, &histRange);
//4、创建一个512*300像素大小的彩色图像,用于绘制显示
int width = 512;
int height = 300;
Mat histImage(height, width, CV_8UC3, Scalar(20, 20, 20));
//5、将最小值与最大值标准化直方图矩阵
normalize(b_hist, b_hist, 0, height, NORM_MINMAX);
normalize(g_hist, g_hist, 0, height, NORM_MINMAX);
normalize(r_hist, r_hist, 0, height, NORM_MINMAX);
//6、使用彩色通道绘制直方图
int binStep = cvRound((float)width / (float)histsize); //通过将宽度除以区间数来计算binStep变量
for (int i = 1; i < histsize; i++)
{
line(histImage,
Point(binStep * (i - 1), height - cvRound(b_hist.at<float>(i - 1))),
Point(binStep * (i), height - cvRound(b_hist.at<float>(i))),
Scalar(255, 0, 0)
);
line(histImage,
Point(binStep * (i - 1), height - cvRound(g_hist.at<float>(i - 1))),
Point(binStep * (i), height - cvRound(g_hist.at<float>(i))),
Scalar(0, 255, 0)
);
line(histImage,
Point(binStep * (i - 1), height - cvRound(r_hist.at<float>(i - 1))),
Point(binStep * (i), height - cvRound(r_hist.at<float>(i))),
Scalar(0, 0, 255)
);
}
dst = histImage;
return;
}
//显示灰度直方图
void showgrayHist(Mat& img_gray, Mat& dst)
{
//1、定义变量范围并创建3个矩阵来存储每个直方图
int histsize = 256;
float range[] = {
0,256 };
const float* histRange = {
range };
Mat grayhist;
//2、使用calcHist函数计算直方图
calcHist(&img_gray, 1, 0, Mat(), grayhist, 1, &histsize, &histRange);
//3、创建一个512*300像素大小的彩色图像,用于绘制显示
int width = 512;
int height = 300;
Mat histImage(height, width, CV_8UC3, Scalar(20, 20, 20));
//4、将最小值与最大值标准化直方图矩阵
normalize(grayhist, grayhist, 0, height, NORM_MINMAX);
//5、使用彩色通道绘制直方图
int binStep = cvRound((float)width / (float)histsize); //通过将宽度除以区间数来计算binStep变量
for (int i = 1; i < histsize; i++)
{
line(histImage,
Point(binStep * (i - 1), height - cvRound(grayhist.at<float>(i - 1))),
Point(binStep * (i), height - cvRound(grayhist.at<float>(i))),
Scalar(255, 0, 0),2,8,0
);
}
dst = histImage;
return;
}
int main()
{
Mat src = imread("cat.jpg");
Mat histImage;
if (!src.data)
{
cout << "No src data!" << endl;
}
else
{
imshow("src", src);
}
showHist(src, histImage);
Mat image_gray, grayhistImage;
cvtColor(src, image_gray, COLOR_BGR2GRAY); //灰度化 关联 HSV(色相,饱和度和明度)化是:COLOR_BGR2HSV
imshow("image_gray", image_gray);
showgrayHist(image_gray,grayhistImage);
//namedWindow("histImage", WINDOW_FREERATIO);//创建一个名为Image的可调节的窗口 以免图片显示不全
namedWindow("histImage", 0);
imshow("histImage", histImage);
namedWindow("grayhistImage", 0);
imshow("grayhistImage", grayhistImage);
waitKey(0);
return 0;
}
其他图片直方图:
三、直方图均衡化
/*
直方图均衡化:
原理:直方图均衡化旨在拉伸图像的灰度级别分布,使其更均匀。这通过重新分配像素值以增加亮度级别的数量来实现。
这有助于增强图像的对比度,并突出图像中的细节。该方法的原理是重新映射图像的累积分布函数,使其变为均匀分布。
*/
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv