3、图像读取与显示:imread()函数详解、imshow()窗口管理、waitKey()等待机制

好,咱们进入第三个核心环节。图像读取与显示,说白了就是OpenCV的“敲门砖”。你连图都读不进来、显示不出来,后面那些花里胡哨的处理就全是空谈。

我个人习惯,每次写新项目,第一件事就是先跑通imread和imshow。这就像炒菜前先热锅,是个仪式感,也是个验证环境的好办法。

3.1 imread():把硬盘里的图“请”进内存

这个函数,是OpenCV里最常用的函数之一,没有“之一”可能有点夸张,但绝对排前三。

它的原型很简单:

cv::Mat img = cv::imread(const String& filename, int flags = IMREAD_COLOR);

两个参数:

  • filename:图片路径。可以是绝对路径,也可以是相对路径。我建议你一开始就用绝对路径,省得找半天找不到图。
  • flags:读取方式。这个参数决定了图片以什么“颜色模式”进入内存。

flags的常用值,我整理了一张表,你存着:

标志 数值 含义
IMREAD_COLOR 1 默认。加载为BGR三通道彩色图。忽略透明通道。
IMREAD_GRAYSCALE 0 加载为单通道灰度图。
IMREAD_UNCHANGED -1 加载原图,包括Alpha通道(如果有的话)。

嗯,这里要注意一点:OpenCV默认的颜色通道是BGR,不是RGB。很多新手在这里栽跟头。我曾经在项目里用imread读了一张图,然后用matplotlib显示,结果颜色全偏蓝。折腾了半天才发现是通道顺序的问题。

避坑指南: 我曾经因为忘记加路径检查,程序跑了一整天,最后发现是图片路径里有个中文空格。imread()如果读不到图,不会报错,只会返回一个空Mat。所以,读图后一定要加个判断:
if (img.empty()) {
    std::cerr << "图片读失败了,检查路径!" << std::endl;
    return -1;
}

3.2 imshow():把内存里的图“亮”出来

图读进来了,怎么看到它?用imshow。

cv::imshow(const String& winname, InputArray mat);

两个参数:

  • winname:窗口名字。你可以随便起,但最好有意义。比如“Original Image”、“Processed Result”。
  • mat:要显示的图像数据。

窗口管理这块,有几个小细节:

  • 窗口大小:默认情况下,窗口会自适应图片大小。如果图片太大,你可以用cv::namedWindow()先创建窗口,再设置属性。比如cv::WINDOW_NORMAL可以让窗口自由缩放。
  • 多个窗口:你可以同时显示多个窗口,只要窗口名字不同就行。我调试的时候经常开三四个窗口,原图、灰度图、边缘检测图,一目了然。
  • 窗口销毁:用cv::destroyWindow("窗口名")销毁单个窗口,或者cv::destroyAllWindows()一键清空。
小技巧: 如果你想让窗口显示在屏幕正中央,可以用cv::moveWindow()手动调整位置。不过说实话,我很少用这个,默认位置也挺好。

3.3 waitKey():让程序“等一等”

这个函数,是很多初学者的“噩梦”。为什么我显示了图片,窗口一闪就没了?

答案就在waitKey()。

int cv::waitKey(int delay = 0);

它的作用很简单:等待用户按键。

  • delay = 0:无限等待,直到用户按下任意键。
  • delay > 0:等待delay毫秒。如果超时没有按键,程序继续往下走。

为什么会这样?因为imshow()只是把图像数据“推送”到了显示缓冲区,但真正刷新到屏幕上,需要waitKey()来触发。说白了,waitKey()给了OpenCV一个“喘口气、刷新一下”的机会。

我刚开始学的时候,也犯过这个错。写了imshow,没写waitKey,结果窗口一闪而过,我还以为是电脑坏了。后来查了文档才明白,原来OpenCV的事件循环是靠waitKey()驱动的。

核心要点: imshow() + waitKey() 是固定搭配。没有waitKey(),窗口就显示不出来。记住这个公式:
cv::imshow("窗口名", img);
cv::waitKey(0);  // 等待按键

另外,waitKey()的返回值也很有用。它返回的是按键的ASCII码。你可以用它来做交互控制:

int key = cv::waitKey(0);
if (key == 'q') {
    std::cout << "用户按了q,退出" << std::endl;
} else if (key == 's') {
    cv::imwrite("save.jpg", img);
    std::cout << "图片已保存" << std::endl;
}

你想想看,这样是不是就能做一个简单的按键响应程序了?

3.4 完整示例:一条龙走一遍

说了这么多,咱们来一个完整的例子。把读取、显示、等待、销毁串起来:

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

int main() {
    // 1. 读取图片
    cv::Mat img = cv::imread("lena.jpg", cv::IMREAD_COLOR);
    if (img.empty()) {
        std::cerr << "图片读失败了!" << std::endl;
        return -1;
    }

    // 2. 显示图片
    cv::imshow("Lena - 彩色", img);

    // 3. 转成灰度图再显示
    cv::Mat gray;
    cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
    cv::imshow("Lena - 灰度", gray);

    // 4. 等待按键
    std::cout << "按任意键关闭窗口..." << std::endl;
    cv::waitKey(0);

    // 5. 清理
    cv::destroyAllWindows();
    return 0;
}

这个例子,你直接复制到你的IDE里跑一下。如果能看到两个窗口,一个彩色一个灰度,那就说明你环境没问题,基础操作也掌握了。

嗯,最后再啰嗦一句:imread、imshow、waitKey这三个函数,是OpenCV图像处理的“三驾马车”。你后面学到的所有图像处理操作,都离不开它们。把这三个函数用熟了,后面的路就好走了。