🥑C++数理逻辑定义和实现成像捕捉及交互式变形

关键词

C++ | 数学 | 逻辑 | 按位转换 | 隐写术 | 最低有效位编码策略 | 对比增强 | 线性变换 | 非线性 | 直方图 | 累计 | 直方图均衡 | 概率分布 | 灰度 | 形态学 | 膨胀算子 | 腐蚀算子 | 开运算 | 闭运算 | 滤波器 | 克莱默-布鲁克纳滤波器 | 细化算法 | 骨架化 | 空间过滤 | 离散卷积 | 低通 | 高通 | 一阶导数 | 二阶导数 | Nagao滤波 | 拉普拉斯 | 自适应滤波 | 西格玛滤波 | 自适应窗口 | Deriche 轮廓滤波 | 分贝 | 频域滤波 | 傅里叶 | 莫列波纹 | 线性扩散 | 方程 | 算法 | 视频序列 | 高斯窗口 | 角点检测 | 纹理光谱 | Tamura 系数 | 粗糙度 | 局部二值模式 | 图像分割 | 动作分析 | 多光谱 | 成像捕捉 | 网格 | 变换压缩 | 断层 | 立体视觉 | 径向基函数

🏈page指点迷津 | Brief

🎯要点

  1. 🎯图像的数学转换。

  2. 🎯按位转换,使用最低有效位编码策略的隐写术。

  3. 🎯对比增强线性变换数学定义。

  4. 🎯直方图运算:灰度概率分布,累计直方图,直方图均衡。

  5. 🎯数学形态学:🖊膨胀和腐蚀算子逻辑数学定义和C++代码,🖊开运算和闭运算逻辑数学定义及C++代码,🖊克莱默-布鲁克纳滤波器逻辑数学定义及C++代码,🖊交替顺序滤波器逻辑数学定义及C++代码,🖊形态梯度数学逻辑定义及C++代码,🖊细化(骨架化)形态学技术逻辑关系及C++代码,🖊细化算法C++代码。

  6. 🎯空间过滤:离散卷积逻辑数学定义和C++代码,🖊低通滤波器C++方法,🖊高通滤波器的一阶导数滤波C++方法,🖊二阶导数滤波C++拉普拉斯方法,🖊自适应滤波器C++西格玛滤波方法,🖊自适应窗口滤波器C++ Nagao 滤波方法,C++桑原非利斯托尔滤波方法,🖊Deriche 轮廓滤波器逻辑数学定义和C++代码,🖊Deriche 轮廓滤波器应用于二维图像,🖊计算梯度范数,🖊二阶Deriche 轮廓滤波器对图像拉普拉斯计算,🖊快速傅里叶变换C++代码,应用于两幅图傅里叶变换的模和参数混合,🖊图像光谱的可视化C++代码,以分贝为单位,🖊频域滤波:高斯滤波逻辑数学定义和C++代码,🖊莫列图像的处理,数学关系变换和C++代码去除莫列波纹,🖊线性扩散滤波数学方程和C++代码,🖊非线性扩散滤波C++代码二维上实现 Perona 和 Malik 算法,🖊视频序列上的非线性扩散滤波器C++代码。

  7. 🎯特征提取:C++实现高斯窗口,使用 Harris 和 Stephens 算法的实现角点检测,🖊兴趣点的亚像素检测线性系统数学定义和C++实现,🖊霍夫变换:C++对累加器网格进行阈值化,C++直线检测的霍夫变换,🖊圆和椭圆检测:C++计算圆检测的累加器,🖊纹理特征:纹理光谱数学定义及C++实现,🖊Tamura 系数和Tamura对比度C++代码,🖊粗糙度数学定义和C++使用积分图像的局部平均值,🖊C++绝对差异计算,🖊C++Tamura粗糙度计算,🖊纹理的方向性数学定义及C++Tamura的方向计算,🖊局部二值模式和对比度数学定义和C++实现,🖊C++使用局部二值模式串联直方图。

  8. 🎯图像分割,🎯二维和三维动作分析,🎯多光谱成像捕捉,🎯可视化和渲染三维网格对象,🎯通过变换压缩,🎯断层重建,🎯立体视觉图像分析,🎯使用径向基函数交互式变形。

🍇C++灰度处理和滤镜函数使用

我们将使用 C++ 和 OpenCV 来读取图像并显示结果。首先,让我们编写一个简单的 C++ 程序来读取来自相机的流并使用 OpenCV 显示 RGB 和灰度图像。

 #include <iostream>
 #include "opencv2/opencv.hpp"
 ​
 int main() {
     cv::VideoCapture cam(0);
     if (!cam.isOpened()) {
         throw std::runtime_error("Error");
     }
 ​
     cv::namedWindow("Window");
     while (true) {
         cv::Mat frame;
         cam >> frame;
         cv::resize(frame, frame, cv::Size(400, 400));
         cv::imshow("bgr_frame", frame);
         cv::Mat gray_frame;
         cv::cvtColor(frame, gray_frame, CV_BGR2GRAY);
         cv::imshow("gray_frame", gray_frame);
         if (cv::waitKey(30) >= 0) break;
     }
 }
 ​

我们要在类里安排我们的工作,我们将其称为 ImageOperator。

 class ImageOperator{
 public:
     ImageOperator() = default;
     ~ImageOperator() = default;
     
     static void to_gray_m1(const cv::Mat& input, cv::Mat& output);
     static void to_gray_m2(const cv::Mat& input, cv::Mat& output);
     static void to_gray_m3(const cv::Mat& input, cv::Mat& output);
     static void to_gray(const unsigned char* input,
                         const int width,
                         const int height,
                         const int channel,
                         unsigned char* output);
 };
 ​

您可能注意到,我们在第 6 行到第 9 行中声明了四个函数,用于将 RGB 图像转换为灰度图像。

前三个函数以 OpenCV Mat 矩阵作为输入参数。 我们将探索三种不同的方法将图像转换为灰度。 最后一个函数 to_gray 使用原始 C unsigned char 指针。我们将使用加权方法将 RGB 图像转换为灰度图像。执行此操作的等式是:gray_image = ( (0.3 * R) + (0.59 * G) + (0.11 * B) ).

使用迭代器

它也被称为安全方法。它不如其他方法有效,但它使循环像素变得更加容易。

 void ImageOperator::to_gray_m1(const cv::Mat &input, cv::Mat &output) {
   
     unsigned char *data_out = (unsigned char*)(output.data);
     int ind = 0;
     auto end = input.end<cv::Vec3b>();
     cv::MatConstIterator_<cv::Vec3b> it = input.begin<cv::Vec3b>();
     for (; it != end; ++it) {
         const unsigned char &r = (*it)[2];
         const unsigned char &g = (*it)[1];
         const unsigned char &b = (*it)[0];
         data_out[ind] = 0.3*r+0.59*g+0.11*b;
         ind++;
     }
 ​
 }
 ​

我们使用 cv::Vec3b 是因为每个元素由三个通道 bgr(蓝色、绿色和红色)组成,每个通道都是 1 字节,因此我们需要 3 字节。 输出 Mat 仅包含一个通道,并且具有与输入 Mat 相同的大小(行数和列数),因此我们可以访问其原始 unsigned char 指针数据并对其进行修改。

使用原始指针和总大小

可以使用 data 属性访问 cv::Mat 的原始数据指针。图像的总字节数可以通过以下公式计算: img_size_in_byte = number_of_channels * img_width*img_height 因此,如果我们有 4040 bgr 图像,则其总大小为 34040 = 4800,相应的灰度图像大小为 140*40 = 1600。这里,我们假设每个通道需要一个字节。

每三个连续字节应转换为灰度图像中的一个值。

 void ImageOperator::to_gray_m2(const cv::Mat &input, cv::Mat &output) {
     unsigned char *data_in = (unsigned char*)(input.data);
     unsigned char *data_out = (unsigned char*)(output.data);
 ​
     int index = 0;
     int byte_size = input.channels()*input.rows*input.cols;
     while(index!=byte_size){
         data_out[index/input.channels()] = unsigned(0.11*data_in[index]+0.59*data_in[index+1]+0.3*data_in[index+2]);
 ​
         index+=3;
     }
 }
 ​

在上面的代码片段中,我们使用索引来循环 bgr 输入图像中的字节。我们还将每个循环的索引移动 3,直到它等于总大小(以字节为单位)。

在 for 循环中使用原始指针

图像数据以连续字节的形式存储在内存中。在此方法中,您可以使用其行和列坐标但从 1D 字符指针访问像素。input.step 给出一行中的总字节数。如果将其与行索引相乘,就可以获得指向特定行的指针。访问特定行后,可以使用 col 索引来访问特定列。换句话说:要访问 2D 数组中 x,y 位置的一个像素,可以编写 img[x][y]\operatorname{img}[ x ][ y ]。要在一维数组中获得相同的结果,必须使用 img[ystep+x]\operatorname{img}\left[ y ^*\right. step+x]

 void ImageOperator::to_gray_m3(const cv::Mat &input, cv::Mat &output) {
 ​
     unsigned char *data_in = (unsigned char*)(input.data);
     unsigned char *data_out = (unsigned char*)(output.data);
 ​
     int index = 0;
     for (int row = 0; row < input.rows; ++row) {
         for (int col = 0; col < input.cols*input.channels(); col+=input.channels()) {
             data_out[index]= 0.11*data_in[row*input.step+col]+
                              0.59*data_in[row*input.step+col+1]+
                              0.3*data_in[row*input.step+col+2];
             index++;
         }
     }
 }
 ​

前面的代码循环遍历 bgr 图像中的每 3 个字节并计算其比例值。

现在,在了解了前面的方法之后,您会发现将 RGB 图像转换为灰度图像非常简单。实际上,甚至不需要解释,因为正如您所注意到的,我们在方法 2 和 3 中使用了原始指针。

Last updated