C语言inputpicture

99ANYc3cd6
预计阅读时长 33 分钟
位置: 首页 C语言 正文

“输入图片”在编程中通常指读取图片文件,并将其数据加载到内存中,以便进行后续处理(如显示、修改、分析等)。

C语言inputpicture
(图片来源网络,侵删)

在C语言中,由于其本身不包含高级的图形或图像处理库,所以直接操作图片文件是比较复杂的,图片文件(如JPEG, PNG, BMP)有特定的格式(头部信息、像素数据、压缩算法等),我们需要借助第三方库来简化这个过程。

下面我将从最简单的方式更专业的方式,为你介绍几种在C语言中输入图片的方法。


使用最简单的BMP格式(不依赖第三方库)

BMP(Bitmap)是一种未经压缩或简单压缩的位图格式,它的结构相对简单,我们可以手动解析它的文件头和像素数据,是学习C语言操作图像格式的绝佳入门案例。

BMP文件基本结构:

  1. 文件头:存储文件类型、文件大小等信息。
  2. 信息头:存储图像的宽度、高度、颜色深度等关键信息。
  3. 像素数据:实际的图像像素数据,通常按行存储,可能存在每行字节数不是4的倍数而需要填充的情况。

示例代码:读取一个24位(真彩色)的BMP文件,并获取其像素数据

这个例子会:

C语言inputpicture
(图片来源网络,侵删)
  1. 定义BMP文件头和信息头的结构体。
  2. 以二进制模式打开一个BMP文件。
  3. 读取并解析文件头和信息头。
  4. 分配内存来存储像素数据。
  5. 读取像素数据到内存中。
  6. 关闭文件并释放内存。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> // 用于固定宽度整数类型,如 uint8_t, uint32_t
// 1. 定义BMP文件头 (14 bytes)
#pragma pack(push, 1) // 确保结构体按1字节对齐,避免填充
typedef struct {
    uint16_t bfType;      // 文件类型,必须是 'BM'
    uint32_t bfSize;      // 整个文件的大小
    uint16_t bfReserved1; // 保留,必须为0
    uint16_t bfReserved2; // 保留,必须为0
    uint32_t bfOffBits;   // 从文件头到像素数据的偏移量
} BMPFileHeader;
// 2. 定义BMP信息头 (40 bytes)
typedef struct {
    uint32_t biSize;         // 信息头大小,固定为40
    int32_t  biWidth;        // 图像宽度(像素)
    int32_t  biHeight;       // 图像高度(像素),正数表示从下到上,负数表示从上到下
    uint16_t biPlanes;       // 颜色平面数,必须为1
    uint16_t biBitCount;     // 每个像素的位数,如1, 4, 8, 16, 24, 32
    uint32_t biCompression;  // 压缩方式,0表示不压缩
    uint32_t biSizeImage;    // 像素数据的大小(字节)
    int32_t  biXPelsPerMeter; // 水平分辨率
    int32_t  biYPelsPerMeter; // 垂直分辨率
    uint32_t biClrUsed;      // 使用的颜色数
    uint32_t biClrImportant; // 重要的颜色数
} BMPInfoHeader;
#pragma pack(pop)
// 计算每行的填充字节数
int calculate_padding(int width, int bit_count) {
    // 每行字节数 = (width * bit_count) / 8
    // 每行字节数必须是4的倍数,如果不是则需要填充
    int bytes_per_row = (width * bit_count) / 8;
    int padding = (4 - (bytes_per_row % 4)) % 4;
    return padding;
}
int main() {
    const char* filename = "input.bmp"; // 假设你有一个名为input.bmp的文件在同一目录下
    FILE* file = fopen(filename, "rb");
    if (!file) {
        perror("Error opening file");
        return 1;
    }
    // 读取文件头和信息头
    BMPFileHeader file_header;
    BMPInfoHeader info_header;
    fread(&file_header, sizeof(BMPFileHeader), 1, file);
    fread(&info_header, sizeof(BMPInfoHeader), 1, file);
    // 验证文件类型和格式
    if (file_header.bfType != 0x4D42) { // 'BM' in little-endian
        fprintf(stderr, "Not a valid BMP file.\n");
        fclose(file);
        return 1;
    }
    if (info_header.biBitCount != 24) {
        fprintf(stderr, "This example only supports 24-bit BMP files.\n");
        fclose(file);
        return 1;
    }
    printf("Image Width: %d\n", info_header.biWidth);
    printf("Image Height: %d\n", info_header.biHeight);
    printf("Bits per pixel: %d\n", info_header.biBitCount);
    // 计算每行的填充字节数
    int padding = calculate_padding(info_header.biWidth, info_header.biBitCount);
    printf("Padding per row: %d bytes\n", padding);
    // 分配内存存储像素数据
    // BMP是BGR格式,每个像素3字节
    long image_data_size = info_header.biWidth * info_header.biHeight * 3;
    unsigned char* pixel_data = (unsigned char*)malloc(image_data_size);
    if (!pixel_data) {
        perror("Error allocating memory for pixel data");
        fclose(file);
        return 1;
    }
    // 将文件指针移动到像素数据的起始位置
    fseek(file, file_header.bfOffBits, SEEK_SET);
    // 逐行读取像素数据(跳过填充)
    for (int i = 0; i < info_header.biHeight; i++) {
        // fread一次读取一行的像素数据
        size_t bytes_read = fread(pixel_data + (i * info_header.biWidth * 3), 
                                  1, 
                                  info_header.biWidth * 3, 
                                  file);
        // 跳过每行末尾的填充字节
        fseek(file, padding, SEEK_CUR);
    }
    printf("Successfully read pixel data into memory.\n");
    printf("First pixel (B, G, R): %d, %d, %d\n", 
           pixel_data[0], pixel_data[1], pixel_data[2]);
    // --- 在这里你可以对 pixel_data 进行处理 ---
    // 修改像素值、进行图像分析等...
    // 清理资源
    free(pixel_data);
    fclose(file);
    return 0;
}

如何编译和运行:

  1. 准备一张24位色的BMP图片(例如用画图程序另存为input.bmp)。
  2. 将上面的代码保存为read_bmp.c
  3. 使用GCC编译:gcc read_bmp.c -o read_bmp
  4. 运行:./read_bmp

优点

  • 不需要任何外部库,纯C语言实现。
  • 能让你深入了解图像文件的底层结构。

缺点

  • 只能处理BMP格式,对主流的JPEG、PNG等格式无能为力。
  • 代码复杂,需要自己处理文件格式细节。

使用成熟的图像处理库(推荐)

在实际开发中,我们几乎总是使用成熟的第三方库来处理图像,因为它们功能强大、稳定且支持多种格式,最常用的是 libjpeglibpng,以及它们的现代封装库。

C语言inputpicture
(图片来源网络,侵删)

推荐库:Stb Image (Single-File Libraries)

这是一个非常流行的“单文件库”,你只需要把 stb_image.h 头文件包含到你的项目中即可,它极其轻量、易于使用,并且支持JPEG, PNG, BMP, TGA, PSD, GIF等多种格式。

获取库文件https://github.com/nothings/stb 下载 stb_image.h 文件,并放在你的项目目录中。

示例代码:使用Stb Image读取一张PNG或JPG图片

这个例子会:

  1. 包含 stb_image.h
  2. 使用 stbi_load() 函数一键加载图片。
  3. 获取图片的宽度、高度和通道数。
  4. 获取像素数据指针。
  5. 使用后调用 stbi_image_free() 释放内存。
#define STB_IMAGE_IMPLEMENTATION // 这个宏必须在包含头文件之前定义一次,它会展开库的实现代码
#include "stb_image.h"           // 包含单文件库的头文件
#include <stdio.h>
int main() {
    const char* filename = "input.png"; // 可以是 .jpg, .png, .bmp 等格式
    // 3. 加载图像
    // stbi_load 返回一个指向像素数据的 unsigned char 指针
    // 如果加载失败,返回 NULL
    int width, height, channels;
    unsigned char *pixel_data = stbi_load(filename, &width, &height, &channels, 0);
    if (!pixel_data) {
        fprintf(stderr, "Error: Could not load image %s\n", filename);
        return 1;
    }
    printf("Successfully loaded image: %s\n", filename);
    printf("Image Width: %d\n", width);
    printf("Image Height: %d\n", height);
    printf("Number of channels: %d\n", channels);
    printf("Image format: %s\n", 
           channels == 1 ? "Grayscale" : 
           channels == 3 ? "RGB" : 
           channels == 4 ? "RGBA" : "Unknown");
    // pixel_data 是一个一维数组,按行存储像素
    // 对于RGB图像,pixel_data[0]是第一个像素的R值,pixel_data[1]是G,pixel_data[2]是B
    // pixel_data[3]是第二个像素的R值,以此类推
    printf("First pixel (R, G, B): %d, %d, %d\n", 
           pixel_data[0], pixel_data[1], pixel_data[2]);
    // --- 在这里你可以对 pixel_data 进行处理 ---
    // 4. 释放图像数据
    stbi_image_free(pixel_data);
    return 0;
}

如何编译和运行:

  1. 准备一张input.pnginput.jpg图片。
  2. 将上面的代码保存为read_image.c,并将stb_image.h放在同一目录下。
  3. 使用GCC编译:gcc read_image.c -o read_image
  4. 运行:./read_image

优点

  • 极其简单:几行代码就能完成图片加载。
  • 支持多种格式:自动处理JPEG、PNG等复杂格式。
  • 无需链接:因为是单文件实现,编译时不需要额外链接库文件。
  • 跨平台:纯C代码,任何标准C编译器都能用。

缺点

  • 功能相对基础,主要用于读取,对于高级的图像处理(如滤镜、变换),你可能需要另一个库,Stb Image ResizeOpenCV

使用功能强大的计算机视觉库(OpenCV)

如果你的项目需要复杂的图像处理、计算机视觉功能(如人脸识别、物体检测、图像变换等),OpenCV 是不二之选。

安装OpenCV 安装方法因操作系统而异,在Linux上(如Ubuntu)可以使用包管理器: sudo apt-get install libopencv-dev 在Windows和macOS上,通常需要从官网下载源码或预编译包进行安装。

示例代码:使用OpenCV读取图片

OpenCV的C++接口更常用且功能更完整,但它也提供了C语言接口(#include <opencv2/opencv.h>),这里我们展示更现代的C++方式,因为它更简洁。

#include <opencv2/opencv.hpp> // OpenCV C++头文件
#include <iostream>
int main() {
    // cv::imread 是OpenCV中用于读取图像的函数
    // cv::IMREAD_COLOR 表示以彩色模式读取(忽略alpha通道)
    cv::Mat image = cv::imread("input.jpg", cv::IMREAD_COLOR);
    // 检查图像是否成功加载
    if (image.empty()) {
        std::cerr << "Error: Could not read the image." << std::endl;
        return 1;
    }
    // 打印图像信息
    std::cout << "Image Width: " << image.cols << std::endl;
    std::cout << "Image Height: " << image.rows << std::endl;
    std::cout << "Number of channels: " << image.channels() << std::endl;
    // OpenCV中的 cv::Mat 是一个多维数组类,可以直接操作像素
    // image.data 指向像素数据的指针
    std::cout << "First pixel (B, G, R in OpenCV): " 
              << (int)image.data[0] << ", " 
              << (int)image.data[1] << ", " 
              << (int)image.data[2] << std::endl;
    // 显示图像
    cv::imshow("Loaded Image", image);
    cv::waitKey(0); // 等待用户按下任意键
    // OpenCV的cv::Mat会在其生命周期结束后自动释放内存,
    // 所以我们不需要手动释放image.data。
    return 0;
}

如何编译和运行(以Linux为例): 你需要链接OpenCV的库。 g++ your_program.cpp -o your_programpkg-config --cflags --libs opencv4``

优点

  • 功能极其强大:包含了数千种图像处理和计算机视觉算法。
  • 性能优异:底层用C/C++和优化过的汇编编写。
  • 生态系统完善:拥有庞大的社区和海量的教程。

缺点

  • 安装和配置相对复杂
  • 库本身非常庞大,不适合只需要简单读取图片的轻量级项目。

总结与选择建议

方法 优点 缺点 适用场景
手动解析BMP 无需外部库,理解底层原理 仅支持BMP,代码复杂,易出错 学习C语言文件操作和图像格式,或教学演示
Stb Image 极其简单,支持多格式,单文件,无依赖 功能基础,主要用于读取 强烈推荐,绝大多数需要读取图片的C/C++项目。
OpenCV 功能强大,性能卓越,生态系统完善 安装配置复杂,库体积大 复杂的图像处理、计算机视觉、科学研究、专业项目

给你的建议:

  • 如果你是初学者,或者只是想简单地在程序里用一张图片:直接使用 Stb Image,它是最简单、最高效的解决方案。
  • 如果你想深入理解图像文件格式:尝试手动解析 BMP 格式,这是一个非常有价值的练习。
  • 如果你的项目涉及复杂的图像处理和分析:学习并使用 OpenCV,它是行业标准,虽然入门稍难,但回报巨大。
-- 展开阅读全文 --
头像
C语言gettickcount如何获取精确时间?
« 上一篇 01-03
C语言annotation是什么?如何使用?
下一篇 » 01-03

相关文章

取消
微信二维码
支付宝二维码

目录[+]