iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0

一、 簡介

圖片強度轉換主要用於調整影像的亮度和對比度,以便加強影像中的特徵或提升影像的可視性。這種轉換通常透過調整像素的亮度值和對比度來實現,從而使影像呈現出更合適的視覺效果。這種處理方法在許多影像處理應用中都非常常見,例如在調整曝光、增強細節等方面。

二、 原理

1. 負片轉換(Negative transformation)

負片轉換用於將一張彩色或灰度影像的顏色或亮度值取反,從而產生一個負片效果。負片轉換的原理非常簡單,它基本上是將每個像素的亮度值反向,以產生新的像素值。對於灰度影像,取反的方法是將最大亮度值(255)減去每個像素的亮度值。負片轉換的公式可以表示為:
https://ithelp.ithome.com.tw/upload/images/20230920/20161732iNYj2chJ1k.png

這張Lenna圖就是經過負片轉換以後的結果,如果你再對這張照片進行負片轉換,就會變為原先的Lenna圖。
https://ithelp.ithome.com.tw/upload/images/20230920/20161732LVQN4HYkRM.jpg

2. 伽馬校正(Gamma correction)

伽馬校正是一種調整影像亮度和對比度的校正法,用於修正螢幕、相機和影像處理中的非線性亮度響應。它的主要目的是使影像在不同的輸出設備上以及不同的環境中都能夠呈現出更自然、更平衡的視覺效果。伽馬校正的核心概念基於人類眼睛對亮度變化的非線性感知。人眼對亮度的感知不是線性的,而是呈現出一種對低亮度變化敏感,對高亮度變化不敏感的特性。因此,在數位影像中,為了使影像在顯示器上以及人眼中看起來更加平衡,需要對亮度值進行調整。

伽馬校正的數學公式可以表示為:
https://ithelp.ithome.com.tw/upload/images/20230920/20161732hfEi6IgNGa.png

  • g(x, y) 是經過伽馬校正後的新像素值,位於坐標 (x, y) 處。
  • f(x, y) 是原始影像中的像素值,位於坐標 (x, y) 處。
  • γ 是伽馬值,是一個實數,用於調整伽馬校正的效果。較小的 γ 值(小於1)會增強暗部細節,較大的 γ 值(大於1)會增強亮部細節。
  • 1/255 是一個常數,用於將像素值從 0~255 的範圍縮放到 1 的範圍,對輸入數值進行正規化。

下圖是圖片在經過不同的γ值校正後所產生的結果。

https://ithelp.ithome.com.tw/upload/images/20230920/20161732utQHEXE8T5.jpg

下圖是伽馬校正的輸入-輸出關係曲線圖,透過調整γ值可以得到不同的關係曲線,從而影響亮度值的映射方式。

https://ithelp.ithome.com.tw/upload/images/20230920/20161732rNlEo2pbXB.jpg

3. 二值化(Thresholding)

二值化是一種影像處理技術,用於將一張灰度影像轉換為只有兩個值的影像,黑色和白色。這種處理可以簡化影像,強調重要特徵,並減少計算成本。
https://ithelp.ithome.com.tw/upload/images/20230920/201617329js65fY9Ih.png
二值化的原理是基於閾值的概念。閾值是一個用來劃分灰度值的數值,根據閾值,影像中的每個像素點將被分為兩個類別:高於閾值的像素被賦予一個值(白色),低於閾值的像素被賦予另一個值(黑色),形成了一個只包含兩種顏色的影像。
https://ithelp.ithome.com.tw/upload/images/20230920/20161732KSefZ60Ods.png

https://ithelp.ithome.com.tw/upload/images/20230920/20161732uKvKX3gdBY.jpg

三、程式碼

1. 逐行解釋

這個程式碼提供了兩種強度轉換的運算方式,一個是使用OpenCV內建的函式,另一個是使用蜂巢迴圈實現的演算法,兩者效果一樣。你可以透過設定USE_OPENCV10來決定你要使用前者還是後者的實現方式。

#define USE_OPENCV 1

1) 負片轉換

負片轉換最簡單,直接將255減原始圖就是負片轉換的結果。

cv::imshow("Nagative Transformation", 255 - grayImage);

2) 使用OpenCV進行伽馬校正

cv::intensity_transform::gammaCorrection(grayImage, dst, gamma);
  • grayImage:原始灰階影像。
  • dst:用於儲存伽馬校正後的結果的影像。
  • gamma:伽馬值,決定了伽馬曲線的形狀,影響校正的效果。伽馬值越大,影像的暗部和亮部的對比度會增強。

3) 使用OpenCV進行二值化

cv::threshold ,用於執行閾值二值化操作。它需要幾個參數:

cv::threshold(grayImage, binaryImage, thresholdValue, 255, CV_8UC1);
  • grayImage:原始灰階影像。
  • binaryImage:用於儲存二值化後的結果的影像。
  • thresholdValue:閾值,決定了像素的分類,小於閾值的像素被設置為0,大於等於閾值的像素被設置為255。
  • 255:表示二值化後的像素值,一般用於將像素值設置為最大值,即白色。
  • CV_8UC1:表示輸出影像的數據類型,這裡是8位無符號整數,單通道。

4) 使用演算法實踐伽馬校正

  1. 宣告一個空的 cv::Mat 物件 dst 來儲存伽馬校正後的影像。
  2. 透過迴圈遍歷原始灰階影像的每個像素。
  3. 對每個像素值進行正規化,將像素值除以255,得到一個介於0到1之間的正規化值。
  4. 透過伽馬值 gamma 的不同,使用 std::pow 函數計算正規化值的指數運算,得到調整後的像素值。
  5. 將調整後的像素值乘以255,將值重新映射到8位範圍內。
  6. 將調整後的像素值存儲到 dst 影像的對應位置。

這樣,程式碼通過遍歷每個像素,對每個像素的灰階值進行伽馬校正的計算,最終生成了一張具有調整亮度的新灰階影像。

cv::Mat dst;
// 自行計算伽馬校正
dst = cv::Mat::zeros(grayImage.rows, grayImage.cols, CV_8UC1);

for (int y = 0; y < grayImage.rows; y++)
{
    for (int x = 0; x < grayImage.cols; x++)
    {
        uchar value = grayImage.at<uchar>(y, x);
        float input = value / 255.0f;
        dst.at<uchar>(y, x) = (uchar)255 * std::pow(input, gamma);
    }
}

5) 使用演算法實踐二值化

  1. 獲取軌跡條位置的值,作為二值化的閾值。
  2. 建立一個空的 cv::Mat 物件 binaryImage,用於存儲二值化後的影像。
  3. 使用 cv::Mat::zeros 建立一個全黑的單通道灰階影像,大小與原始灰階影像相同。
  4. 使用兩個迴圈遍歷原始灰階影像的每個像素。
  5. 獲取原始灰階影像中座標處的像素值,如果像素值大於等於閾值 thresholdValue,則將 binaryImage 中相應位置的像素值設置為255,表示目標物體。否則,保持該像素值為0,表示背景。
double thresholdValue = position;
cv::Mat binaryImage;
binaryImage = cv::Mat::zeros(grayImage.rows, grayImage.cols, CV_8UC1);
for (int y = 0; y < grayImage.rows; y++)
{
    for (int x = 0; x < grayImage.cols; x++)
    {
        uchar value = grayImage.at<uchar>(y, x);
        if (value >= thresholdValue)
        {
            binaryImage.at<uchar>(y, x) = 255;
        }
    }
}

2. 完整程式碼

  1. 設定OpenCV的日誌級別為LOG_LEVEL_SILENT,禁止OpenCV輸出日誌訊息。
  2. 建立一個名為"Binary Image"的視窗。建立一個稱為"Threshold"的滑動條,可以通過調整它的值來控制影像的二值化閾值,觀察影像的二值化效果。
  3. 建立第二個視窗,名稱為"Gamma Correction"。建立了一個稱為"Gamma"的滑動條,可以通過調整它的值來控制影像的伽馬值,觀察影像的伽馬校正效果。
  4. 顯示灰階影像的負片效果,並將其顯示在名稱為"Nagative Transformation"的視窗中。
#include <iostream>
#include "opencv2/opencv.hpp"
#include "math.h"
#include "opencv2/intensity_transform.hpp"
#include "opencv2/core/utils/logger.hpp"

#define USE_OPENCV 1

using namespace std;

// 設置滑動條的回調函數
void trackbar_thresholding_callback(int position, void*);
void trackbar_gamma_correction_callback(int position, void*);

cv::Mat grayImage;

int main()
{
    cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_SILENT);

    // 讀取影像
    grayImage = cv::imread("C:\\Users\\vince\\Downloads\\Lenna.png", cv::IMREAD_GRAYSCALE);

    // 建立二值化影像視窗和滑動條
    cv::namedWindow("Binary Image", cv::WindowFlags::WINDOW_NORMAL);
    cv::resizeWindow("Binary Image", 512.0f * ((float)grayImage.cols / grayImage.rows), 512);
    cv::createTrackbar("Threshold", "Binary Image", NULL, 255, trackbar_thresholding_callback);
    cv::imshow("Binary Image", grayImage);

    // 建立Gamma校正視窗和滑動條
    cv::namedWindow("Gamma Correction", cv::WindowFlags::WINDOW_NORMAL);
    cv::resizeWindow("Gamma Correction", 512.0f * ((float)grayImage.cols / grayImage.rows), 512);
    cv::createTrackbar("Gamma", "Gamma Correction", NULL, 100, trackbar_gamma_correction_callback);
    cv::imshow("Gamma Correction", grayImage);

    // 顯示底片影像
    cv::imshow("Negative Transformation", 255 - grayImage);

    cv::waitKey(0);
    return 0;
}

// Gamma校正的滑動條回調函數
void trackbar_gamma_correction_callback(int position, void*)
{
    double gamma = position / 10.0;
    cv::Mat dst;

#if USE_OPENCV
    cv::intensity_transform::gammaCorrection(grayImage, dst, gamma);
#else
    dst = cv::Mat::zeros(grayImage.rows, grayImage.cols, CV_8UC1);

    for (int y = 0; y < grayImage.rows; y++)
    {
        for (int x = 0; x < grayImage.cols; x++)
        {
            uchar value = grayImage.at<uchar>(y, x);
            float input = value / 255.0f;
            dst.at<uchar>(y, x) = (uchar)255 * std::pow(input, gamma);
        }
    }
#endif

    cv::imshow("Gamma Correction", dst);
}

// 二值化的滑動條回調函數
void trackbar_thresholding_callback(int position, void*)
{
    double thresholdValue = position;
    cv::Mat binaryImage;

#if USE_OPENCV
    cv::threshold(grayImage, binaryImage, thresholdValue, 255, CV_8UC1);
#else
    binaryImage = cv::Mat::zeros(grayImage.rows, grayImage.cols, CV_8UC1);
    
    for (int y = 0; y < grayImage.rows; y++)
    {
        for (int x = 0; x < grayImage.cols; x++)
        {
            uchar value = grayImage.at<uchar>(y, x);
            if (value >= thresholdValue)
            {
                binaryImage.at<uchar>(y, x) = 255;
            }
        }
    }
#endif
    cv::imshow("Binary Image", binaryImage);
}

3. 測試結果

https://ithelp.ithome.com.tw/upload/images/20230920/201617328mk7w1c3fN.png


上一篇
【Day8】影像處理的數學基礎:深入解析影像摺積原理
下一篇
【Day10】OpenCV 直方圖均衡化:增強影像對比度
系列文
圖解C++影像處理與OpenCV應用:從基礎到高階,深入學習超硬核技術!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言