數字圖像處理與OpenCV初探

什么是數字圖像處理?

? ? 當今時代,數字圖像無處不在。手機拍照、安防監控、醫療檢查、地圖導航、工業質檢……我們每天都在接收、分析和處理大量圖像信息。對于計算機而言,圖像并不是一張“看得懂”的照片,而是由數值組成的矩陣。如何讓機器也具備“看圖”的能力,正是數字圖像處理的核心目標。

? ??簡而言之,數字圖像處理就是用計算機對圖像進行操作和分析,讓圖像更“清晰”、更“有用”、更“可識別”。舉例如下:

  • 拍完照后用手機“自動美顏”一下,可能用到了濾波、邊緣平滑、膚色增強等圖像處理算法;
  • 醫生查看 CT 或眼底圖時,圖像可能經過了對比度增強或偽彩色處理,使細節更加清晰;
  • 攝像頭識別車輛車牌,需要經過顏色識別、輪廓識別、字符識別等操作;

?什么是OpenCV?

? ? OpenCV(Open Source Computer Vision Library)是一個開源、跨平臺的計算機視覺庫,最初由英特爾開發,現在已經成為業界和學術界廣泛使用的工具之一。OpenCV有如下特性:

  • 跨平臺:支持 Windows、Linux、macOS
  • 語言支持豐富:C++/Python 作為主流語言選擇,也有部分選擇Java、JavaScript等
  • 實時性強:底層基于 C/C++,速度快,能勝任對性能要求高的實時應用
  • 功能強大:從圖像讀取到復雜特征匹配,從邊緣檢測到深度學習支持等

? ? 在我們的專欄中,我們的示例主要使用C++,這是工程領域中最合適的使用方式。C++提供的卓越的性能,可以滿足很多實時性的應用需求。同時,我們也會適當給出一些Python示例,在深度學習訓練階段,Python是我們的首選語言(一般選擇pytorch框架)。OpenCV可以對深度學習進行數據預處理支持。

? ? 對于OpenCV的安裝,Python環境下只需要運行以下命令即可:

?pip install opencv-python

? ? 對于C++環境,我們一般都是從源碼直接編譯,然后再部署到自己的開發環境中。我們這里不講如何源碼編譯,大家可以在網上自行搜索。我們稍后會提供一個完整的C++項目,該項目會包含OpenCV所有的依賴庫,大家可以基于該項目進行自己的開發工作。

數字圖像基本結構

? ? 上圖為一個4行8列的矩陣,每個元素的取值范圍為[0,256)。我們可以將其看作為一個4*8的灰度圖像,灰度圖像的取值范圍為[0,256)。在現實生活中,我們更多看到的是彩色圖像,彩色圖像相對于灰度圖像來說,每個元素需要3個值表示,分別代表Red,Green和Blue,其數據矩陣如下:? ?

? ? 以上同樣為一個4行8列的矩陣,但每個元素由一個3*1的向量構成,如第0行0列的向量值為[172,47,117],這三個元素具體表示:Blue=172,Green=47,Red=117。特別注意這里的通道排列順序為BGR,而在生活中我們習慣稱呼彩色圖像為RGB圖像。

? ? OpenCV提供了函數cv::imread(),該函數可讀取多種格式圖像,如JPG, BMP, PNG等,其返回值為cv::Mat對象,該對象保存了圖像相關的所有信息。不論讀取哪種格式圖像,只要該圖像為三通道數據,讀取后的圖像在內存中的排列順序均為BGR(四通道多為BGRA,A表示Alpha通道,用于記錄半透明相關信息)。

? ??OpenCV提供了函數cv::imshow(),該函數用于顯示圖像,其核心參數為cv::Mat對象。我們通過一個實驗來加深通道排列順序的理解。

import cv2  #導入opencv,可用于讀取與顯示圖像
import matplotlib.pyplot as plt  #用于圖像顯示img_bgr = cv2.imread('lena.png')  #讀取圖像, 默認通道為bgrif img_bgr is None:print("圖像加載失敗,請檢查路徑是否正確。")
else:img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)  # 轉換為rgb順序cv2.imshow('opencv show', img_bgr)  #使用opencv顯示圖像cv2.waitKey(0)  #opencv需要調用該函數已阻止程序繼續執行cv2.destroyAllWindows()  #用戶關閉圖像窗口后清除資源#使用matplot顯示圖像,這里需要傳入rgb順序圖像plt.imshow(img_rgb)plt.title("matplot show")plt.show()

? ? ?以上一段python代碼首先使用OpenCV讀取一張圖像,然后分別使用OpenCV與matplot庫進行顯示。需要特別注意,在matplot庫中,默認將通道順序解讀為RGB。因此,我們調用了cvtColor函數對其進行通道轉換(cv2.COLOR_BGR2RGB),使得matplot可以正確顯示圖像顏色。以下分別給出正確通道順序顯示結果與錯誤通道順序顯示結果。

正確通道順序顯示結果
錯誤通道順序顯示結果

? ? 接下來我們給出一段C++代碼,該代碼實現了圖像讀取與顯示,處理語法上的差異,與python代碼基本一致。

int main()
{cv::Mat img_bgr = cv::imread("lena.png", cv::IMREAD_COLOR);cv::imshow("opencv show", img_bgr);cv::waitKey(0);cv::destroyAllWindows();return 0;
}

數字圖像元素讀取與修改?

? ? ?到目前為止,我們了解了圖像數據的基本結構,也能正確讀取和顯示圖像。那么,我們應該如何讀取或修改圖像單個元素數據呢??

? ? 有如下方法可以讀取或修改圖像像素數據(C++),如下:

  • 使用cv::Mat.at<>()方法,該方法適合讀取少量數據。由于函數會進行邊界檢查,其速度較慢,在圖像處理算法實踐中,我們基本不會使用該函數讀取數據。以下給出示例代碼
{   // 讀取100行,150列數據,該數據為三通道數據cv::Vec3b val = img_bgr.at<cv::Vec3b>(100, 150);  uchar b = val[0];uchar g = val[1];uchar r = val[2];// 讀取單通道數據(即灰度圖)uchar gray = img_bgr.at<uchar>(100, 150);
}
  • 在實際項目中,我們總是直接訪問指針以獲得最佳的訪問效率,以下給出示例代碼
{// // 3通道圖像(bgr)訪問// 遍歷每一行for (int row = 0; row < img_bgr.rows; ++row){// 獲取每一行的起始指針cv::Vec3b* ptr = img_bgr.ptr<cv::Vec3b>(row);// 遍歷每一個元素(cv::Vec3b)for (int col = 0; col < img_bgr.cols; ++col){// 獲取每個通道的值uchar b = ptr[col][0];uchar g = ptr[col][1];uchar r = ptr[col][2];// 每個通道亮度*2// 由于每個通道取值范圍為[0,255],因此需要確保不越界!b = b * 2 > 255 ? 255 : b * 2;g = g * 2 > 255 ? 255 : g * 2;r = r * 2 > 255 ? 255 : r * 2;// 將修改后值賦給原通道ptr[col][0] = b;ptr[col][1] = g;ptr[col][2] = r;}}// // 單通道(灰度圖像)訪問// 遍歷每一行for (int row = 0; row < img_bgr.rows; ++row){// 獲取每一行的起始指針uchar* ptr = img_bgr.ptr<uchar>(row);// 遍歷每一個元素(uchar)for (int col = 0; col < img_bgr.cols; ++col){// 獲取灰度值uchar gray = ptr[col];// 每個通道亮度*2// 由于取值范圍為[0,255],因此需要確保不越界!gray = gray * 2 > 255 ? 255 : gray * 2;// 將修改灰度值賦給原圖像ptr[col] = gray;}}
}

? ?通過以上程序,我們可以得到一個亮度更高的圖像,效果如下:?

每個通道亮度翻倍后圖像

? ? 雖然直接訪問指針可以獲得最佳的運行效率,然而我們也可能因為訪問不當而產生以下不良后果,典型錯誤為內存越界錯誤,這可能導致整個程序崩潰。所以,在實際項目中,我們需要慎重使用指針,確保代碼正確性以避免內存越界錯誤!

? ? 另外,一些性能優化的常識可以讓我們避免一些極端低效的代碼,如下代碼大大降低運行效率:

{// 該代碼運行效率會非常低,由于違背了內存連續性訪問原則,// 導致頻繁的緩存命中失敗,嚴重降低數據訪問效率!// 遍歷每一列for (int col = 0; col < img_bgr.cols; ++col){// 遍歷每一行for (int row = 0; row < img_bgr.rows; ++row){cv::Vec3b val = img_bgr.ptr<cv::Vec3b>(row)[col];uchar b = val[0];uchar g = val[1];uchar r = val[2];}}
}

? ? 觀察以上代碼,我們for循環順序發生了改變,該代碼對圖像元素的訪問順序為:

? ? 0行0列->1行0列->2行0列...->0行1列->1行1列->2行1列....

? ? 也就是說在列方向上遍歷,而圖像元素在行方向上連續存儲,從而每次訪問都可能導致緩存命中失敗,從而嚴重影響訪問效率!

? ? ?一般情況下,C++提供了非常靈活的圖像數據讀取方式,有時候我們可能也會使用Python進行少量的數據讀取操作,以下給出使用Python讀取圖像數據的方法:

(b, g, r) = img_bgr[100, 150]  #獲取第100行第150列的B、G、R通道值
blue_channel = img_bgr[:, :, 0]  #獲取藍通道數據

cv::Mat關鍵元素?

? ? cv::Mat是 OpenCV中最核心的數據結構之一,用于表示圖像、視頻幀、矩陣等二維數據。理解 cv::Mat的內部結構對于高效圖像處理非常關鍵。早期的C接口使用IplImage結構,除了兼容需求,我們不再使用IplImage接口了。

? ? 以下是cv::Mat的基本數據結構:

? ? cv::Mat
? ? ?├── data? ? ? ? ? ?→ 指向圖像數據的指針
? ? ?├── rows? ? ? ? ? → 行數(即圖像高度)
? ? ?├── cols? ? ? ? ? ?→ 列數(即圖像寬度)
? ? ?├── step? ? ? ? ? ?→ 每行占用的字節數(stride)
? ? ?├── channels? ? → 通道數(通過 type 解析)
? ? ?├── type? ? ? ? ? ? → 數據類型和通道數的編碼
? ? ?├── depth()? ? ? ?→ 每個通道的數據類型(如 CV_8U)
? ? ?├── refcount? ? ? → 引用計數指針(實現共享內存)
? ? ?└── others? ? ? ? ?→ flags、allocator 等

? ? data為一個uchar*類型數據,指向圖像像素數據的首地址,可以直接通過指針操作像素,如:

uchar* p = img_bgr.data;? p[0] = 255; p[1] = 255;

? ? rows和cols分別代表圖像的行數與列數,也即圖像的高度與寬度。

? ? step表示圖像每一行占用的總字節數,利用該數據可以準確跳轉到每行數據首指針上,以下兩種寫法均可以跳轉到第10行首指針處,故data1與data2為相等指針。

cv::Vec3b* data1 = (cv::Vec3b*)(img_bgr.data + img_bgr.step * 10);
cv::Vec3b* data2?= img_bgr.ptr<cv::Vec3b>(10);

? ? type()函數返回一個整數,該整數編碼了通道數與數據類型信息。一般情況下,我們可以分別調用channels()與depth()函數來分別獲取通道數與數據類型。

? ? 在常規數字圖像中,通道數一般返回為1,3,4通道數據,分別表示灰度圖,真彩色,帶Alpha通道真彩色。當然,在其他應用中,也可以返回任意通道,如2通道可以編碼圖像梯度信息。

? ? 圖像數據類型主要定義了數據精度與數據符號,如CV_8U為8位無符號整數,CV_8S為8位有符號整數,CV_16U/CV_16S定義了16位整數,CV_32S定義了32位有符號整數(注意沒有CV_32U!),CV_32F/CV_64F分別定義了單精度與雙精度浮點類型。

int depth = img_bgr.depth();
int channels = img_bgr.channels();

? ? elemSize()表示一個像素占用的字節數,elemSize1()表示一個通道占用的字節數,使用elemSize() / elemSize1()可計算處通道數,等價于channnels()函數。?

? ??refcount作為內存引用計數,在淺拷貝時共享內存數據,僅增加引用計數,代碼如下:

cv::Mat img = cv::imread("lena.png", cv::IMREAD_COLOR);int* ref = img.refcount;  // 引用計數為1cv::Mat img2 = img;  //淺拷貝,img與img2公用內存int* ref2 = img2.refcount; // 淺拷貝后引用計數增加到2img.release();   // 釋放img,引用計數減1int* ref3 = img2.refcount; // 釋放img后,引用計數減少到1

? ? 除了淺拷貝之外,我們在很多時候有深拷貝需求(即不共享內存數據),函數copyTo()與clone()均可實現該目標,代碼如下:

// 方式 1:clone(返回新對象)
cv::Mat img_clone = img.clone();// 方式 2:copyTo(拷貝到已有對象)
cv::Mat img_copy;
img.copyTo(img_copy);

結語?

? ? 通過介紹數字圖像處理與OpenCV的基本知識,我們理解了數字圖像的基本結構,以及如何高效的訪問圖像中的任意元素。同時對通道順序以及內存連續性問題進行特別講解,使得我們可以在工程實踐中避免一些微妙的錯誤,提升程序的效率。最后,我們講解了OpenCV中最為重要的數據結構cv::Mat,通過該數據結構,可以實現圖像數據的所有基本操作。

? ? 在工程應用中,為了運行效率我們一般會選擇OpenCV的C++接口。然而在某些情況下,Python接口也發揮了重要的作用。如在深度學習的訓練過程中,我們一般使用pytorch框架。此時,使用OpenCV的Python接口進行數據預處理是非常必要的。因此,在博文中,我們同步給出了C++與Python代碼片段,以適應不同應用場景需求。

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

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

相關文章

ubuntu網絡連接失敗 + mobaxterm拖拽文件出錯等問題解決方法

網絡連接問題&#xff0c;表現在不能通過源下載以及更新 終端問題顯示【通過 ip a 命令獲得】 kejiubuntu:~/Desktop$ ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00…

C# CS_Prj01 串口通信控制臺程序

一直以來&#xff0c;玩8088單板機&#xff0c;上位機都是使用的綠色現成的串口軟件。 今天&#xff0c;感覺8088單板機的各部分測試都基本完成了。 本著玩的精神&#xff0c;自己寫一個上位機的簡單串口程序&#xff0c;與自己的8088單板機通訊。 功能&#xff1a;一個完整…

40套精品大氣黑金系列行業PPT模版分享

黑金系列PPT模版&#xff0c;優秀員工頒獎典禮PPT模版&#xff0c;消費訂貨會PPT模版&#xff0c;共贏未來PPT模版&#xff0c;投資類PPT模版&#xff0c;雙12年終盛典PPT模版&#xff0c;商業計劃書PPT模版&#xff0c;高端通用企業文化PPT模版&#xff0c;公司喜報企業捷報PP…

SAP學習筆記 - 開發31 - 前端Fiori開發 Device Adaptation(設備自適應)

上一章講了Fiori開發中的 Responsiveness&#xff08;響應式設計&#xff09;。 SAP學習筆記 - 開發30 - 前端Fiori開發 Responsiveness&#xff08;響應式設計&#xff09;-CSDN博客 本章繼續學習Fiori 開發中的知識。 目錄 1&#xff0c;Device Adaptation&#xff08;設備…

網絡的那些事——初級——OSPF(2)

前面說了OSPF的狀態機和一起簡單的OSPF配合&#xff0c;接下這章繼續寫OSPFV2 IP frr和OSPFV3. 什么是OSPF IP FRR? OSPF IP FRR&#xff08;Fast Reroute&#xff09;利用全網鏈路狀態數據庫&#xff0c;預先計算出備份路徑保存在轉發表中&#xff0c;以備在故障時提供流量保…

C++(初階)(二十一)——unordered_set和unordered_map

二十二&#xff0c;unordered_set和unordered_map的使用 1.unordered_set 1.1介紹 c11 template<class Key,class Hash std::hash<Key>,class KeyEqual std::equal_to<Key>,class Allocator std::allocator<Key> > class unordered_set;c17 na…

Java面試題:分布式ID時鐘回撥怎么處理?序列號耗盡了怎么辦?

歡迎來到啾啾的博客&#x1f431;。 記錄學習點滴。分享工作思考和實用技巧&#xff0c;偶爾也分享一些雜談&#x1f4ac;。 有很多很多不足的地方&#xff0c;歡迎評論交流&#xff0c;感謝您的閱讀和評論&#x1f604;。 目錄 引言1 分布式ID2 問題2.1 時鐘回撥2.1.1 毫秒級時…

影視劇學經典系列-梁祝-陶淵明《感士不遇賦并序》

1、背景 《感士不遇賦并序》是東晉詩人陶淵明創作的一篇抒發懷才不遇之慨的辭賦作品。受董仲舒《士不遇賦》和司馬遷《悲士不遇賦》啟發&#xff0c;陶淵明借古喻今&#xff0c;批判“真風告逝&#xff0c;大偽斯興”的亂世。社會批判?以“密網裁而魚駭&#xff0c;宏羅制而鳥…

Spring Cloud Gateway 全面學習指南

Spring Cloud Gateway 全面學習指南 學習目錄 第一部分&#xff1a;基礎概念與核心架構 API網關概述與Spring Cloud Gateway簡介Spring Cloud Gateway核心架構與工作原理Spring Cloud Gateway與Zuul的對比分析Spring Cloud Gateway核心組件詳解 第二部分&#xff1a;基礎配…

蛋白分析工具和數據庫

UniProt&#xff08;Universal Protein Resource&#xff09;是一個綜合性的蛋白質數據庫&#xff0c;提供了全球范圍內已知的蛋白質序列和功能信息。其中&#xff0c;UniProtKB&#xff08;UniProt Knowledgebase&#xff09;是最核心的組成部分&#xff0c;包含了經過注釋和分…

Docker -- 快速入門

鏡像與容器 當我們使用Docker安裝應用時&#xff0c;Docker會自動搜索并下載應用鏡像&#xff08;image&#xff09;。鏡像不僅包含應用本身&#xff0c;還包含應用運行所需要的環境&#xff0c;配置、系統函數庫。Docker會在運行鏡像時創建一個隔離環境&#xff0c;稱為容器&…

輸入數量未知如何設置輸入

在 C 的算法題中&#xff0c;如果你不知道輸入數據有多少組&#xff08;即測試用例的數量未知&#xff09;&#xff0c;通常的處理方式是使用 循環讀取輸入直到文件結束 &#xff08;EOF&#xff09;。這類題目常見于在線評測系統&#xff08;如 LeetCode、牛客網、POJ 等&…

如何在Windows上使用qemu安裝ubuntu24.04服務器?

2025年6月15日&#xff0c;周日晚上 在Windows上使用QEMU安裝Ubuntu 24.04需要完成環境配置、鏡像準備、虛擬機創建及系統安裝等步驟。以下是綜合多個搜索結果后的詳細指南&#xff1a; 1. 安裝QEMU環境 下載QEMU Windows版 從QEMU官網下載64位安裝包&#xff08;如qemu-w64-s…

前端開發面試題總結-vue2框架篇(二)

Vue2高頻問答 一、為什么 Vue 的 data 屬性必須聲明為返回一個初始數據的函數? 回答重點&#xff1a; Vue 的 data 屬性聲明成一個返回初始數據的函數&#xff0c;是為了確保每個組件實例都有獨立的狀態。通過這種方式&#xff0c;避免了組件使用相同的數據對象導致的狀態共…

Web第二次方向考核復盤

一、簡答題 1. &#xff08;1&#xff09;為什么要清除浮動&#xff1f; 答&#xff1a;當子元素浮動時會脫離文檔流&#xff0c;父元素無法正確計算子元素高度導致高度、邊框異常顯示。同時會影響后續文檔流布局。 <style>.box1 {border: solid 2px #000;}.child1 {fl…

Linux入門(十八)read函數

read 讀取控制臺輸入 基本語法 read 選項 參數 選項 -p 指定讀取值時的提示符 -t 指定讀取值時等待的時間&#xff08;秒&#xff09;&#xff0c;如果沒有在指定的時間內輸入&#xff0c;就不再等待了 參數 變量&#xff1a;指定讀取值的變量名 實例&#xff1a; 1、讀取控制…

Python 文件操作詳解

文章目錄 Python 文件操作詳解一、文件操作的基本流程二、文件打開模式詳解1. 基本模式2. 擴展模式3. 模式組合示例 三、文件操作方法大全1. 打開和關閉文件2. 讀取文件內容3. 寫入文件內容4. 文件指針操作 四、文件編碼處理五、二進制文件操作六、常見文件操作場景1. 文件內容…

用AI配合MCP快速生成n8n工作流

在數字化時代的浪潮中&#xff0c;AI技術正以前所未有的速度改變著我們的生活和工作方式。從智能家居到智能辦公&#xff0c;從數據分析到自動化流程&#xff0c;AI的應用場景無處不在。今天&#xff0c;我們將帶你走進一個充滿創新與效率的世界&#xff0c;探索如何通過AI大眼…

ArkUI-X框架LogInterface使用指南

ArkUI-X框架支持日志攔截能力&#xff0c;Android側提供原生接口&#xff0c;用于注入LogInterface接口&#xff0c;框架日志及ts日志通過該接口輸出&#xff0c;本文的核心內容是介紹如何在Android平臺上有效利用ArkUI-X框架的LogInterface攔截日志。 Android平臺創建ArkUI-X…

函數重載與函數模板

函數重載與函數模板 函數重載 函數組成 返回類型 函數名稱(參數列表){函數體}函數簽名&#xff1a;函數名稱(參數列表) C 允許定義同名函數&#xff0c;前提是它們具有不同的簽名。這被稱為函數重載 。 C 編譯器通過檢查調用中參數的數量、類型和順序來選擇要調用的適當函…