图像投影技术实现与可视化(基于OpenCV)

在图像处理领域,投影技术是一种重要的分析手段,广泛应用于字符识别、目标检测等场景。通过将图像的像素信息投影到某个方向上,可以简化图像数据,提取关键特征。本文将详细介绍如何使用OpenCV实现图像的水平投影、垂直投影以及对角线投影,并对投影结果进行可视化。
本文软件及opencv版本:vs2022 opencv4.5

1. 图像预处理

在进行投影之前,通常需要对图像进行预处理,以增强投影的效果。常见的预处理步骤包括灰度化和二值化。灰度化是将彩色图像转换为灰度图像,而二值化则是将灰度图像转换为只有黑白两种颜色的图像。二值化后的图像更便于进行投影计算。

Mat preprocessImage(const Mat& src) {
    Mat gray, binary;
    if (src.channels() == 3) {
        cvtColor(src, gray, COLOR_BGR2GRAY);
    } else {
        gray = src.clone();
    }
    adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C,
        THRESH_BINARY, 11, 2);
    return binary;
}

2. 水平投影

水平投影是将图像的像素信息沿着水平方向进行投影,得到一个表示每一行像素密度的向量。在二值图像中,通常计算每一行中白色像素的数量。

Mat getHorizonProjection(const Mat& src) {
    Mat dst(src.rows, 1, CV_32F, Scalar(0));
    for (int i = 0; i < src.rows; i++) {
        float sum = 0;
        for (int j = 0; j < src.cols; j++) {
            sum += int(src.at<uchar>(i, j) / 255);
        }
        dst.at<float>(i, 0) = sum;
    }
    return dst;
}

3. 垂直投影

垂直投影是将图像的像素信息沿着垂直方向进行投影,得到一个表示每一列像素密度的向量。同样,在二值图像中,计算每一列中白色像素的数量。

Mat getVerticalProjection(const Mat& src) {
    Mat dst(1, src.cols, CV_32F, Scalar(0));
    for (int j = 0; j < src.cols; j++) {
        float sum = 0;
        for (int i = 0; i < src.rows; i++) {
            sum += int(src.at<uchar>(i, j) / 255);
        }
        dst.at<float>(0, j) = sum;
    }
    return dst;
}

4. 对角线投影

对角线投影是将图像的像素信息沿着对角线方向进行投影。常见的对角线方向包括45度和135度。通过对角线投影可以提取图像在对角方向上的特征。

Mat getDiagonalProjection(const Mat& src, int direction = 0) {
    int diagonalSize = min(src.rows, src.cols);
    Mat dst;
    switch (direction) {
    case 0: // 45度方向
        dst = Mat(src.rows + src.cols - 1, 1, CV_32F, Scalar(0));
        for (int d = 0; d < src.rows + src.cols - 1; d++) {
            float sum = 0;
            for (int i = max(0, d - src.cols + 1); i <= min(d, src.rows - 1); i++) {
                int j = d - i;
                sum += int(src.at<uchar>(i, j) / 255);
            }
            dst.at<float>(d, 0) = sum;
        }
        break;
    case 1: // 135度方向
        dst = Mat(src.rows + src.cols - 1, 1, CV_32F, Scalar(0));
        for (int d = 0; d < src.rows + src.cols - 1; d++) {
            float sum = 0;
            for (int i = max(0, d - src.cols + 1); i <= min(d, src.rows - 1); i++) {
                int j = src.cols - 1 - (d - i);
                sum += int(src.at<uchar>(i, j));
            }
            dst.at<float>(d, 0) = sum;
        }
        break;
    default:
        dst = Mat(diagonalSize, 1, CV_32F, Scalar(0));
        break;
    }
    return dst;
}

5. 投影结果可视化

为了更直观地展示投影结果,我们可以将投影向量可视化为图像。通过绘制线段,可以清晰地看到每一行或每一列的像素密度。

Mat visualizeProjection(const Mat& projection, int type) {
    Mat vis;
    if (type == 0) { // 水平投影
        normalize(projection, vis, 0, 255, NORM_MINMAX);
        vis.convertTo(vis, CV_8U);
        Mat display(256, projection.rows, CV_8U, Scalar(255));
        for (int i = 0; i < projection.rows; i++) {
            line(display, Point(i, 255), Point(i, 255 - vis.at<uchar>(i, 0)), Scalar(0), 1);
        }
        return display;
    } else if (type == 1) { // 垂直投影
        normalize(projection, vis, 0, 255, NORM_MINMAX);
        vis.convertTo(vis, CV_8U);
        Mat display(256, projection.cols, CV_8U, Scalar(255));
        for (int j = 0; j < projection.cols; j++) {
            line(display, Point(j, 255), Point(j, 255 - vis.at<uchar>(0, j)), Scalar(0), 1);
        }
        return display;
    } else { // 对角线投影
        normalize(projection, vis, 0, 255, NORM_MINMAX);
        vis.convertTo(vis, CV_8U);
        Mat display(256, projection.rows, CV_8U, Scalar(255));
        for (int k = 0; k < projection.rows; k++) {
            line(display, Point(k, 255), Point(k, 255 - vis.at<uchar>(k, 0)), Scalar(0), 1);
        }
        return display;
    }
}

6. 完整代码

以下是完整的代码实现,包括图像的读取、预处理、投影计算以及结果的可视化和保存。

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

using namespace cv;
using namespace std;


// 图像预处理:灰度化+二值化 
Mat preprocessImage(const Mat& src) {
    Mat gray, binary;

    // 如果输入是彩色图像,转为灰度图 
    if (src.channels() == 3) {
        cvtColor(src, gray, COLOR_BGR2GRAY);
    }
    else {
        gray = src.clone();
    }

    // 二值化处理 
   
   adaptiveThreshold(gray, binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C,
        THRESH_BINARY, 11, 2);

   return binary;
}

// 水平投影 
Mat getHorizonProjection(const Mat& src) {
    Mat dst(src.rows, 1, CV_32F, Scalar(0));
    for (int i = 0; i < src.rows; i++) {
        float sum = 0;
        for (int j = 0; j < src.cols; j++) {
            sum += int(src.at<uchar>(i, j)/255);      //目前投影都以白色即255为前景黑色0为背景
            //sum += src.cols-int(src.at<uchar>(i, j) / 255);       //注释上一行代码,取消注释本行则计算黑色零值的投影 
        }
        dst.at<float>(i, 0) = sum;
    }
    return dst;
}

// 垂直投影 
Mat getVerticalProjection(const Mat& src) {
    Mat dst(1, src.cols, CV_32F, Scalar(0));
    for (int j = 0; j < src.cols; j++) {
        float sum = 0;
        for (int i = 0; i < src.rows; i++) {
            sum += int(src.at<uchar>(i, j)/255);            //目前投影都以白色即255为前景黑色0为背景
        //    sum += src.rows-int(src.at<uchar>(i, j) / 255); //注释上一行代码,取消注释本行则计算黑色零值的投影 
        }
        dst.at<float>(0, j) = sum;
    }
    return dst;
}

// 对角线投影(支持多个方向)
Mat getDiagonalProjection(const Mat& src, int direction = 0) {
    // direction: 0-主对角线, 1-副对角线, 2-45度方向, 3-135度方向 
    int diagonalSize = min(src.rows, src.cols);
    Mat dst;

    switch (direction) {

    case 0: // 45度方向(类似主对角线但更宽)
        dst = Mat(src.rows + src.cols - 1, 1, CV_32F, Scalar(0));
        for (int d = 0; d < src.rows + src.cols - 1; d++) {
            float sum = 0;
            int count = 0;
            for (int i = max(0, d - src.cols + 1); i <= min(d, src.rows - 1); i++) {
                int j = d - i;
                sum += int(src.at<uchar>(i, j)/255);
                count++;
            }
            dst.at<float>(d, 0) = sum; // 平均值 
        }
        break;

    case 1: // 135度方向(类似副对角线但更宽)
        dst = Mat(src.rows + src.cols - 1, 1, CV_32F, Scalar(0));
        for (int d = 0; d < src.rows + src.cols - 1; d++) {
            float sum = 0;
            int count = 0;
            for (int i = max(0, d - src.cols + 1); i <= min(d, src.rows - 1); i++) {
                int j = src.cols - 1 - (d - i);
                sum += int(src.at<uchar>(i, j));
                count++;
            }
            dst.at<float>(d, 0) = sum; // 平均值 
        }
        break;

    default:
        dst = Mat(diagonalSize, 1, CV_32F, Scalar(0));
        break;
    }

    return dst;
}

// 将投影结果可视化 
Mat visualizeProjection(const Mat& projection, int type) {
    Mat vis;
    if (type == 0) { // 水平投影 
        normalize(projection, vis, 0, 255, NORM_MINMAX);
        vis.convertTo(vis, CV_8U);
        Mat display(256, projection.rows, CV_8U, Scalar(255));
        for (int i = 0; i < projection.rows; i++) {
            line(display, Point(i, 255), Point(i, 255 - vis.at<uchar>(i, 0)), Scalar(0), 1);
        }
        return display;
    }
    else if (type == 1) { // 垂直投影 
        normalize(projection, vis, 0, 255, NORM_MINMAX);
        vis.convertTo(vis, CV_8U);
        Mat display(256, projection.cols, CV_8U, Scalar(255));
        for (int j = 0; j < projection.cols; j++) {
            line(display, Point(j, 255), Point(j, 255 - vis.at<uchar>(0, j)), Scalar(0), 1);
        }
        return display;
    }
    else { // 对角线投影 
        normalize(projection, vis, 0, 255, NORM_MINMAX);
        vis.convertTo(vis, CV_8U);
        Mat display(256, projection.rows, CV_8U, Scalar(255));
        for (int k = 0; k < projection.rows; k++) {
            line(display, Point(k, 255), Point(k, 255 - vis.at<uchar>(k, 0)), Scalar(0), 1);
        }
        return display;
    }
}

int main() {
    // 读取输入图像 
    Mat src = imread("./test.jpg");
    if (src.empty()) {
        cout << "无法加载图像文件" << endl;
        return -1;
    }

    // 图像预处理 
    Mat binary = preprocessImage(src); // 使用自适应阈值 

    // 计算各种投影 
    Mat horizonProj = getHorizonProjection(binary);
    Mat verticalProj = getVerticalProjection(binary);

    // 计算多种对角线投影 
    Mat diagProj3 = getDiagonalProjection(binary, 0); // 45度方向 
    Mat diagProj4 = getDiagonalProjection(binary, 1); // 135度方向 

    // 可视化投影结果 
    Mat horizonVis = visualizeProjection(horizonProj, 0);
    Mat verticalVis = visualizeProjection(verticalProj, 1);
    Mat diagVis3 = visualizeProjection(diagProj3, 2);
    Mat diagVis4 = visualizeProjection(diagProj4, 2);

    // 保存结果 
    imwrite("./result/binary_image.jpg", binary);
    imwrite("./result/horizon_projection.jpg", horizonVis);
    imwrite("./result/vertical_projection.jpg", verticalVis);
    imwrite("./result/diagonal_projection_45deg.jpg", diagVis3);
    imwrite("./result/diagonal_projection_135deg.jpg", diagVis4);

    // 显示结果 
    imshow("原始图像", src);
    imshow("二值化图像", binary);
    imshow("水平投影", horizonVis);
    imshow("垂直投影", verticalVis);
    imshow("45度方向投影", diagVis3);
    imshow("135度方向投影", diagVis4);

    waitKey(0);

    return 0;
}
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐