opencv图像处理

图像滤波

图像的滤波核心是使用一个小的矩阵(滤波器或卷积核)在图像上进行滑动卷积,将计算得到的结果作为目标像素的值

均值滤波

cv::blur(InputArray src, OutputArray dst, Size ksize,
         Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT);
  • src:输入的原始图像,支持Mat格式
  • dst:经过滤波处理的图像
  • ksize:滤波器大小,通常为2D大小,表示滤波的宽度和高度。
  • anchor:锚点指定窗口内的参考点,默认是Point(-1,-1)表示窗口中心点
  • boarderType:边界类型,用来处理边界参数的外推方式。
cv::Mat src = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat dst;
cv::blur(src, dst, cv::Size(3, 3));  // 3x3的均值滤波

高斯滤波

void cv::GaussianBlur( const cv::Mat& src, cv::Mat& dst, cv::Size ksize,
    double sigmaX, double sigmaY = 0, int borderType = cv::BORDER_DEFAULT );
  • sigmaX:X方向的标准差,决定模糊的程度,值越大,模糊效果越强。
  • sigmaY:Y方向的标准差,默认是0,表示与X方向相同。
cv::GaussianBlur(src, dst, cv::Size(5, 5), 0);  // 5x5的高斯滤波

中值滤波

void medianBlur(InputArray src, OutputArray dst, int ksize);
  • ksize: 滤波器窗口大小

示例

cv::medianBlur(src, dst, 5);  // 5x5的中值滤波

示例

下面示例,将图片使用高斯噪音处理,然后再使用滤波器进行模糊,噪音就可以看起来过滤掉。

#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <linux/fb.h>
#include <signal.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

void add_gaussian_noise(Mat& image, double mean, double stddev)
{
    Mat noise(image.size(), image.type());

    randn(noise, mean, stddev);

    image = image + noise;
}

void display_image(const Mat& image, int framebuffer_width = 720)
{
    static std::ofstream ofs("/dev/fb0");
    cv::Mat framebuffer;
    cv::cvtColor(image, framebuffer, cv::COLOR_BGR2BGRA);
    cv::Size2f frame_size = framebuffer.size();

    for (int y = 0; y < frame_size.height; y++) {
        ofs.seekp(y * framebuffer_width * 4);
        ofs.write(reinterpret_cast<char*>(framebuffer.ptr(y)), frame_size.width * 4);
    }
}

int main(int, char**)
{
    Mat image = imread("test1.jpg", IMREAD_COLOR);
    if (image.empty()) {
        cerr << "Error: Could not open or find the image!" << endl;
        return -1;
    }

    double mean = 2.0;
    double stddev = 30.0;

    add_gaussian_noise(image, mean, stddev);

    display_image(image);
    cv::imwrite("noise.jpg", image);

    usleep(1000 * 1000 * 2);

    Mat dst;
    cv::GaussianBlur(image, dst, cv::Size(3, 3), 1); 
    //cv::medianBlur(image, dst, 5);
    //cv::blur(src, dst, cv::Size(3, 3));
    display_image(dst);
    cv::imwrite("dst.jpg", dst);
}


如下图,中间是加了噪声的,最后一张是通过高斯滤波处理之后的。

图像形态学

膨胀与腐蚀

膨胀操作会增加图像中的白色区域或前景区域,使得图像中的对象或结构变得更大。具体来说,它会将图像中的每个像素点扩展到其邻域像素中(根据结构元素的大小和形状)。这意味着如果结构元素的一部分重叠在一个前景区域内,该区域就会被扩展。使白色区域扩展,物体变大。

cv2.dilate(src, dst=None, kernel, anchor=(-1, -1), iterations=1, 
    borderType=cv2.BORDER_CONSTANT, borderValue=0)
  • iteration:膨胀迭代次数,越大效果越明显
  • borderType:边界填充类型
  • borderValue:边界填充值,默认0是黑色
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
//创建一个3*3的矩形结构元素
cv::dilate(src, dst, kernel);

腐蚀操作的效果与膨胀相反,它会减少图像中的白色区域或前景区域,使得图像中的对象变得更小。腐蚀操作会检查每个像素的邻域,如果邻域内的所有像素都是前景像素(白色),该像素才保持前景,否则变为背景(黑色)。使白色区域收缩,物体变小。

cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
cv::erode(src, dst, kernel);

以下是示例

#include <fcntl.h>
#include <fstream>
#include <iostream>
#include <linux/fb.h>
#include <signal.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <opencv2/opencv.hpp>


void display_image(const cv::Mat& image, int framebuffer_width = 720)
{
    static std::ofstream ofs("/dev/fb0");
    if (!ofs) {
        std::cerr << "Error: Could not open framebuffer device!" << std::endl;
        return;
    }

    cv::Mat framebuffer;
    if (image.channels() == 1) {
        cv::cvtColor(image, framebuffer, cv::COLOR_GRAY2BGRA);
    } else if (image.channels() == 3) {
        cv::cvtColor(image, framebuffer, cv::COLOR_BGR2BGRA);
    } else if (image.channels() == 4) {
        image.copyTo(framebuffer);
    } else {
        std::cerr << "Error: Unsupported image format!" << std::endl;
        return;
    }

    cv::Size2f frame_size = framebuffer.size();

    if (frame_size.width > framebuffer_width) {
        // 计算新的宽高,保持图像的纵横比
        float aspect_ratio = frame_size.height / frame_size.width;
        int newWidth = framebuffer_width;
        int newHeight = static_cast<int>(newWidth * aspect_ratio);

        // 调整图像大小
        cv::Mat resizedImage;
        cv::resize(framebuffer, resizedImage, cv::Size(newWidth, newHeight));
        framebuffer = resizedImage;
        frame_size = resizedImage.size();
    }

    for (int y = 0; y < frame_size.height; y++) {
        ofs.seekp(y * framebuffer_width * 4);
        ofs.write(reinterpret_cast<char*>(framebuffer.ptr(y)), frame_size.width * 4);
    }
}

int main(int, char**)
{
    cv::Mat image = imread("test1.jpg", cv::IMREAD_COLOR);
    if (image.empty()) {
        std::cerr << "Error: Could not open or find the image!" << std::endl;
        return -1;
    }

    double mean = 2.0;
    double stddev = 30.0;


    display_image(image);

    usleep(1000 * 1000 * 1);

    cv::Mat dst;
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));

    cv::dilate(image, dst, kernel);

    display_image(dst);

    usleep(1000 * 1000 * 1);

    cv::erode(image, dst, kernel);

    display_image(dst);
}

阈值化

阈值(Thresholding)是一种常见的图像分割方法,用于将灰度图像转换为二值图像。通过设置一个阈值,将像素值高于该阈值的区域设为白色(255),低于阈值的区域设为黑色(0),从而简化图像内容,便于后续处理。

二值化阈值:二值化是将图像中的像素值根据设定的阈值分为两类,通常用于简单的图像分割。

cv::threshold(src, dst, thresh, maxval, type);

示例:cv::threshold(src, dst, 127, 255, cv::THRESH_BINARY);
  • thresh: 阈值,决定像素分类的分界线。
  • maxval:根据type选择,满足条件设置为最大值。
  • type: 阈值类型

自适应阈值:根据图像的局部区域动态计算阈值,适用于光照不均匀的图像。

cv::adaptiveThreshold( src, dst, maxValue, adaptiveMethod,
     thresholdType, blockSize, C);

  • maxVaule:二值化的最大值,一般为255
  • adaptiveMethod: ADAPTIVE_THRESH_MEAN_C是使用邻域的均值作为阈值,ADAPTIVE_THRESH_GAUSSIAN_C :使用邻域的加权均值(高斯加权)作为阈值。
  • thresholdType:阈值类型,通常使用 THRESH_BINARY 或 THRESH_BINARY_INV。
  • blockSize:邻域的大小,必须是奇数(例如 3 , 5 , 7 , ...),该窗口在图像上滑动。
  • C:调整值,用于从计算的阈值中减去。调整结果的灵敏度。
cv::adaptiveThreshold(src, dst, 255,
        cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 11, 2);

开闭运算、顶帽、黑帽

  • 开运算:开运算是先进行腐蚀操作再进行膨胀操作。它通常用于去除小的噪点或小的物体,同时保留较大区域的结构。
  • 闭运算:闭运算是先进行膨胀操作再进行腐蚀操作。它用于去除小的黑色区域(如小孔洞或空隙),并连接物体之间的细小裂缝。
  • 顶帽:顶帽是开运算的结果与原图像之间的差异,主要用于提取比背景更亮的区域或小的亮点。
  • 黑帽:黑帽是闭运算的结果与原图像之间的差异,主要用于提取比背景更暗的区域或小的暗点。
void cv::morphologyEx( InputArray src,  OutputArray dst, int op,
    InputArray kernel, Point anchor = Point(-1, -1), int iterations = 1,
    int borderType = BORDER_CONSTANT,
    const Scalar&borderValue=morphologyDefaultBorderValue()  );
  • kernel:结构元素,通常使用 cv::getStructuringElement() 生成。
  • op:形态学操作类型。

下面是op的类型

  • cv:MORPH_OPEN:开运算。
  • cv::MORPH_CLOSE:闭运算。
  • cv::MORPH_GRADIENT: 形态学梯度(膨胀 - 腐蚀),提取物体边缘。
  • cv::MORPH_TOPHAT :顶帽运算(原图 - 开运算结果),提取比背景亮的区域。
  • cv::MORPH_BLACKHAT 黑帽运算(闭运算结果 - 原图),提取比背景暗的区域。

示例

示例程序如下:

int main(int, char**)
{
    cv::Mat image = imread("test1.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cerr << "Error: Could not open or find the image!" << std::endl;
        return -1;
    }

    cv::Mat binary_img;
    cv::threshold(image, binary_img, 128, 255, cv::THRESH_BINARY);

    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));

    cv::Mat dst;

    cv::Mat opened_img, closed_img, tophat_img, blackhat_img;

    cv::morphologyEx(binary_img, opened_img, cv::MORPH_OPEN, kernel);
    cv::morphologyEx(binary_img, closed_img, cv::MORPH_CLOSE, kernel);
    cv::morphologyEx(binary_img, tophat_img, cv::MORPH_TOPHAT, kernel);
    cv::morphologyEx(binary_img, blackhat_img, cv::MORPH_BLACKHAT, kernel);


    display_image(image);
    usleep(1000 * 1000 * 2);
    display_image(binary_img);
    usleep(1000 * 1000 * 2);
    display_image(closed_img);
    usleep(1000 * 1000 * 2);
    display_image(tophat_img);
    usleep(1000 * 1000 * 2);
    display_image(blackhat_img);

}

图像边缘检测

canny

Canny边缘检测是一个多阶段的图像处理算法,用于提取图像中的边缘。它的目标是识别图像中强度变化较大的区域,即边缘

int main(void)
{
    cv::Mat image = cv::imread("test1.jpg", cv::IMREAD_GRAYSCALE);

    if (image.empty()) {
        std::cerr << "Error: Could not open image!" << std::endl;
        return -1;
    }

    // 进行 Canny 边缘检测
    cv::Mat edges;
    cv::Canny(image, edges, 100, 200); // 低阈值 100,高阈值 200
    display_image(edges);
    return 0;
}

下面是处理的前后效果。

霍夫变换

霍夫变换(Hough Transform)是一种用于检测图像中的几何形状(如直线、圆等)的方法。它主要用于图像中的特征检测,特别是在噪声较大的图像中,可以有效地进行形状的识别。霍夫变换的基本思想是将图像中的边缘点通过一种数学映射,转化到参数空间,然后在参数空间中寻找对应的曲线(或直线、圆等)。

霍夫线变换: 检测线。

cv2.HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap)
  • image: 输入图像,通常是经过边缘检测(如 Canny 边缘检测)后的二值图像。
  • rho: 直线距离精度,单位是像素,通常设置为 1。
  • theta: 角度精度,单位是弧度,通常设置为 np.pi / 180(即 1°)。
  • threshold: 最小投票数,当某一候选直线在参数空间中的投票数大于此阈值时,认为检测到直线。
  • minLineLength: 最小直线长度。只有长度大于该值的直线才会被返回。
  • maxLineGap: 最大直线间隙。如果两段直线之间的间隙小于该值,则认为它们是同一条直线的两部分
int main(void)
{
    cv::Mat img = cv::imread("test1.jpg");
    if (img.empty()) {
        std::cerr << "Error: Could not open image!" << std::endl;
        return -1;
    }

    // 图像预处理:边缘检测
    cv::Mat edges;
    cv::Canny(img, edges, 50, 150);  // 使用 Canny 边缘检测

    display_image(edges);

    usleep(1000 * 1000);

    // 使用 HoughLinesP 进行霍夫变换(概率霍夫变换)
    std::vector<cv::Vec4i> lines;
    cv::HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10);  // 参数为 (边缘图像, 输出的直线, ρ精度, θ精度, 阈值, 最小线段长度, 最大线段间隙)

    // 绘制检测到的直线
    cv::Mat result = img.clone();
    //cv::cvtColor(result, result, cv::COLOR_GRAY2BGR);  // 将灰度图转为彩色图以显示彩色直线
    for (size_t i = 0; i < lines.size(); i++) {
        cv::Vec4i l = lines[i];
        cv::line(result, cv::Point(l[0], l[1]), 
            cv::Point(l[2], l[3]), cv::Scalar(255, 0, 0), 2);  // 绘制直线
    }

    display_image(result);

    return 0;
}

上面的示例中,先使用canny进行边缘处理,然后送入到霍夫变换中进行检测,最后绘制成直线。下面是检测后的效果。

霍夫圆变换: 检测圆。

cv2.HoughCircles(image, method, dp, minDist, param1, param2, minRadius, maxRadius)
  • image: 输入图像,通常是灰度图像。
  • method: 霍夫变换的检测方法。对于 HoughCircles,一般使用 cv2.HOUGH_GRADIENT。
  • dp: 分辨率反比,表示霍夫空间的分辨率与图像分辨率的比例。通常设置为 1。
  • minDist: 设定圆心之间的最小距离,防止检测到相互靠得太近的圆。
  • param1: 边缘检测的高阈值(Canny 边缘检测的高阈值)。
  • param2: 圆的中心检测阈值,较小的值会检测到更多的圆,较大的值会检测较少的圆。
  • minRadius: 最小圆半径(单位:像素)。
  • maxRadius: 最大圆半径(单位:像素)。
int main(void)
{
    cv::Mat img = cv::imread("test1.jpg");
    if (img.empty()) {
        std::cerr << "Error: Could not open image!" << std::endl;
        return -1;
    }

    // 进行边缘检测
    cv::Mat edges;
    cv::Canny(img, edges, 50, 150);

    display_image(edges);
    usleep(1000 * 1000);

    // 存储检测到的圆
    std::vector<cv::Vec3f> circles;
    // 使用霍夫圆变换检测圆
    cv::HoughCircles(edges, circles, cv::HOUGH_GRADIENT, 1, 23, 80, 52, 27, 100);

    // 在原图上绘制检测到的圆
    cv::Mat result = img.clone();

    for (size_t i = 0; i < circles.size(); i++) {
        cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
        int radius = cvRound(circles[i][2]);
        // 绘制圆心
        cv::circle(result, center, 3, cv::Scalar(0, 255, 0), -1);
        // 绘制圆
        cv::circle(result, center, radius, cv::Scalar(255, 0, 0), 3);
    }
    display_image(result);

    return 0;
}

同样是先使用canny检测进行处理,然后再使用霍夫圆进行检测圆,最后试用cv::circle进行绘制。