opencv(C++)操作圖像像素

文章目錄

    • 添加噪點的案例
    • 圖像像素值
      • 1、訪問圖像屬性
      • 2、像素訪問方法 at
        • 灰度圖像
        • 彩色圖像
      • 3、OpenCV 的向量類型
      • 4、 圖像傳遞方式
    • The cv::Mat_ 類
      • 1、作用及優點
      • 2、使用 cv::Mat_ 簡化像素訪問
    • 用指針掃描圖像
      • 背景
      • 算法
      • 案例
      • 原理
        • 1. 圖像數據存儲的基本結構
        • 2、行填充(Padding)與有效寬度
        • 3、計算每行的像素值數量
        • 4、使用指針運算訪問圖像數據
      • 顏色縮減方案
        • 1、方法一:整數除法
        • 2、方法二:取模運算
        • 3、方法三:位運算
      • 參數的輸入與輸出
        • 1、原地處理(In-place Transformation)
        • 2. 提供靈活性的函數設計
        • 3. 靈活函數的實現
      • 高效掃描連續圖像
        • 優點
        • 適用場景
      • 低級指針運算
        • 核心概念
          • 1、圖像數據的起始地址
          • 2、行與列的偏移
          • 3、像素地址計算
        • 優點
        • 缺點
      • 使用迭代器掃描圖像
        • 核心思想
          • 1、迭代器的聲明:
          • 2、迭代器的使用:
          • 3、顏色縮減:
      • 編寫高效的圖像掃描循環
      • 通過鄰域訪問掃描圖像
        • 準備工作
        • 實現方法
        • 銳化濾波
      • 執行簡單圖像算術
        • 圖像加法
        • 圖像減法
        • 乘法和除法
        • 逐通道操作
        • 重載圖像操作
          • 分割圖像通道
        • 重映射圖像
          • 簡單實現

添加噪點的案例

#include "base_function_image.h"
#include <iostream>
#include <random>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"void salt(cv::Mat &image, int n) 
{// 檢查輸入圖像是否為空if (image.empty()) {std::cerr << "Error: Input image is empty!" << std::endl;return;}// C++11 隨機數生成器std::default_random_engine generator(std::random_device{}());std::uniform_int_distribution<int> randomRow(0, image.rows - 1);std::uniform_int_distribution<int> randomCol(0, image.cols - 1);for (int k = 0; k < n; ++k) {// 隨機生成圖像坐標int i = randomCol(generator); // 列索引int j = randomRow(generator); // 行索引// 根據圖像類型設置像素值if (image.type() == CV_8UC1) { // 灰度圖像(單通道)image.at<uchar>(j, i) = 255; // 設置為白色} else if (image.type() == CV_8UC3) { // 彩色圖像(三通道)image.at<cv::Vec3b>(j, i)[0] = 255; // B通道image.at<cv::Vec3b>(j, i)[1] = 255; // G通道image.at<cv::Vec3b>(j, i)[2] = 255; // R通道} else {std::cerr << "Error: Unsupported image type!" << std::endl;return;}}
}int main() 
{// 加載圖像cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) {std::cerr << "Error: Could not load the image!" << std::endl;return -1;}// 顯示原始圖像cv::imshow("Original Image", image);// 添加鹽噪聲int numSaltNoisePoints = 1000; // 噪聲點數量salt(image, numSaltNoisePoints);// 顯示處理后的圖像cv::imshow("Image with Salt Noise", image);// 保存結果cv::imwrite("salt_noise_image.jpg", image);// 等待用戶按鍵后退出cv::waitKey(0);return 0;
}

在這里插入圖片描述

圖像像素值

1、訪問圖像屬性

在 OpenCV 中,cv::Mat 類提供了多種方法來訪問圖像的不同屬性。其中,cols 和 rows 是兩個公共成員變量,用于獲取圖像的列數和行數。

int numCols = image.cols; // 獲取圖像的列數
int numRows = image.rows; // 獲取圖像的行數// 如果圖像大小為 640x480,則 image.cols 返回 640,image.rows 返回 480。

2、像素訪問方法 at

為了訪問圖像中的像素,cv::Mat 提供了模板方法 at(int y, int x),其中:

  • x 是列索引(水平方向)。
  • y 是行索引(垂直方向)。
  • T 是像素的數據類型。

由于 cv::Mat 可以存儲任意類型的元素,因此程序員需要顯式指定返回類型。例如:

灰度圖像

對于單通道灰度圖像,每個像素是一個 8 位無符號整數(uchar),可以這樣訪問:

image.at<uchar>(j, i) = 255; // 將第 j 行、第 i 列的像素值設置為 255(白色)
彩色圖像

對于三通道彩色圖像,每個像素是一個包含三個 8 位無符號整數的向量(藍色、綠色和紅色)。OpenCV 定義了一個專門的類型 cv::Vec3b 來表示這種短向量。

image.at<cv::Vec3b>(j, i)[0] = 255; // 設置藍色通道值為 255
image.at<cv::Vec3b>(j, i)[1] = 255; // 設置綠色通道值為 255
image.at<cv::Vec3b>(j, i)[2] = 255; // 設置紅色通道值為 255

或者,可以直接使用 cv::Vec3b 向量賦值:

image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255); // 設置像素為白色

3、OpenCV 的向量類型

OpenCV 提供了一系列向量類型,用于表示不同長度和數據類型的向量。這些類型基于模板類 cv::Vec<T, N>,其中:

  • T 是元素類型。
  • N 是向量的長度。

常見類型
2 元素向量:cv::Vec2b(2 個字節)、cv::Vec2f(2 個浮點數)、cv::Vec2i(2 個整數)。
3 元素向量:cv::Vec3b(3 個字節,常用于 RGB 顏色)。
4 元素向量:cv::Vec4b(4 個字節,常用于 RGBA 顏色)。

命名規則
最后一個字母表示數據類型:
b:8 位無符號整數(unsigned char)。
f:單精度浮點數(float)。
s:短整型(short)。
i:整型(int)。
d:雙精度浮點數(double)。

4、 圖像傳遞方式

在 OpenCV 中,即使通過值傳遞圖像對象,它們仍然共享相同的圖像數據。這是因為 cv::Mat 內部使用引用計數機制管理數據。

以下函數通過值傳遞圖像參數,并修改其內容:

void modifyImage(cv::Mat image) {for(int i = 300; i < 600; ++i){for(int j = 300; j < 600; ++j){image.at<uchar>(i, j) = 255; // 修改像素值}}}int main() {cv::Mat img = cv::imread(IMAGE_LOGO);modifyImage(img); // 調用函數cv::imshow("Modified Image", img); // 顯示修改后的圖像cv::waitKey(0);return 0;
}

在這里插入圖片描述

盡管 modifyImage 函數的參數是通過值傳遞的,但由于 cv::Mat 的內部機制,原始圖像的內容也會被修改。

The cv::Mat_ 類

1、作用及優點

在 OpenCV 中,cv::Mat 是一個通用的矩陣類,可以存儲任意類型的元素。然而,使用 cv::Mat 的 at 方法訪問像素時,需要顯式指定模板參數(如 uchar 或 cv::Vec3b),這有時會顯得繁瑣。

為了簡化操作,OpenCV 提供了一個模板子類 cv::Mat_,它繼承自 cv::Mat。通過 cv::Mat_,可以在創建變量時指定矩陣元素的類型,從而避免每次調用 at 方法時重復指定類型。

優點

  • 減少冗余:在頻繁訪問像素時,cv::Mat_ 可以避免每次都指定模板參數。
  • 提高可讀性:使用 operator() 的代碼更短、更直觀。
  • 兼容性:cv::Mat_ 是 cv::Mat 的子類,兩者可以無縫轉換。例如,您可以將 cv::Mat 對象直接賦值給 cv::Mat_ 對象,反之亦然。

2、使用 cv::Mat_ 簡化像素訪問

cv::Mat_ 提供了一個額外的操作符 operator(),可以直接訪問矩陣元素。與 cv::Mat 的 at 方法相比,operator() 更加簡潔,因為類型在創建 cv::Mat_ 對象時已經確定。

#include <opencv2/opencv.hpp>
#include <iostream>int main() {// 加載圖像cv::Mat image = cv::imread("input.jpg", cv::IMREAD_GRAYSCALE); // 灰度圖像if (image.empty()) {std::cerr << "Error: Could not load the image!" << std::endl;return -1;}// 轉換為 cv::Mat_<uchar> 類型cv::Mat_<uchar> img(image);// 使用 operator() 訪問像素img(50, 100) = 0; // 將第 50 行、第 100 列的像素值設置為 0(黑色)// 顯示修改后的圖像cv::imshow("Modified Image", img);cv::waitKey(0);return 0;
}

用指針掃描圖像

由于像素數量龐大,需要高效地實現掃描。

背景

彩色圖像由 3 通道像素組成(紅、綠、藍),每個通道是一個 8 位無符號整數(0-255)。因此,總顏色數為 256 × 256 × 256 種顏色。
為了簡化分析,有時需要減少圖像中的顏色數量。一種簡單的方法是將 RGB 顏色空間劃分為等大小的立方體。例如,如果每個維度的顏色數量減少為原來的 1/8,則總顏色數將變為 32 × 32 × 32 = 32768 種顏色。

算法

設 N 為顏色縮減因子:
1、對每個像素的每個通道值進行整數除法:value / N。
2、再乘以 N:(value / N) * N,得到小于或等于原值的最大 N 的倍數。
3、加上 N/2,使結果位于區間的中心位置:(value / N) * N + N/2。
重復上述步驟對每個通道(R、G、B)進行處理后,顏色總數將減少為 (256/N) × (256/N) × (256/N) 種。

案例

定義了一個用于顏色縮減的函數 colorReduce

/*
cv::Mat image:輸入圖像(彩色或灰度圖像)。
int div = 64:每個通道的顏色縮減因子,默認值為 64。
*/
void colorReduce(cv::Mat image, int div = 64) {int nl = image.rows; // 圖像的行數int nc = image.cols * image.channels(); // 每行的總元素數(列數 × 通道數)for (int j = 0; j < nl; j++) { // 遍歷每一行uchar* data = image.ptr<uchar>(j); // 獲取第 j 行的指針for (int i = 0; i < nc; i++) { // 遍歷當前行的所有像素// 對每個像素進行處理data[i] = data[i] / div * div + div / 2;}}
}
int main() {// 加載圖像cv::Mat image = cv::imread("boldt.jpg");if (image.empty()) {std::cerr << "Error: Could not load the image!" << std::endl;return -1;}// 處理圖像colorReduce(image, 64);// 顯示結果cv::namedWindow("Reduced Color Image", cv::WINDOW_AUTOSIZE);cv::imshow("Reduced Color Image", image);// 等待用戶按鍵后退出cv::waitKey(0);return 0;
}

在這里插入圖片描述

原理

1. 圖像數據存儲的基本結構

在 OpenCV 中,彩色圖像的數據存儲遵循以下規則:
1、每個像素由 3 個字節組成,分別對應藍色(B)、綠色(G)和紅色(R)通道。
2、圖像數據按行優先存儲:

  • 第一行的第一個像素對應圖像左上角,其數據是 3 個字節(BGR 值)。
  • 第二個像素是第一行的第二個像素,依此類推。
    3、一個寬度為 W、高度為 H 的彩色圖像需要的內存大小為:W × H × 3 字節。
2、行填充(Padding)與有效寬度

為了提高效率,OpenCV 有時會在每一行末尾填充額外的字節。這些填充字節的作用包括:

  • 對齊內存:使每行的長度對齊到特定的邊界(如 8 字節對齊),以更好地利用硬件特性。
  • 性能優化:某些圖像處理算法在對齊的內存上運行得更快。
    盡管有填充字節,這些額外的數據并不會顯示或保存,實際圖像的寬度仍然保持不變。

相關屬性

  • 真實寬度:image.cols 返回圖像的真實列數。
  • 有效寬度:image.step 返回每行的實際字節數(包括填充字節)。
    如果沒有填充,image.step 等于 image.cols × image.elemSize()。
  • 像素元素大小:image.elemSize() 返回單個像素占用的字節數。
    例如,對于 3 通道的短整型矩陣(CV_16SC3),每個像素占用 6 字節(3 × 2 字節)。
  • 總像素數:image.total() 返回圖像中像素的總數(即矩陣元素數)。
3、計算每行的像素值數量

每行的像素值數量可以通過以下公式計算:

// image.cols 是圖像的列數。
// image.channels() 是每個像素的通道數(灰度圖像為 1,彩色圖像為 3)。
int nc = image.cols * image.channels();
4、使用指針運算訪問圖像數據

以下是一個典型的雙層循環實現,用于遍歷圖像的所有像素:

for (int j = 0; j < image.rows; j++) { // 遍歷每一行uchar* data = image.ptr<uchar>(j); // 獲取第 j 行的指針for (int i = 0; i < nc; i++) {     // 遍歷當前行的所有像素data[i] = data[i] / div * div + div / 2; // 處理每個像素}
}

如果希望進一步簡化指針操作,可以在處理過程中直接移動指針。例如:

for (int j = 0; j < image.rows; j++) {uchar* data = image.ptr<uchar>(j);for (int i = 0; i < nc; i++) {*data++ = *data / div * div + div / 2; // 使用指針運算}
}
  • *data++ 表示先訪問 data 指向的值,然后將指針向前移動一個字節。
  • 這種方式避免了顯式的索引操作,但需要注意指針的邊界。

顏色縮減方案

1、方法一:整數除法

通過整數除法將像素值映射到最近的區間中心位置:

  • data[i] / div:將像素值整除 div,得到最接近的倍數。
  • (data[i] / div) * div:恢復到該倍數。
    • div / 2:偏移到區間的中心位置。
// 假設 div = 64,像素值范圍為 [0, 255]
// 將像素值分組為若干區間(如 [0, 63], [64, 127], [128, 191], [192, 255])
// 每個區間內的像素值會被映射到該區間的中心位置(如 [0, 63] 映射到 32)
data[i] = (data[i] / div) * div + div / 2;
2、方法二:取模運算

通過取模運算找到最接近的倍數,并調整到區間的中心位置:

  • data[i] % div:計算當前像素值相對于 div 的余數。
  • data[i] - data[i] % div:得到小于或等于當前像素值的最大倍數。
    • div / 2:偏移到區間的中心位置。
/* 取模運算可以快速找到像素值所屬的區間
例如,當 div = 64 時,像素值 100 的處理過程如下:100 % 64 = 36,計算余數。100 - 36 = 64,得到最接近的倍數。64 + 32 = 96,偏移到區間的中心位置。
*/ 
data[i] = data[i] - data[i] % div + div / 2;
3、方法三:位運算

如果 div 是 2 的冪(即 div = pow(2, n)),可以使用位運算高效地完成顏色縮減:

  • mask = 0xFF << n:生成一個掩碼,用于屏蔽最低的 n 位。
  • *data &= mask:通過按位與操作保留高階位,丟棄低階位。
  • *data += div >> 1:加上 div / 2,偏移到區間的中心位置。
/*
假設 div = 16,則 n = 4(因為 16 = 2^4)。
掩碼 mask = 0xFF << 4 = 0xF0(十六進制表示為 11110000)。
對于像素值 100 的處理過程如下:100 & 0xF0 = 96,屏蔽低 4 位。96 + 8 = 104,偏移到區間的中心位置。
*/
uchar mask = 0xFF << n; // e.g., for div=16, mask=0xF0
*data &= mask;          // 屏蔽低 n 位
*data++ += div >> 1;    // 加上 div/2
  • 效率高:位運算是硬件級的操作,比整數除法和取模運算更快。
  • 適用場景:當 div 是 2 的冪時,位運算是最佳選擇。
方法操作優點缺點
整數除法(data[i] / div) * div + div / 2簡單直觀,適用于任意 div運算速度較慢
取模運算data[i] - data[i] % div + div / 2計算邏輯清晰速度略優于整數除法,但仍較慢
位運算*data &= mask; *data++ += div >> 1極其高效,適合 div 為 2 的冪不適用于非 2 的冪的 div
  • 實時處理:位運算因其高效性,特別適合需要高性能的應用場景(如視頻處理)。
  • 通用性:整數除法和取模運算適用于任意縮減因子,靈活性更高。
  • 內存優化:位運算減少了不必要的計算開銷,適合嵌入式設備或資源受限的環境。

參數的輸入與輸出

1、原地處理(In-place Transformation)

在顏色縮減的例子中,我們直接對輸入圖像進行修改,這被稱為原地處理。
然而,在某些應用場景中,用戶可能希望保留原始圖像不變。此時,用戶需要在調用函數前手動復制一份圖像。例如:

// 讀取圖像
cv::Mat image = cv::imread("boldt.jpg");// 克隆圖像
cv::Mat imageClone = image.clone();// 對克隆圖像進行處理,保持原始圖像不變
colorReduce(imageClone);// 顯示處理后的圖像
cv::namedWindow("Image Result");
cv::imshow("Image Result", imageClone);

通過調用 clone() 方法,可以輕松創建一個圖像的深拷貝(Deep Copy),從而避免修改原始圖像

2. 提供靈活性的函數設計

為了避免用戶手動復制圖像,我們可以設計一個更靈活的函數,允許用戶選擇是否進行原地處理。該函數如下:

void colorReduce(const cv::Mat &image, // 輸入圖像cv::Mat &result,      // 輸出圖像int div = 64);        // 顏色縮減因子  默認值為 64
3. 靈活函數的實現

OpenCV 提供了一個便捷的方法 create,用于確保輸出矩陣具有與輸入矩陣相同的大小和類型。如果輸出矩陣已經滿足要求,則不會重新分配內存。

void colorReduce(const cv::Mat &image, cv::Mat &result, int div = 64) {// 確保輸出圖像具有正確的大小和類型result.create(image.rows, image.cols, image.type());int nl = image.rows; // 圖像的行數int nc = image.cols * image.channels(); // 每行的總元素數for (int j = 0; j < nl; j++) { // 遍歷每一行const uchar* data_in = image.ptr<uchar>(j); // 獲取輸入圖像第 j 行的指針uchar* data_out = result.ptr<uchar>(j);     // 獲取輸出圖像第 j 行的指針for (int i = 0; i < nc; i++) { // 遍歷每個像素// 顏色縮減處理data_out[i] = data_in[i] / div * div + div / 2;}}
}

高效掃描連續圖像

在 OpenCV 中,如果圖像沒有填充額外的字節(即每行末尾沒有多余像素),它實際上可以被視為一個一維數組。這種特性可以通過 isContinuous 方法檢測,或者通過檢查 image.step == image.cols * image.elemSize() 來驗證。

void colorReduce(cv::Mat image, int div = 64) {int nl = image.rows; // 行數int nc = image.cols * image.channels(); // 每行總元素數// 檢查圖像是否連續// 如果圖像連續,則將其視為一個長的一維數組,減少外層循環次數。// if (image.isContinuous()) {nc = nc * nl; // 總像素數nl = 1;       // 將圖像視為一維數組// image.reshape(1, 1); // 調整為單行矩陣 (另一方案)}// 計算掩碼和 div/2// 使用掩碼 mask 和右移操作快速完成顏色縮減int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0) + 0.5);uchar mask = 0xFF << n; // 掩碼uchar div2 = div >> 1;  // div/2// 掃描圖像for (int j = 0; j < nl; j++) {uchar* data = image.ptr<uchar>(j); // 獲取第 j 行指針for (int i = 0; i < nc; i++) {*data &= mask;       // 屏蔽低 n 位*data++ += div2;     // 偏移到區間中心}}
}
優點
  • 提高掃描效率:避免不必要的外層循環。
  • 靈活性強:支持連續性和非連續性圖像
適用場景
  • 大規模圖像處理任務。
  • 需要高效內存訪問的應用場景。

低級指針運算

在 OpenCV 的 cv::Mat 類中,圖像數據存儲在一個連續的內存塊中,數據類型通常為 unsigned char。通過直接操作指針,可以高效地訪問和處理圖像數據。

核心概念
1、圖像數據的起始地址
  • 使用 image.data 獲取圖像數據塊的起始地址。
  • image.data 返回一個指向圖像第一個像素的 unsigned char* 指針。
2、行與列的偏移
  • 圖像的每一行可能包含填充字節,因此每行的實際字節數由 image.step 表示。
  • 列的偏移量由每個像素的大小(image.elemSize())決定
3、像素地址計算

任意像素 (j, i) 的地址可以通過以下公式計算

/*
j 是行號。
i 是列號。
image.step 是每行的總字節數(包括填充字節)。
image.elemSize() 是每個像素的字節大小
*/
data = image.data + j * image.step + i * image.elemSize();
void colorReduce(cv::Mat image, int div = 64) {uchar* data = image.data; // 獲取圖像數據的起始地址for (int j = 0; j < image.rows; j++) { 	// 遍歷每一行uchar* row = image.ptr<uchar>(j); // 獲取第 j 行的指針for (int i = 0; i < image.cols * image.channels(); i++) { // 遍歷每個像素row[i] = row[i] / div * div + div / 2; // 處理像素}}
}
優點

低級指針運算提供了對圖像數據的完全控制,適合性能要求極高的場景。

缺點
  • 容易出錯,尤其是在處理多通道圖像或填充字節時。
  • 可讀性差,代碼維護困難。

使用迭代器掃描圖像

cv::Mat 提供了迭代器類(cv::MatIterator_),可以方便地遍歷圖像的每個像素。迭代器隱藏了底層實現細節,使代碼更簡潔、安全。

核心思想
1、迭代器的聲明:
  • 使用 cv::Mat_cv::Vec3b::iterator 聲明迭代器。
  • cv::Vec3b 表示彩色圖像的每個像素(包含 BGR 三個通道)。
2、迭代器的使用:
  • 使用 image.begincv::Vec3b() 和 image.endcv::Vec3b() 獲取起始和結束迭代器。
  • 遍歷圖像時,通過解引用操作符 *it 訪問當前像素。
3、顏色縮減:
  • 對每個像素的 BGR 通道值進行位運算和偏移操作。
void colorReduce(cv::Mat image, int div = 64) 
{// 確保 div 是 2 的冪int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0) + 0.5);uchar mask = 0xFF << n; // 掩碼uchar div2 = div >> 1;  // div/2// 獲取迭代器// cv::Vec3b 表示每個像素的 BGR 通道值cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();// 遍歷所有像素for (; it != itend; ++it) {// 使用 (*it)[i] 訪問第 i 個通道(B=0, G=1, R=2)(*it)[0] &= mask; (*it)[0] += div2; // 藍色通道(*it)[1] &= mask; (*it)[1] += div2; // 綠色通道(*it)[2] &= mask; (*it)[2] += div2; // 紅色通道}
}

編寫高效的圖像掃描循環

OpenCV 提供了一個方便的函數 cv::getTickCount()。該函數返回自計算機啟動以來的時鐘周期數。通過在代碼執行前后分別獲取時鐘周期數,可以計算出代碼的執行時間。
要將執行時間轉換為秒,可以使用另一個方法 cv::getTickFrequency(),它返回每秒的時鐘周期數(假設 CPU 頻率固定,盡管現代處理器不一定如此)。

const int64 start = cv::getTickCount(); // 獲取起始時鐘周期
colorReduce(image);                     // 調用函數
// 計算執行時間(秒)
double duration = (cv::getTickCount() - start) / cv::getTickFrequency();

通過鄰域訪問掃描圖像

在圖像處理中,經常需要根據像素的鄰域值計算每個像素的新值。當鄰域包含前一行和后一行的像素時,就需要同時掃描圖像的多行。本節將展示如何實現這一操作。

準備工作

圖像銳化的原理是:從圖像中減去拉普拉斯算子的結果,可以增強圖像邊緣,使圖像更清晰。
銳化后的像素值計算公式如下:

// left 是當前像素左側的像素
// up 是上一行對應的像素
sharpened_pixel = 5 * current - left - right - up - down;
實現方法

由于需要訪問鄰域像素,無法在原圖上直接進行處理,必須提供一個輸出圖像。
使用三個指針分別指向當前行、上一行和下一行。此外,由于每個像素的計算需要鄰域信息,無法處理圖像的第一行、最后一行以及第一列和最后一列的像素。循環代碼如下:

void sharpen(const cv::Mat &image, cv::Mat &result) 
{// 如果需要,分配輸出圖像result.create(image.size(), image.type());int nchannels = image.channels(); // 獲取通道數// 遍歷所有行(除第一行和最后一行)for (int j = 1; j < image.rows - 1; j++) {const uchar* previous = image.ptr<const uchar>(j - 1); // 上一行const uchar* current = image.ptr<const uchar>(j);      // 當前行const uchar* next = image.ptr<const uchar>(j + 1);     // 下一行uchar* output = result.ptr<uchar>(j);                  // 輸出行// 遍歷所有列(除第一列和最后一列)for (int i = nchannels; i < (image.cols - 1) * nchannels; i++) {// 應用銳化算子*output++ = cv::saturate_cast<uchar>(5 * current[i] - current[i - nchannels] -current[i + nchannels] - previous[i] - next[i]);}}// 將未處理的像素設置為 0// 無法處理第一行、最后一行、第一列和最后一列的像素,因此將這些像素設置為 0result.row(0).setTo(cv::Scalar(0));               // 第一行result.row(result.rows - 1).setTo(cv::Scalar(0)); // 最后一行result.col(0).setTo(cv::Scalar(0));               // 第一列result.col(result.cols - 1).setTo(cv::Scalar(0)); // 最后一列
}
銳化濾波
0  -1  0
-1  5 -1
0  -1  0

為了滿足銳化濾波器的要求,當前像素的四個水平和垂直鄰居被乘以-1,而當前像素本身則乘以5。
將核應用于圖像不僅是方便的表示方法,它是信號處理中卷積概念的基礎。
OpenCV定義了一個執行此任務的特殊函數:cv::filter2D 函數。只需定義一個核(以矩陣形式),然后用圖像和核調用該函數,它返回濾波后的圖像。利用這個函數,重新定義我們的銳化函數如下:

void sharpen2D(const cv::Mat &image, cv::Mat &result) {// 構造核(所有元素初始化為0)cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));// 賦值給核kernel.at<float>(1, 1) = 5.0;kernel.at<float>(0, 1) = -1.0;kernel.at<float>(2, 1) = -1.0;kernel.at<float>(1, 0) = -1.0;kernel.at<float>(1, 2) = -1.0;// 應用濾波cv::filter2D(image, result, image.depth(), kernel);
}

此實現產生的結果與之前的實現完全相同(且效率相同)。如果輸入的是彩色圖像,則相同的核會被應用到所有三個通道。 當使用較大的核時,使用
filter2D 函數特別有利,因為它在這種情況下會使用更高效的算法。

執行簡單圖像算術

由于圖像是規則的矩陣,因此可以對它們進行加法、減法、乘法或除法運算。

圖像加法

可以通過cv::add函數實現,也可以直接通過矩陣操作如image1 + image2來完成。
當像素值相加后超過255(對于8位無符號圖像),需要使用飽和處理,即超過255的值會被截斷為255。

cv::Mat result;
cv::add(image1, image2, result); // 使用add函數
// 或者
result = image1 + image2; // 直接相加

指定權重作為標量乘數參與運算

// c[i] = k1 * a[i] + k2 * b[i] + k3;
cv::addWeighted(imageA, k1, imageB, k2, k3, resultC);

指定一個掩碼(mask)

// if (mask[i]) c[i] = a[i] + b[i];
cv::add(imageA, imageB, resultC, mask);

如果應用了掩碼,則操作僅對掩碼值非零的像素執行(掩碼必須是單通道的)。可以查看 cv::subtract、cv::absdiff、cv::multiply 和 cv::divide 等函數的不同形式。

OpenCV還提供了按位操作符(對像素的二進制表示逐位操作):cv::bitwise_and、cv::bitwise_or、cv::bitwise_xor和 cv::bitwise_not。cv::min 和 cv::max 操作也非常有用,它們分別計算元素級別的最小值和最大值。

在所有情況下,都會使用 cv::saturate_cast 函數,以確保結果保持在定義的像素值范圍內(即避免溢出或下溢)。

圖像必須具有相同的大小和類型(如果輸出圖像的大小與輸入不匹配,則會重新分配)。由于操作是逐元素進行的,因此可以將其中一個輸入圖像用作輸出。

還有一些接受單張圖像作為輸入的操作符可用,例如:

  • cv::sqrt(平方根)
  • cv::pow(冪運算)
  • cv::abs(絕對值)
  • cv::cuberoot(立方根)
  • cv::exp(指數運算)
  • cv::log(對數運算)
    在這里插入圖片描述在這里插入圖片描述
    在這里插入圖片描述
圖像減法

可以通過cv::subtract函數或直接減法操作完成。這有助于檢測圖像之間的差異。

cv::Mat result;
cv::subtract(image1, image2, result); // 使用subtract函數
// 或者
result = image1 - image2; // 直接相減
乘法和除法

圖像乘法和除法也能以類似的方式完成,分別使用cv::multiply和cv::divide函數,或者直接使用*和/操作符。

cv::Mat result;
cv::multiply(image1, image2, result); // 使用multiply函數
// 或者
result = image1 * image2; // 直接相乘cv::divide(image1, image2, result); // 使用divide函數
// 或者
result = image1 / image2; // 直接相除

在圖像融合時可能需要用到加法操作;在比較兩個相似圖像的不同之處時,則可能用到減法操作。同時,考慮到數值溢出或下溢的問題,合理利用OpenCV提供的函數(如cv::addWeighted用于帶權重的加法)可以幫助更有效地處理這些問題

逐通道操作
std::vector<cv::Mat> channels;
cv::split(image, channels); // 分離通道
channels[0] = channels[0] * 2.0; // 對第一個通道進行操作
cv::merge(channels, image); // 合并通道回原圖像
重載圖像操作

大多數算術函數都有對應的運算符重載。意味著可直接使用C++的運算符來代替調用特定的OpenCV函數,使代碼更加緊湊和易讀。例如,cv::addWeighted函數可以這樣寫:

result = 0.7 * image1 + 0.9 * image2;

許多C++運算符都被重載了,包括按位運算符&, |, ^, 和 ~; 最小值、最大值和絕對值函數;以及比較運算符<, <=, ==, !=, >, 和 >=(返回8位二進制圖像)。你還可以找到矩陣乘法m1 * m2(其中m1和m2都是cv::Mat實例),矩陣求逆m1.inv(),轉置m1.t(),行列式m1.determinant(),向量范數v1.norm(),叉積v1.cross(v2),點積v1.dot(v2)等。當適用時,相應的復合賦值運算符也被定義了(如+=)。

image = (image & cv::Scalar(mask, mask, mask)) + cv::Scalar(div / 2, div / 2, div / 2);

使用cv::Scalar是因為我們處理的是彩色圖像。利用這些圖像運算符可以使代碼變得非常簡單,極大地提高了編程效率,因此在多數情況下都應考慮使用它們。

分割圖像通道

有時可能希望獨立地處理圖像的不同通道。
例如,可能只想對圖像的一個通道執行某些操作。雖然可以在掃描圖像像素的循環中完成這一任務,但也可以使用cv::split函數將一個彩色圖像的三個通道復制到三個獨立的cv::Mat實例中。
假設想要僅向藍色通道添加另一張圖像,可以按照以下步驟操作:

// 創建包含3個圖像的vector
std::vector<cv::Mat> planes;// 將一個3通道圖像拆分為3個單通道圖像
cv::split(image1, planes);// 向藍色通道添加另一張圖像
planes[0] += image2;// 將3個單通道圖像合并為一個3通道圖像
// cv::merge函數執行相反的操作,即從三個單通道圖像創建一個彩色圖像
cv::merge(planes, result);
重映射圖像

通過移動圖像中的像素來改變其外觀。
這個過程中像素的值不會改變,而是每個像素的位置被重新映射到一個新的位置。這種方法可用于創建圖像的特殊效果或糾正由鏡頭引起的圖像失真。

簡單實現

為了使用OpenCV的remap函數,首先需要定義重映射過程中要使用的映射圖,然后將此映射應用于輸入圖像。
顯然,定義映射的方式決定了最終產生的效果。定義了一個變換函數,該函數將在圖像上創建波動效果:

// 通過創建波浪效果進行圖像重映射
void wave(const cv::Mat &image, cv::Mat &result) {// 映射函數cv::Mat srcX(image.rows, image.cols, CV_32F); // x映射cv::Mat srcY(image.rows, image.cols, CV_32F); // y映射// 創建映射for (int i = 0; i < image.rows; i++) {for (int j = 0; j < image.cols; j++) {// 像素(i,j)的新位置srcX.at<float>(i, j) = j; // 保持在同一列// 原本在第i行的像素現在跟隨正弦波移動srcY.at<float>(i, j) = i + 5 * sin(j / 10.0);}}// 應用映射cv::remap(image,   // 源圖像result,  // 目標圖像srcX,    // x方向映射srcY,    // y方向映射cv::INTER_LINEAR // 插值方法);
}

原始位于(i, j)的像素點,在重映射后,其x坐標保持不變(即仍然在原來的列),而y坐標則根據一個正弦函數變化,這樣就會產生一種波動的效果。
通過調整正弦函數的參數,可以控制波動的幅度和頻率。
在這里插入圖片描述

cv::remap函數接受源圖像、目標圖像以及兩個映射矩陣(分別對應于x和y方向上的映射)作為輸入,并允許指定插值方法以確定如何計算新位置處的像素值。在例子中,使用了線性插值(cv::INTER_LINEAR)來平滑過渡像素值的變化。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/901167.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/901167.shtml
英文地址,請注明出處:http://en.pswp.cn/news/901167.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Python實現貪吃蛇一

貪吃蛇是一款經典的小游戲&#xff0c;最近嘗試用Python實現它。先做一個基礎版本實現以下目標&#xff1a; 1、做一個按鈕&#xff0c;控制游戲開始 2、按Q鍵退出游戲 3、右上角顯示一個記分牌 4、隨機生成一個食物&#xff0c;蛇吃到食物后長度加一&#xff0c;得10分 5、蛇碰…

《AI大模型應知應會100篇》第13篇:大模型評測標準:如何判斷一個模型的優劣

第13篇&#xff1a;大模型評測標準&#xff1a;如何判斷一個模型的優劣 摘要 近年來&#xff0c;大語言模型&#xff08;LLMs&#xff09;在自然語言處理、代碼生成、多模態任務等領域取得了顯著進展。然而&#xff0c;隨著模型數量和規模的增長&#xff0c;如何科學評估這些模…

工會考試重點內容有哪些:核心考點與備考指南

工會考試重點內容總結&#xff1a;核心考點與備考指南 工會考試主要考察考生對工會法律法規、職能職責、實務操作等內容的掌握程度&#xff0c;適用于企事業單位工會干部、社會化工會工作者等崗位的選拔。本文梳理工會考試的核心考點&#xff0c;幫助考生高效備考。 一、工會…

Verilog學習-1.模塊的結構

module aoi(a,b,c,d,f);/*模塊名為aoi&#xff0c;端口列表a、b、c、d、f*/ input a,b,c,d;/*模塊的輸入端口為a,b,c,d*/ output f;;/*模塊的輸出端口為f*/ wire a,b,c,d,f;/*定義信號的數據類型*/ assign f~((a&b)|(~(c&d)));/*邏輯功能描述*/ endmoduleveirlog hdl 程…

MySQL數據庫備份與恢復詳解

在數據庫管理中&#xff0c;數據的備份與恢復是至關重要的一環。對于MySQL數據庫&#xff0c;定期備份不僅能防止數據丟失&#xff0c;還能在發生故障時快速恢復數據庫。本文將詳細介紹MySQL數據庫的備份與恢復方法&#xff0c;覆蓋所有常用備份和恢復方式&#xff0c;幫助大家…

FFMPEG和opencv的編譯

首先 sudo apt-get update -qq && sudo apt-get -y install autoconf automake build-essential cmake git-core libass-dev libfreetype6-dev libgnutls28-dev libmp3lame-dev libsdl2-dev libtool libva-dev libvdpau-dev libvorbis-de…

華為機試—最大最小路

題目 對于給定的無向無根樹&#xff0c;第 i 個節點上有一個權值 wi? 。我們定義一條簡單路徑是好的&#xff0c;當且僅當&#xff1a;路徑上的點的點權最小值小于等于 a &#xff0c;路徑上的點的點權最大值大于等于 b 。 保證給定的 a<b&#xff0c;你需要計算有多少條簡…

spring cloud微服務開發中聲明式服務調用詳解及主流框架/解決方案對比

聲明式服務調用詳解 1. 核心概念 定義&#xff1a;通過配置或注解聲明服務調用邏輯&#xff0c;而非手動編寫客戶端代碼&#xff0c;提升開發效率與可維護性。核心特性&#xff1a; 解耦&#xff1a;調用邏輯與業務代碼分離內置容錯&#xff1a;熔斷、超時、重試等動態發現&am…

基于springboot+vue的秦皇島旅游景點管理系統

開發語言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服務器&#xff1a;tomcat7數據庫&#xff1a;mysql 5.7數據庫工具&#xff1a;Navicat11開發軟件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.3.9 系統展示 用戶登錄 旅游路…

【數據結構】之二叉樹

二叉樹是我們在數據結構中學到的第一個非線性結構&#xff0c;是后續學習更為復雜的樹、圖結構的基礎。本文整理了二叉樹的概念定義、基本操作、遍歷算法、偽代碼與代碼實現以及實例說明&#xff0c;方便大家隨時查找對應。 一、定義與基本術語 二叉樹是一種樹形結構&#xf…

Honeyview:快速瀏覽各類圖像

Honeyview是一款免費、輕量級圖片查看工具?&#xff0c;專為快速瀏覽各類圖像設計&#xff0c;支持Windows系統?。其核心優勢在于?極速加載?與?廣泛格式兼容性?&#xff0c;可替代系統自帶的圖片查看工具&#xff0c;尤其適合需要處理專業圖像&#xff08;如PSD、RAW&…

Streamlit性能優化:緩存與狀態管理實戰

目錄 &#x1f4cc; 核心特性 &#x1f4cc; 運行原理 &#xff08;1&#xff09;全腳本執行 &#xff08;2&#xff09;差異更新 &#x1f4cc; 緩存機制 ?為什么使用緩存&#xff1f; 使用st.cache_data的優化方案 緩存適用場景 使用st.session_state的優化方案 &…

十七、TCP編程

TCP 編程是網絡通信的核心&#xff0c;其 API 圍繞面向連接的特性設計&#xff0c;涵蓋服務端和客戶端的交互流程。以下是基于 ?C 語言的 TCP 編程核心 API 及使用流程的詳細解析&#xff1a; 核心 API 概覽 ?函數?角色?描述socket()通用創建套接字&#xff0c;指定協議族…

將外網下載的 Docker 鏡像拷貝到內網運行

將外網下載的 Docker 鏡像拷貝到內網運行&#xff0c;可以通過以下步驟實現&#xff1a; 一、在有外網訪問權限的機器上操作 下載鏡像 使用docker pull命令下載所需的鏡像。例如&#xff0c;如果你需要下載一個名為nginx的鏡像&#xff0c;可以運行以下命令&#xff1a;docke…

《深入理解生命周期與作用域:以C語言為例》

&#x1f680;個人主頁&#xff1a;BabyZZの秘密日記 &#x1f4d6;收入專欄&#xff1a;C語言 &#x1f30d;文章目入 一、生命周期&#xff1a;變量的存在時間&#xff08;一&#xff09;生命周期的定義&#xff08;二&#xff09;C語言中的生命周期類型&#xff08;三&#…

Hqst的超薄千兆變壓器HM82409S在Unitree宇樹Go2智能機器狗的應用

本期拆解帶來的是宇樹科技推出的Go2智能機器狗&#xff0c;這款機器狗采用狗身體形態&#xff0c;前端設有激光雷達&#xff0c;攝像頭和照明燈。在腿部設有12個鋁合金精密關節電機&#xff0c;并配有足端力傳感器&#xff0c;通過關節運動模擬狗的運動&#xff0c;并可做出多種…

壹起航:15年深耕,引領中國工廠出海新征程

在全球化浪潮洶涌澎湃的當下&#xff0c;中國工廠正以前所未有的熱情和決心&#xff0c;將目光投向廣闊的海外市場。然而&#xff0c;出海之路并非一帆風順&#xff0c;建立品牌、獲取穩定詢盤、降低營銷成本等難題&#xff0c;如同橫亙在企業面前的高山&#xff0c;阻礙著他們…

【差分隱私相關概念】基礎合成定理和高級合成技術簡單關系

差分隱私中的合成定理用于分析多個機制組合時的隱私損失。基礎合成定理和高級合成技術分別在不同場景下提供了隱私預算增長的估計&#xff0c;其關系如下&#xff1a; 基礎合成定理&#xff08;線性增長&#xff09; 機制組合&#xff1a;當k個滿足(ε, δ)-DP的機制按順序組…

【異常處理】Clion IDE中cmake時頭文件找不到 頭文件飄紅

如圖所示是我的clion項目目錄 我自定義的data_structure.h和func_declaration.h在unit_test.c中無法檢索到 cmakelists.txt配置文件如下所示&#xff1a; cmake_minimum_required(VERSION 3.30) project(noc C) #設置頭文件的目錄 include_directories(${CMAKE_SOURCE_DIR}/…

MOS的驅動電流怎么計算?

一、MOS 驅動電流的計算方法 MOS 管在開關時&#xff0c;驅動電路主要是給柵極充放電。柵極電流 不是用來維持電流&#xff0c;而是用來克服電容的充放電需求&#xff0c;尤其是總柵極電荷 Qg。 驅動電流估算公式如下&#xff1a; I_drive Qg f_sw&#xff08;Qg&#xff…