OpenCV基础(4)--图像预处理

1. 颜色空间

1.1 BGR颜色空间

在opencv中,三原色顺序为BGR,每个通道灰度级为0-255,(0,0,0)则说明无颜色混合,为黑色,(255,255,255)为白色。opencv中还有4通道,为BGRA模型,A表示透明度,也叫alpha阿尔法通道。

1.2 HSV颜色空间

H: hue色度,对应颜色,如红绿蓝;

S:saturation饱和度,对应于深浅,类似于灰度级;

V:value亮度,对应于图像的明暗。

HSV更符合人眼感知颜色的方式。

1.3 GRAY灰度颜色空间

0-255层级,由黑----到白的变化。单通道。

1.4 cvtColor()颜色转化

此外还有YUV,Lab等颜色空间,不详细叙述。

cvtColor()颜色空间转换方式为:

COLOR_BGR2GRAY: BGR--->GRAY

COLOR_BGR2HSV: BGR---->HSV

COLOR_HSV2BGR: HSV--->BGR

通俗易懂。

示例程序:

// BGR空间转为HSV
cvtColor(origin_img, new_img, COLOR_BGR2HSV);

1.5 通道颜色分离与合并

2个概念:

  • 像素深度:存储每个像素的位数,例如CV_8U为8位;
  • 图像深度:实际用于存储像素位数的深度,如定义16位只用到15位,则为15位图像深度。

对于BGR颜色空间,有3个通道。有时只想处理R红色,则需要分离,处理完成后又需要合并B,G,R像素。

opencv提供方法为:分离函数cv::split(),以及合并函数cv::merge()。

【示例】分离图片的通道,修改R的数值,再合并:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main() {
    // 读取图像
    cv::Mat img = cv::imread("D:/Project_Images/ikun.png");

    if (img.empty()) {
        std::cout << "无法打开图像" << std::endl;
        return -1;
    }

    // 分离 RGB 三个通道
    std::vector<cv::Mat> channels;
    cv::split(img, channels);

    // 显示分离后的三个通道:B G R
    cv::imshow("Blue Channel", channels[0]);
    cv::imshow("Green Channel", channels[1]);
    cv::imshow("Red Channel", channels[2]);

    // 修改 R 通道数值
    cv::Mat& r_channel = channels[2];
    // 阈值化处理像素值
    cv::threshold(r_channel, r_channel, 128, 255, cv::THRESH_BINARY);

    // 合并三个通道为一张图像
    cv::Mat merged_img;
    cv::merge(channels, merged_img);        


    cv::namedWindow("Split and Merge", cv::WINDOW_NORMAL);
    cv::imshow("Split and Merge", merged_img);
    cv::waitKey(0);

    // 关闭窗口
    cv::destroyAllWindows();

    return 0;
}

示例效果:

2. 点运算

常用于改变图像的灰度范围及分布,有时也称为对比度增强,对比度拉伸或灰度变换。包括图像灰度变换,直方图处理,伪色彩处理等技术。

2.1 像素点操作和卷积

1. 首先需要学会对像素值访问,在本系列博客讲义之前的Mat数据读取中讲过。

1. 指针法
src.ptr<Vec3b>(i)[j][c] = 255;    // 注意() []

ptr 是Mat中定义好的类函数
先索引i行的地址,再找到j的位置

2. at访问
src.at<Vec3b>(i, j)[c] = 0;

2. 像素点亮度处理

示例:

src.at<Vec3b>(i,j)[c] = saturate_cast<uchar>(src.at<Vec3b>(i, j)[c] + 5 )

直接在像素值上加或减即可,分别对应提高和降低亮度

3. 图像卷积:

去噪,滤波,边缘提取都需要使用到图像卷积,目的是使各个目标的差距变得更大。常见的应用为锐化(突出边缘)和边缘提取。

4. 卷积原理和过程:

数字图像是二维的离散信号,卷积操作是将卷积核在图像上滚动,将图像上的像素灰度值和对应的卷积核上的数值相乘,然后将相乘的值相加作为卷积核中心上的值(可正可负)。

5. 卷积核选择规则:

  • 大小奇数,例如3*3,5*5,7*7;
  • 所有元素之和为1,是为了维持原图像的能量守恒,也有不为1的特殊情况;
  • 如果卷积后的矩阵所有元素和>1, 比原图像会更亮,< 1,会比原图像更暗。 = 0, 不会全黑但会很暗。

6. opencv中提供filter2D()实现卷积操作

示例程序:

filter2D(src, dst, src.depth(), kernel);
// 原图像,输出图,图像深度,卷积核

使用二维卷积操作:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
    Mat src_image = imread("D:/Project_Images/ikun.png");
    imshow("origin image", src_image);
    // 创建一个卷积核
    Mat kernel = (Mat_<double>(3, 3) <<
        -1, 0, 1,
        -2, 0, 2,
        -1, 0, 1);
    Mat dst_image;
    filter2D(src_image, dst_image, src_image.depth(), kernel);
    imshow("convert image", dst_image);
    waitKey(0);
    return 0;
}

运行效果:

明显可以看出,ikun的轮廓被突出。

7. 卷积操作调参比较简单,不同卷积操作只需要改变卷积核即可,以下是几种常见的卷积核:

均值滤波核:

1/ 91/ 91/ 9
1/ 91/ 91/ 9
1/ 91/ 91/ 9

高斯滤波核(正态分布)

1/162/161/16
2/164/162/16
1/162/161/16

边缘锐化核:

-1-1-1
-19-1
-1-1-1

可以看到,这个卷积核,刻意将边缘像素值与中心点像素值区分开来。有很明显的锐化效果。

2.2 图像像素值反转

适用于增强嵌入在图像暗色区域的白色或灰色细节,简而言之,黑色变白色,白变黑。

示例:遍历像素点反转灰度图的像素值

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
    Mat src;
    int height, width;
    int i, j;
    src = imread("D:/Project_Images/ikun_gray.png");
    if (!src.data)
    {
        cout << "no image!" << endl;
        return -1;
    }
    height = src.rows;
    width = src.cols * src.channels();
    imshow("origin image", src);
    // 遍历操作像素值
    for (i = 0; i < height; i++)
    {
        for (j = 0; j < width; j++)
        {                      
            src.at<uchar>(i, j) = 255 - src.at<uchar>(i, j);
        }
    }
    imshow("convert image", src);
    waitKey(0);
    return 0;
}

可以看到,黑白颜色交换了。

也可以使用OpenCV提供的接口方法:

bitwise_not(src, dst):位反转,就是上面的代码操作
bitwise_and(src, dst):位与操作
bitwise_or(src, dst): 或操作
bitwise_xor(src, dst): 异或操作

2.3 像素值对数变换

可以拉伸范围较窄的低灰度值,同时压缩范围较宽的高灰度值,可以用来扩展图像中的暗像素值,同时压缩亮像素值。

示例程序:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
// 将每个像素点的3个数值都变换
void log_transfor(Mat& img, Mat& result)
{
    result = img.clone();
    int rows = img.rows;
    int cols = img.cols;
    for (int i = 0; i < rows; i++)
    {
        for (int j = 0; j < cols; j++)
        {
            for (int k = 0; k < 3; k++)
            {
                // 彩色图像对数变换
                result.at<Vec3b>(i, j)[k] = \
                            31 * log2(1 + img.at<Vec3b>(i, j)[k]);
            }
        }
    }
}

int main()
{
    Mat img = imread("D:/Project_Images/ikun.png");
    Mat result;
    log_transfor(img, result);
    imshow("origin image", img);
    imshow("convert image", result);
    waitKey(0);
    return 0;
}

运行结果:

2.4 图像灰度化

2个常识:

  • 灰度值是形容黑白图像的,亮度值是形容彩色图像的。将彩色图像转换为黑白图像后,亮度值就会转换为灰度值。
  • B,G,R混色后会偏向灰色,因此灰度化就是需要BGR同时参与一定比例即可。

图像灰度化有2种方法:

  1. 平均值法:将B,G,R亮度分量相加除以3;
  2. 加权平均法:由于人眼对绿色灵敏度高,蓝色灵敏度低,因此加权才更加合理:
gray(x, y) = r(x, y) *0.3 +g(x, y) *0.59 + b(x, y) *0.11

OpenCV给出的接口cvtColor()使用的是加权平均法。

3. 直方图处理

直方图是表示图像中亮度分布(黑白则为灰度分布),给出的是图像中某个亮度下共有几个像素,即统计数量。

  • 缺点在于不能反应像素的位置,失去了空间信息。
  • 优点在于计算代价小,可以图像平移,旋转,缩放不变性。

常用于灰度图像的阈值分割,基于颜色的图像检索,图像分类。

3.1 标准直方图

直方图就是一副像素统计图像。

以绘制灰度直方图为例,需要使用2个接口:calcHist()计算直方图, normalize()数据归一化;

程序示例如下:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
// 绘制标准直方图
int main()
{
    // 1. 读取图像
    Mat image = imread("D:/Project_Images/cat2.jpeg", IMREAD_GRAYSCALE); //图片先灰度化
    if (image.empty())
    {
        std::cout << "Failed to load image!" << std::endl;
        return -1;
    }

    // 2. 计算直方图
    int histSize = 256;  // 箱子数量(横坐标的份数)
    float range[] = { 0, 256 };  // 使用数组,定义像素值范围
    const float* histRange = range;
    // 横坐标间隔均匀,纵坐标不累加个数值
    bool uniform = true, accumulate = false;
    Mat hist;
    // 输入图像,1张图,不指定通道,无掩码,输出图像,1维,...
    calcHist(&image, 1, nullptr, Mat(), hist, 1, &histSize, &histRange, \
    uniform, accumulate);

    // 3. 创建直方图画布
    int histWidth = 512, histHeight = 400;
    // 除以256个箱子计算每个箱子宽度,cvRound用于取四舍五入的整数
    int binWidth = cvRound((double)histWidth / histSize);    
    Mat histImage(histHeight, histWidth, CV_8UC3, Scalar(0, 0, 0));

    // 4. 归一化直方图数据,变换到[0, 行数]的范围,以适应histImage窗口的高度,Mat()为空矩阵
    normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());

    // 5. 绘制直方图
    for (int i = 1; i < histSize; i++)
    {
        line(histImage, Point(binWidth * (i - 1), \
        histHeight - cvRound(hist.at<float>(i - 1))),\
        Point(binWidth * i, histHeight - cvRound(hist.at<float>(i))), \
        Scalar(0, 255, 0), 2, LINE_AA);
    }

    // 显示直方图
    namedWindow("Histogram", WINDOW_AUTOSIZE);
    imshow("Histogram", histImage);

    // 等待按键退出
    waitKey(0);

    return 0;
}

运行结果:

3.2 直方图均衡化

是为了增强图像的对比度,也叫直方图线性变换。主要思想是将一幅图像的直方图分布变成近似均匀分布,从而增强图像的对比度。基本原理是对图像中像素个数多的灰度值进行展宽,对像素较少的灰度值进行归并,从而加强对比度,使图像清晰。

OpenCV提供的方法为equalizeHist(image, result);

image必须是8位单通道灰度图。

示例:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
    Mat src_img, dst_img;
    src_img = imread("D:/Project_Images/animal.jpeg", IMREAD_GRAYSCALE);
    if (src_img.empty())
    {
        cout << "no image exist!" << endl;
        return -1;
    }
    imshow("origin image", src_img);
    equalizeHist(src_img, dst_img);    // 就这一行代码起作用
    imshow("equalize histogram", dst_img);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

实现效果:

效果上:增强对比度。

3.3 直方图匹配

简单来讲,将原图的像素值分布转为目标图的风格。

步骤:

  1. 将原图像的灰度直方图均衡化;
  2. 对规定的直方图均衡化;
  3. 建立原图像和目标图像的映射关系。

需要使用映射函数LUT。

示例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;
// 直方图匹配
int main()
{
    // 加载原始图像和目标图像
    Mat srcImage = imread("D:/Project_Images/animal.jpeg", IMREAD_GRAYSCALE);
    Mat targetImage = imread("D:/Project_Images/panda.jpg", IMREAD_GRAYSCALE);

    // 计算原始图像和目标图像的直方图
    Mat srcHist, targetHist;
    int histSize = 256;  // 直方图横轴范围是 [0, 255]
    float range[] = { 0, 256 };
    const float* histRange = { range };
    calcHist(&srcImage, 1, 0, Mat(), srcHist, 1, &histSize, &histRange);
    calcHist(&targetImage, 1, 0, Mat(), targetHist, 1, &histSize, &histRange);

    // 对原始图像进行直方图匹配
    Mat lookupTable(1, 256, CV_8U);
    float srcSum = sum(srcHist)[0];
    float targetSum = sum(targetHist)[0];
    float srcAccumulator = 0, targetAccumulator = 0;
    for (int i = 0; i < 256; ++i)
    {
        srcAccumulator += srcHist.at<float>(i) / srcSum;
        targetAccumulator += targetHist.at<float>(i) / targetSum;
        lookupTable.at<uchar>(i) = cvRound(255 * srcAccumulator / targetAccumulator);
    }

    // 应用直方图映射到原始图像
    Mat matchedImage;
    LUT(srcImage, lookupTable, matchedImage);

    // 显示原始图像、目标图像和匹配后的图像
    imshow("Original Image", srcImage);
    imshow("Target Image", targetImage);
    imshow("Matched Image", matchedImage);
    waitKey(0);

    return 0;
}

效果示例:

原图像风格会趋向目标图像。

4. 图像去噪

去除图像中的噪声,减少干扰信息,也叫做滤波过程。

4.1 均值滤波

以3*3的卷积核为例,均值卷积核为9个格子都相等的卷积核,与原图像做卷积操作。

1/ 91/ 91/ 9
1/ 91/ 91/ 9
1/ 91/ 91/ 9

OpenCV提供的函数接口为blur()。

示例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;
int main()
{
    Mat src_img = imread("D:/Project_Images/noise.jpg");
    imshow("origin image", src_img);
    Mat dst_img;
    dst_img.create(src_img.size(), src_img.type());
    // 调用接口blur,这里选用6*6的核
    blur(src_img, dst_img, Size(6, 6));    
    imshow("filter image", dst_img);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

实现效果:

4.2 高斯滤波

先了解4个概念:

低频成分:图像中亮度或灰度值变化不大的部分;

高频成分:图像中亮度或灰度值起伏很大的部分,例如黑白交替条纹。

低通滤波:保留低频成分

高通滤波:保留高频成分

高斯滤波:使用高斯卷积核对图像做卷积处理,平滑图像,属于低频滤波。

OpenCV提供的函数为GuassianBlur()。

示例:

GaussianBlur(src_img, dst_img, Size(7, 7), 0, 0);

效果:

对比均值,在边缘部分,信息丢失没有那么严重。

4.3 中值滤波

使用的滤波窗口取中值。例如3*3的滤波窗口(空窗口,非卷积操作),对图像中该窗口中的9个像素值取中值,将9个像素值全部换成中值。然后滑动窗口,重复操作。

OpenCV函数:medianBlur()

示例:

medianBlur(src_img, dst_img, 7);

效果:

效果:对均值和高斯的折中,兼顾滤波和边缘信息保留。

考点:中值滤波适合去除椒盐噪声(黑、白点噪声)。

原因:使用中值替代,黑色、白色像素孤立点被邻域中的中值同化,自然噪声消失。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值