LINUX下简单实现ISP图像处理从RAW到RGB,BMP算法、RGB到JPEG库的使用(一)
? ? ? ?
????????在這里分享一下相關(guān)的ISP的一些基本簡(jiǎn)單圖像處理算法。在一般的市面上,相關(guān)的ISP算法都是非常復(fù)雜,且經(jīng)過(guò)不同serson設(shè)備的情況進(jìn)行固定參數(shù)并且固化在芯片內(nèi)來(lái)實(shí)現(xiàn)。所以硬件ISP的效率會(huì)比軟件算法實(shí)現(xiàn)的ISP要高,而且后續(xù)開(kāi)發(fā)者所要做的事情比較少。但是缺點(diǎn)就是實(shí)現(xiàn)邏輯復(fù)雜,而且不同設(shè)備并不是完全通用。下面,由我來(lái)分享一下最近的干貨。
? ? ? ? 這里實(shí)現(xiàn)的是ISP的功能是對(duì)圖像的處理,以及像素的變化,一般攝像頭都是自帶硬件ISP,所以你拿到的圖片都是經(jīng)過(guò)處理過(guò)的。這里是范例講解ISP內(nèi)部一些簡(jiǎn)單的算法實(shí)現(xiàn)!在LINUX上對(duì)圖片直接變換。分享一些最簡(jiǎn)單的方法幫助大家理解處理的流程,有一些處理比較復(fù)雜,所以樓主打算下一篇文章再次補(bǔ)上,點(diǎn)名白平衡要跨域處理。
ISP 處理流程:
Bayer、黑電平補(bǔ)償 (black level compensation)、鏡頭矯正(lens shading correction)、壞像素矯正(bad pixel correction)、顏色插值 (demosaic)、Bayer 噪聲去除、 白平衡(AWB) 矯正、 色彩矯正(color correction)、gamma 矯正、色彩空間轉(zhuǎn)換(RGB 轉(zhuǎn)換為 YUV)、在YUV 色彩空間上彩噪去除與邊緣加強(qiáng)、色彩與對(duì)比度加強(qiáng),中間還要進(jìn)行自動(dòng)曝光控制等, 然后輸出 YUV(或者RGB) 格式的數(shù)據(jù), 再通過(guò) I/O 接口傳輸?shù)?CPU 中處理。
? ? ? ? 首先我們要了解一些圖片的格式:
? ? ? ? 這篇博客主要處理RAW到RGB888的一些步驟,上面介紹的圖像格式中常見(jiàn)的PNG,JPG(JPEG)圖像格式是進(jìn)行壓縮編碼過(guò)后的圖片,PNG一般能壓縮到原像素大小圖片的一半左右,還多出了透明度。JPG格式甚至能壓縮到原圖的百分之二三十。BMP是沒(méi)有被壓縮的圖片,不過(guò)他經(jīng)過(guò)了色域轉(zhuǎn)換。RAW格式就是一張灰階圖,它沒(méi)經(jīng)過(guò)色彩空間的變換,只是一張每個(gè)像素記錄灰度值的圖片。一般有RAW10,RAW8,RAW12,也就是每個(gè)像素兩個(gè)字節(jié),里面高位或者低位的前后10,8,12個(gè)位是有效位。
? ? ? ? 過(guò)程一般是RAW轉(zhuǎn)化為RGB再轉(zhuǎn)化為BMP,或者RGB再次轉(zhuǎn)化為YUV格式(這種格式方便傳輸)。也可以由RGB轉(zhuǎn)化為YCrCb后再變化為JPEG(過(guò)程復(fù)雜),或者RGB數(shù)據(jù)解碼為像素?cái)?shù)據(jù),再變?yōu)镻NG。
? ? ? ? 在寫代碼之前我們要弄懂什么叫RAW的顏色和灰階(灰度)。在RAW圖中每個(gè)像素實(shí)際上都是代表了一種顏色。一般情況下主要有這四種分布情況,這是我們直接拿出四個(gè)格子的像素顏色作為參考(例如第一種我們把他叫做GRBG格式的bayer圖),以此類推。每個(gè)我們看見(jiàn)的BMP彩色照片每個(gè)像素都是由三種顏色RGB組成的,但是bayer圖每個(gè)像素只有一種顏色,所以放大來(lái)看就是和下面的圖一樣,但是如果一個(gè)像素包含三種顏色RGB為例,那他就不是單純的紅、綠、藍(lán),而是三種顏色的混色。RAW格式是沒(méi)有混色的格式圖片,它每個(gè)像素只有一種顏色。每個(gè)顏色的值代表著這個(gè)顏色的深淺度。
? ? ? ? 寫代碼前一定要注意!!!溢出!!溢出!!對(duì)于十六位加法要很多像素相加的最好用六十四位作為total值!出現(xiàn)什么問(wèn)題先排除是不是溢出問(wèn)題!!!還有就是計(jì)算無(wú)符號(hào)數(shù)是沒(méi)有小數(shù)的,比如說(shuō)陰影矯正如果沒(méi)有小數(shù),就會(huì)出現(xiàn)每一圈亮度波紋的沒(méi)有過(guò)度(因?yàn)槎际钦麛?shù)倍從1直接跳到2,缺少了1和2之間的倍數(shù)圖像就會(huì)變化的很突兀)看起來(lái)亮度就是一圈一圈的。解決這種方法最好就是變大十倍乃至一百倍相乘運(yùn)算后再次除以十倍乃至一百倍,這樣就能解決無(wú)符號(hào)數(shù)不能計(jì)算小數(shù)的問(wèn)題!!!
讀取一張RAW格式圖片:
? ? ? ? 這里不詳細(xì)說(shuō)明其他,只說(shuō)明對(duì)RAW到RGB,RGB到BMP,以及RGB使用libjpeg轉(zhuǎn)化為jpg格式的過(guò)程。這里樓主用的是一張5600*5600像素的低十位有效的RAW圖。記得注意你當(dāng)前環(huán)境的大小端,低位是前十位還是后十位,如果和我不一樣得要把大小端顛倒過(guò)來(lái)計(jì)算。這里提供一個(gè)轉(zhuǎn)換函數(shù):
// 大小端轉(zhuǎn)換,寫成內(nèi)聯(lián)函數(shù)效率高 inline uint16_t swap_endian(uint16_t num) {return (num >> 8) | (num << 8); }? ? ? ? 首先第一步,再Linux下我們要對(duì)RAW文件進(jìn)行讀取。(這里讀取后我直接把他轉(zhuǎn)成BMP查看是否正確)。以下是讀取代碼:
vector<vector<uint16_t>> read_raw(const char *filename, int rows, int cols) {vector<vector<uint16_t>> image(rows, vector<uint16_t>(cols));FILE *fp;uint16_t buffer[1];int i, j;fp = fopen(filename, "rb");if (fp == NULL){perror("Error opening file");exit(1);}for (i = 0; i < rows; i++){for (j = 0; j < cols; j++){ if (fread(buffer, sizeof(uint16_t), 1, fp) != 1){printf("Error reading file: row %d, col %d\n", i, j);exit(1);}image[i][j] = buffer[0];}}fclose(fp);return image; }? ? ? ? 兩個(gè)字節(jié)兩個(gè)字節(jié)大小的讀取,把讀到的數(shù)據(jù)放到一個(gè)5600*5600的二維向量中。這里用向量有個(gè)好處,就是可以忽略數(shù)組的越界問(wèn)題。雖然上面的讀取簡(jiǎn)單,但是響應(yīng)速度比較慢讀一次五六秒,對(duì)于一個(gè)圖像處理芯片來(lái)說(shuō)肯定是越快越好,所以我對(duì)其進(jìn)行了優(yōu)化,采用的多線程讀取。下面是多線程讀取raw格式文件部分主要的代碼,這是所有線程都要用到的結(jié)構(gòu)體:
#define THREAD_NUM 24 // 定義線程數(shù) #define PIXEL_SIZE 2 // 定義每個(gè)像素占用的字節(jié)數(shù)為2字節(jié)// 線程傳遞消息結(jié)構(gòu)體 struct ThreadArg {int start_row; // 線程處理的起始行號(hào)int end_row; // 線程處理的結(jié)束行號(hào)int image_width; // 圖像的寬度int image_height; // 圖像高度const char *filename; // 文件名vector<vector<uint16_t>> *image_data; // 傳入的圖像數(shù)組vector<vector<Pixel>> *rgb_data; // 傳入的圖像數(shù)組uint16_t min_p; // 最小值參數(shù)// ThreadArg() = default; // 默認(rèn)構(gòu)造函數(shù) };? ? ? ? 然后就是線程讀取函數(shù),和線程函數(shù)的代碼:
void *read_image(void *arg) {ThreadArg *thread_arg = (ThreadArg *)arg; // 將傳遞給該線程的參數(shù)強(qiáng)制轉(zhuǎn)換為 ThreadArg 指針int start_row = thread_arg->start_row; // 獲取該線程需要處理的起始行號(hào)和結(jié)束行號(hào)int end_row = thread_arg->end_row;int image_width = thread_arg->image_width; // 獲取圖像的寬度、文件路徑以及二維矢量的指針const char *filename = thread_arg->filename;vector<vector<uint16_t>> *image_data = thread_arg->image_data;FILE *fp = fopen(filename, "rb");if (fp == NULL){cerr << "Failed to open file " << filename << endl;pthread_exit(NULL);}fseek(fp, start_row * image_width * PIXEL_SIZE, SEEK_SET); // 將文件指針定位到該線程需要處理的起始位置for (int i = start_row; i < end_row; i++) // 循環(huán)讀取該線程需要處理的所有行{vector<uint16_t> row_data(image_width); // 創(chuàng)建一個(gè)臨時(shí)的一維矢量,用于存儲(chǔ)當(dāng)前行的數(shù)據(jù)fread(row_data.data(), PIXEL_SIZE, image_width, fp);(*image_data)[i] = row_data;}fclose(fp);pthread_exit(NULL); }vector<vector<uint16_t>> thread_read_raw(const char *filename, int image_width, int image_height) {vector<vector<uint16_t>> *image_data = new vector<vector<uint16_t>>(image_height, vector<uint16_t>(image_width));pthread_t threads[THREAD_NUM]; // 創(chuàng)建多個(gè)線程,每個(gè)線程讀取圖像的一部分ThreadArg *thread_args = new ThreadArg[THREAD_NUM];int rows_per_thread = image_height / THREAD_NUM;int i;for (i = 0; i < THREAD_NUM - 1; i++) // 計(jì)算該線程需要處理的起始行號(hào)和結(jié)束行號(hào){thread_args[i].start_row = i * rows_per_thread;thread_args[i].end_row = thread_args[i].start_row + rows_per_thread;thread_args[i].filename = filename;thread_args[i].image_data = image_data;thread_args[i].image_width = image_width;pthread_create(&threads[i], NULL, read_image, (void *)&thread_args[i]); // 創(chuàng)建線程并啟動(dòng)}// 處理最后一個(gè)線程需要處理的行數(shù)可能不足 rows_per_thread 的情況thread_args[i].start_row = i * rows_per_thread;thread_args[i].end_row = image_height;thread_args[i].filename = filename;thread_args[i].image_data = image_data;thread_args[i].image_width = image_width;pthread_create(&threads[i], NULL, read_image, (void *)&thread_args[i]); // 創(chuàng)建線程并啟動(dòng)for (i = 0; i < THREAD_NUM; i++) // 等待所有線程執(zhí)行完畢{pthread_join(threads[i], NULL);}vector<vector<uint16_t>> result(*image_data); // 將指針指向的二維矢量復(fù)制到一個(gè)新的二維矢量中delete image_data;delete[] thread_args;return result; }? ? ? ?這是讀取到的一張圖片:
? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ??RAW格式原圖? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ?RAW格式放大? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? 放大后清晰的看見(jiàn)一張RAW格式的圖是由紅綠藍(lán)三色組成。
壞點(diǎn)矯正:
? ? ? ??壞點(diǎn)是指在數(shù)字圖像中出現(xiàn)的一些不正常的像素點(diǎn),它們可能是傳感器損壞、傳輸過(guò)程中出現(xiàn)的錯(cuò)誤或其他原因引起的。壞點(diǎn)的存在會(huì)影響圖像的質(zhì)量和準(zhǔn)確性,因此需要進(jìn)行壞點(diǎn)處理。比如說(shuō)serson上某個(gè)感光傳感器損壞,導(dǎo)致拍出的那一個(gè)點(diǎn)的像素缺失。
? ? ? ? 樓主本人對(duì)壞點(diǎn)處理的方法是判斷每個(gè)像素是否低于相鄰像素的平均值0x80(這個(gè)值是看自己設(shè)置)可以調(diào)成自己合適的參數(shù)。
uint16_t seek_bad_Pixel(vector<vector<uint16_t>> &image) {int width = image.size(), height = image[0].size(), m = 0, n = 0;uint16_t min_pixel = image[0][0];// 尋找壞點(diǎn)for (int i = 0; i < width; i++){for (int j = 0; j < height; j++){// 判斷壞點(diǎn)if (abs((correct_bad_Pixel(image, i, j) - image[i][j])) > 0x80){image[i][j] = correct_bad_Pixel(image, i, j);}if (image[i][j] < min_pixel){min_pixel = image[i][j];}}}return min_pixel; }uint16_t correct_bad_Pixel(vector<vector<uint16_t>> &image, int bad_pixel_x, int bad_pixel_y) {int width = image.size(), height = image[0].size(), count = 0;uint16_t sum = 0, direction_avaerage = 0, direction_up = 0, direction_dowm = 0, direction_right = 0, direction_left = 0; // 周圍元素的總和if (bad_pixel_x < 0 || bad_pixel_x >= width || bad_pixel_y < 0 || bad_pixel_y >= height){return -1;}if (bad_pixel_x - 2 >= 0){direction_up = image[bad_pixel_x - 2][bad_pixel_y];count++;}if (bad_pixel_x + 2 < width){direction_dowm = image[bad_pixel_x + 2][bad_pixel_y];count++;}if (bad_pixel_y - 2 >= 0){direction_right = image[bad_pixel_x][bad_pixel_y - 2];count++;}if (bad_pixel_y + 2 < height){direction_left = image[bad_pixel_x][bad_pixel_y + 2];count++;}sum = direction_up + direction_dowm + direction_right + direction_left;direction_avaerage = sum / count;return direction_avaerage; }? ? ? ? 由于我拿到的RAW圖在sensor出來(lái)的時(shí)候已經(jīng)進(jìn)行過(guò)壞點(diǎn)矯正了,所以和原圖沒(méi)什么區(qū)別(serson一般自帶了ISP)。這是網(wǎng)上搜到的其他矯正方法,感興趣的也可以自己去嘗試以下。
黑電平矯正:
????????由于圖像傳感器中各個(gè)像素元件之間的差異,以及電路噪聲等因素,黑電平的基準(zhǔn)值可能存在一定的偏移。如果不對(duì)黑電平進(jìn)行校正,圖像中的黑色部分可能會(huì)出現(xiàn)灰色或者色偏等不正常的顯示情況。簡(jiǎn)單來(lái)說(shuō)是個(gè)什么情況呢?就是像素傳感器在完全不感光的情況下,會(huì)出現(xiàn)暗電流(漏電)現(xiàn)象,導(dǎo)致在純黑環(huán)境,搜集到的像素值也不為0。所以拍出來(lái)的照片都是在那不為零的基礎(chǔ)上疊加的,發(fā)生了偏移。所以我們要減去這一部分的系數(shù),來(lái)讓照片恢復(fù)正常。通常,這種暗電流的情況和相機(jī)自身的溫度也是由非線性的關(guān)系。而且要拿到該相機(jī)拍的一張純黑的圖作為參照來(lái)處理。但是樓主這里沒(méi)有相關(guān)的圖像和參數(shù),根據(jù)大佬的指導(dǎo),可以用本相片中最暗的一個(gè)像素值作為基準(zhǔn),然后讓整一張圖片偏移這個(gè)值。
暗電流是指在相機(jī)或圖像傳感器沒(méi)有受到光照的情況下,由于材料內(nèi)部自由電子的熱運(yùn)動(dòng)而產(chǎn)生的電流。溫度是影響暗電流的重要因素之一,兩者之間存在一定的關(guān)系。 一般來(lái)說(shuō),溫度越高,暗電流就越大。這是因?yàn)樵诟邷叵?#xff0c;材料內(nèi)部的自由電子熱運(yùn)動(dòng)加劇,導(dǎo)致更多的電子穿過(guò)PN結(jié)并流入電路中。此外,隨著溫度的升高,PN結(jié)的導(dǎo)電性能也會(huì)發(fā)生變化,從而進(jìn)一步影響暗電流的大小。 因此,在進(jìn)行高精度的圖像采集和處理時(shí),需要對(duì)暗電流進(jìn)行校準(zhǔn)和補(bǔ)償,以減少其對(duì)圖像質(zhì)量的影響。同時(shí),也需要注意相機(jī)或圖像傳感器的工作環(huán)境和溫度控制,以保證暗電流的穩(wěn)定性和可控性。
? ? ? ? 以下是這個(gè)簡(jiǎn)單的范例函數(shù):
int Black_Level_Correction(vector<vector<uint16_t>> &image) {int width = image.size(), heigth = image[0].size(), i, j;uint16_t min_pixel = image[0][0];// 第一遍遍歷整個(gè)數(shù)組找到最小的像素值作為黑電平的補(bǔ)償數(shù)for (i = 0; i < width; i++){for (j = 0; j < heigth; j++){if (image[i][j] < min_pixel){min_pixel = image[i][j];}}}// 第二遍遍歷數(shù)組用黑電平補(bǔ)償數(shù)對(duì)圖片每個(gè)像素進(jìn)行補(bǔ)償for (i = 0; i < width; i++){for (j = 0; j < heigth; j++){image[i][j] -= min_pixel;}}return min_pixel; }陰影矯正:
????????陰影矯正是數(shù)字圖像處理中的一項(xiàng)技術(shù),用于去除圖像中的陰影效果,以提高圖像的質(zhì)量和準(zhǔn)確性。陰影效果是指由于光線的遮擋、衰減或反射等原因,在圖像中出現(xiàn)的較暗的區(qū)域。陰影效果會(huì)影響圖像的亮度、對(duì)比度和色彩平衡,降低圖像的可讀性和識(shí)別性。因此,陰影矯正成為數(shù)字圖像處理中的一項(xiàng)重要技術(shù)。 陰影矯正的原理是基于圖像中的光照模型,即光線從光源到物體表面的反射和衰減過(guò)程。光線經(jīng)過(guò)物體表面反射后,會(huì)遵循一定的光照規(guī)律分布到物體表面的不同部位,形成明暗不同的區(qū)域。在陰影區(qū)域中,光線受到遮擋或衰減,導(dǎo)致反射光強(qiáng)度變?nèi)?#xff0c;因此需要對(duì)陰影區(qū)域進(jìn)行矯正。
?網(wǎng)格法
????????鏡頭陰影的漸變曲率從中心到邊緣逐漸增大,增益曲線表現(xiàn)為中心疏,邊緣密。因此將圖像劃分成中間疏、四周密的網(wǎng)格,每個(gè)塊內(nèi)有不同的增益。位于每個(gè)塊內(nèi)的像素點(diǎn)認(rèn)為具有相同的增益值有相同的增益。網(wǎng)格法如下圖所示,該方法能適應(yīng)不同的鏡頭模組,陰影校正效果較好。
同心圓法
????????鏡頭陰影從圖像中心到四周越來(lái)越嚴(yán)重,且基本是呈現(xiàn)中心對(duì)稱的,根據(jù)鏡頭陰影的這個(gè)特點(diǎn),提出了一種鏡頭陰影校正方法,即根據(jù)各像素點(diǎn)與圖像中心的距離R計(jì)算出一個(gè)校正系數(shù)。如下圖所示,該方法簡(jiǎn)單、復(fù)雜度低、占用內(nèi)存少,但是鏡頭裝配過(guò)程復(fù)雜,不存在這種完全對(duì)稱的情況,因此,該方法鏡頭陰影校正的效果一般欠佳,不具有實(shí)際應(yīng)用價(jià)值。
? ? ? ? 同心圓算式增益可以自己安排但是要注意合理性,增益和R(距離)的關(guān)系一定是乘法,而不是加法,因?yàn)槿绻潜緛?lái)是黑色距離再遠(yuǎn),它也是黑色,如果你把本來(lái)是純黑的點(diǎn)變成了其他顏色那就錯(cuò)了,我們?cè)鲆娴氖欠呛谏狞c(diǎn)。最后,要注意溢出和小數(shù)倍的存在。
? ? ? ?為了讓初學(xué)者能有簡(jiǎn)單理解,這里采用的是第二種方法。以下是范例代碼:
void Shadow_Correction(vector<vector<uint16_t>> &image, double compensation) {int width = image.size(), heigth = image[0].size(), arv_x = 0, arv_y = 0, i, j, count = 0, count_1 = 0;float R = 0.0;arv_x = (int)width / 2;arv_y = (int)heigth / 2;for (i = 0; i < width; i++){for (j = 0; j < heigth; j++){// 計(jì)算距離R = (float)abs(sqrt(pow(arv_x - i, 2) + pow(arv_y - j, 2)));if (R > 2800){R = 0;}//如何矯正的值這里是自己決定的公式也是可以自己設(shè)定,甚至可以非線性,具體要看你照片的效果// image[i][j] = (double)pow((1.0+(R/280)/10),3)*image[i][j];image[i][j] = (float)((float)10 + (float)(R / compensation)) * image[i][j];image[i][j] = image[i][j] / 10;if (image[i][j] > 0x3ff){image[i][j] = 0x3ff; // 防止曝光過(guò)度}}} }? ? ? ? ?陰影矯正對(duì)比圖片:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?處理前? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??處理后
? ? ? ??注意:為了大家更直觀的看清楚我這給大家看的是RGB插值后的BMP圖片的效果!
???????? 可以看出處理后,鏡頭附近的陰影消失了很多,整個(gè)鏡頭也顏色鮮艷了,具體要多少參數(shù)自己調(diào)整。但是使用圓心法的缺陷也暴露出來(lái)了,可以看出處理后鏡頭的右下角,以及綠的發(fā)藍(lán)了,亮度過(guò)高了,這也驗(yàn)證了鏡頭的陰影并不是完全的中心對(duì)稱的。而且也能看出處理后上方還是由一些陰影,如果提拉亮度繼續(xù)想要消除陰影,那本來(lái)明亮的地方就會(huì)溢出,過(guò)亮變成白色。所以分區(qū)處理像素亮度是最好的,對(duì)不同區(qū)域進(jìn)行分別處理,但是這樣也相對(duì)復(fù)雜。
以上的多線程版本:
? ? ? ? 上面的雖然便于理解但是,運(yùn)行速度比較慢。樓主寫了個(gè)多線程版本并且把他們集合在一個(gè)函數(shù)里面,但是好像沒(méi)快特別多。并不是線程越多越快,因?yàn)榍袚Q線程和恢復(fù)上下文也是需要時(shí)間了,當(dāng)線程大于一定數(shù)量反而會(huì)更慢。如果大家由改進(jìn)更快的代碼請(qǐng)和樓主分享一下!
void *process_chunk(void *arg) {ThreadArg *args = (ThreadArg *)arg;int start = args->start_row;int end = args->end_row;std::vector<std::vector<uint16_t>> *image = args->image_data;uint16_t min_pixel = args->min_p;int heigth = (*image)[0].size();float R = 0.0;int arv_x = (int)(*image).size() / 2;int arv_y = heigth / 2;for (int i = start; i < end; i++){for (int j = 0; j < heigth; j++){// 黑電平矯正(*image)[i][j] -= min_pixel;// 陰影矯正R = (float)abs(sqrt(pow(arv_x - i, 2) + pow(arv_y - j, 2)));if (R > 2800){R = 0;}(*image)[i][j] = (float)((float)10 + (float)(R / 280.0)) * (*image)[i][j];(*image)[i][j] = (*image)[i][j] / 10;if ((*image)[i][j] > 0x3ff){(*image)[i][j] = 0x3ff; // 防止曝光過(guò)度}}}delete args;pthread_exit(NULL); }void thread_pre_correction(vector<vector<uint16_t>> &image) {int width = image.size(), heigth = image[0].size(), i, j;uint16_t min_pixel;float R = 0.0;int arv_x = (int)width / 2;int arv_y = (int)heigth / 2;min_pixel = seek_bad_Pixel(image); // 壞點(diǎn)矯正并且返回最小值pthread_t *threads = new pthread_t[THREAD_NUM]; // 創(chuàng)建線程數(shù)組int chunk_size = width / THREAD_NUM; // 計(jì)算每個(gè)線程的任務(wù)量for (int t = 0; t < THREAD_NUM; t++){int start = t * chunk_size;int end = (t == THREAD_NUM - 1) ? width : (t + 1) * chunk_size;ThreadArg *args = new ThreadArg;args->start_row = start;args->end_row = end;args->image_data = ℑargs->min_p = min_pixel;pthread_create(&threads[t], nullptr, &process_chunk, args);}for (int t = 0; t < THREAD_NUM; t++){pthread_join(threads[t], nullptr); // 等待所有線程處理完畢}delete[] threads; }RGB插值(RAW域->RGB域):
????????RGB插值是數(shù)字圖像處理中的一種方法,用于將原始圖像中的缺失像素值進(jìn)行估計(jì)和填充。RGB是紅、綠、藍(lán)三種顏色分量的縮寫,對(duì)于一張彩色圖像而言,每個(gè)像素都有三個(gè)分量的值,分別對(duì)應(yīng)紅、綠、藍(lán)三種顏色的亮度值。這里采用最近鄰插值法(Nearest Neighbor Interpolation):將缺失像素的值設(shè)置為最近鄰像素的值。這里生成的格式是RGB888,因?yàn)槭堑褪挥行?#xff0c;這里再次舍去最低位兩個(gè)字節(jié),實(shí)際上是損失精度的。
? ? ? ? 先給一個(gè)簡(jiǎn)單插值范例,自己插自己的值R=G=B得出一張純灰色的圖。
struct Pixel {uint8_t r;uint8_t g;uint8_t b; };vector<vector<Pixel>> color_interpolation_grey(vector<vector<uint16_t>> &image) {int height = image[0].size(), width = image.size();vector<vector<Pixel>> rgbImage(width, vector<Pixel>(height));for (int j = 0; j < width; j++){for (int i = 0; i < height; i++){// 抹干凈高六位image[i][j] = image[i][j] & 0x3FF; // 0000001111111111// 右移兩位image[i][j] = image[i][j] >> 2;rgbImage[i][j].b = image[i][j];rgbImage[i][j].g = image[i][j];rgbImage[i][j].r = image[i][j];}}return rgbImage; }? ? ? ?灰色插值后的圖片:
????注意:為了大家更直觀的看清楚我這給大家看的是RGB插值后的BMP圖片的效果!
????彩色RGB插值,有多種插值方法。因?yàn)槊總€(gè)像素只有一種顏色。比如在RAW格式的圖片中,綠色像素它將缺少紅色,藍(lán)色的值,以此類推。所以我們要補(bǔ)齊他的值,讓每一個(gè)像素都由三種顏色混色在一起,豐富顏色。對(duì)此有多種方法,我給出的代碼是用的相鄰插值法。
? ? ? ?這是樓主用的這張RAW圖的格式(GBRG)。
? ? ? ? 對(duì)于各種顏色的插值:
| 取值自己 | 取值相鄰四個(gè)斜對(duì)角 | 取值相鄰上下左右 |
| 取值相鄰四個(gè)斜對(duì)角 | 取值自己 | 取值相鄰上下左右 |
| 相鄰左右 | 相鄰上下 | 取值自己 |
| 相鄰上下 | 相鄰左右 | 取值自己 |
? ? ? ? 下面給出范例代碼:
vector<vector<Pixel>> color_interpolation(vector<vector<uint16_t>> &image) {int height = image[0].size(), width = image.size();vector<vector<Pixel>> rgbImage(width, vector<Pixel>(height));for (int i = 0; i < width; i++){for (int j = 0; j < height; j++){int Gcount = 0, Rcount = 0, Bcount = 0;bool Green = ((i % 2 == 0) && (j % 2 == 0)) || ((i % 2 == 1) && (j % 2 == 1));//GBRG格式bool Red = (i % 2 == 1) && (j % 2 == 0);bool Blue = (i % 2 == 0) && (j % 2 == 1);uint16_t temporary_R = 0, temporary_G = 0, temporary_B = 0; // 防止溢出16位來(lái)讓八位累加!!if (Green){for (int x = i - 1; x <= i + 1; x++){for (int y = j - 1; y <= j + 1; y++){// 判斷邊界內(nèi)有效數(shù)據(jù)if (x >= 0 && x < width && y >= 0 && y < height && !(x == i && y == j)){if ((i % 2 == 0)) // 偶數(shù)行紅藍(lán)讀取{// 紅色if ((x == i && y == j - 1) || (x == i && y == j + 1)){temporary_R += image[x][y];Rcount++;}// 藍(lán)色if ((x == i - 1 && y == j) || (x == i + 1 && y == j)){temporary_B += image[x][y];Bcount++;}}if ((i % 2 == 1)){// 紅色if ((x == i - 1 && y == j) || (x == i + 1 && y == j)){temporary_R += image[x][y];Rcount++;}// 藍(lán)色if ((x == i && y == j - 1) || (x == i && y == j + 1)){temporary_B += image[x][y];Bcount++;}}}}}rgbImage[i][j].g = (image[i][j] / 1) >> 2;rgbImage[i][j].r = (temporary_R / Rcount) >> 2;rgbImage[i][j].b = (temporary_B / Bcount) >> 2;}if (Blue){for (int x = i - 1; x <= i + 1; x++){for (int y = j - 1; y <= j + 1; y++){if (x >= 0 && x < width && y >= 0 && y < height && !(x == i && y == j)){// 綠色插值上下左右if ((x == i && y == j - 1) || (x == i && y == j + 1) || (x == i - 1 && y == j) || (x == i + 1 && y == j)){temporary_G += image[x][y];Gcount++;}// 對(duì)藍(lán)色四個(gè)角插值if ((x == i - 1 && y == j - 1) || (x == i + 1 && y == j - 1) || (x == i - 1 && y == j + 1) || (x == i + 1 && y == j + 1)){temporary_B += image[x][y];Bcount++;}}}}rgbImage[i][j].g = (temporary_G / (uint16_t)Gcount) >> 2;rgbImage[i][j].r = (image[i][j] / (uint16_t)1) >> 2;rgbImage[i][j].b = (temporary_B / (uint16_t)Bcount) >> 2;}if (Red){for (int x = i - 1; x <= i + 1; x++){for (int y = j - 1; y <= j + 1; y++){if (x >= 0 && x < width && y >= 0 && y < height && !(x == i && y == j)){// 綠色插值上下左右if ((x == i && y == j - 1) || (x == i && y == j + 1) || (x == i - 1 && y == j) || (x == i + 1 && y == j)){temporary_G += image[x][y];Gcount++;}// 對(duì)四角紅色插值if ((x == i - 1 && y == j - 1) || (x == i + 1 && y == j - 1) || (x == i - 1 && y == j + 1) || (x == i + 1 && y == j + 1)){temporary_R += image[x][y];Rcount++;}}}}rgbImage[i][j].g = (temporary_G / (uint16_t)Gcount) >> 2;rgbImage[i][j].r = (temporary_R / (uint16_t)Rcount) >> 2;rgbImage[i][j].b = (image[i][j] / (uint16_t)1) >> 2;}}}return rgbImage; }? ? ? ? 下面是一張RAW圖直RGB接插值后生成BMP的圖片效果:
?????????注意:為了大家更直觀的看清楚我這給大家看的是RGB插值后的BMP圖片的效果!
? ? ? ? 插值成功的話整個(gè)圖片應(yīng)該是偏向于一種顏色,不一定是綠色,這也是正常的。而且還要對(duì)像素觀察一下,如果成功的插值像素都是趨向于一種顏色,綿密的,而不是每個(gè)像素都是分明的。下面是正確插值與錯(cuò)誤插值的像素對(duì)比:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?錯(cuò)誤? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 正確
生成一個(gè)BMP文件:
? ? ? ? BMP是一種通用格式,我們只要把格式頭寫好,然后往里面方RGB數(shù)據(jù)就行,BMP的寫入方式是倒著寫的,請(qǐng)看下面代碼:
void Raw_to_Bmp_Pixel16_Enablelow10(vector<vector<Pixel>> &image, const char *filename) {int width = image.size();int height = image[0].size();FILE *pFile = fopen(filename, "wb");if (!pFile){printf("Error: Unable to open file %s\n", filename);return;}// 54個(gè)字節(jié)的報(bào)頭uint32_t bmpSize = width * height * sizeof(uint16_t) + 54;uint32_t offset = 54;// 14個(gè)字節(jié)的格式頭和40個(gè)字節(jié)的信息頭fwrite("BM", sizeof(char), 2, pFile);fwrite(&bmpSize, sizeof(uint32_t), 1, pFile);fwrite("\0\0\0\0", sizeof(char), 4, pFile);fwrite(&offset, sizeof(uint32_t), 1, pFile);uint32_t headerSize = 40;uint32_t planes = 1;uint32_t bitsPerPixel = 24;uint32_t compression = 0;uint32_t bmpDataSize = width * height * sizeof(uint16_t);uint32_t resolutionX = 0;uint32_t resolutionY = 0;uint32_t colors = 0;uint32_t importantColors = 0;fwrite(&headerSize, sizeof(uint32_t), 1, pFile);fwrite(&width, sizeof(uint32_t), 1, pFile);fwrite(&height, sizeof(uint32_t), 1, pFile);fwrite(&planes, sizeof(uint16_t), 1, pFile);fwrite(&bitsPerPixel, sizeof(uint16_t), 1, pFile);fwrite(&compression, sizeof(uint32_t), 1, pFile);fwrite(&bmpDataSize, sizeof(uint32_t), 1, pFile);fwrite(&resolutionX, sizeof(uint32_t), 1, pFile);fwrite(&resolutionY, sizeof(uint32_t), 1, pFile);fwrite(&colors, sizeof(uint32_t), 1, pFile);fwrite(&importantColors, sizeof(uint32_t), 1, pFile);for (int i = 0; i < height; i++){for (int j = 0; j < width; j++){Pixel pixel = image[height - 1 - i][j];/*1 2 的讀取方式 3 4 1 23 4*/// uint16_t lowTen = pixel.r & 0x3FF; // 0000001111111111fwrite(&pixel.r, sizeof(uint8_t), 1, pFile);fwrite(&pixel.g, sizeof(uint8_t), 1, pFile);fwrite(&pixel.b, sizeof(uint8_t), 1, pFile);}}fclose(pFile); }? ? ? ? 上面給的一些范例圖片就是BMP格式的圖片的,在這里就不多做展示了。
使用libjpeg庫(kù)把RGB轉(zhuǎn)化為JPG格式:
? ? ? ? 這個(gè)庫(kù)怎么下載參考這個(gè)鏈接:https://blog.csdn.net/qq_62815119/article/details/127709812
?記得色彩空間要設(shè)置為3,cinfo.input_components = 3; // R, G, B
?還有輸入的格式要設(shè)置成RGB,?cinfo.in_color_space = JCS_RGB;
void write_jpeg_file(const char* filename, vector<vector<Pixel>>& data, int width, int height, int quality) {struct jpeg_compress_struct cinfo;struct jpeg_error_mgr jerr;FILE* outfile;JSAMPROW row_pointer[1];int row_stride;cinfo.err = jpeg_std_error(&jerr);jpeg_create_compress(&cinfo);if ((outfile = fopen(filename, "wb")) == NULL) {fprintf(stderr, "can't open %s\n", filename);exit(1);}jpeg_stdio_dest(&cinfo, outfile);cinfo.image_width = width;cinfo.image_height = height;cinfo.input_components = 3; // R, G, Bcinfo.in_color_space = JCS_RGB;jpeg_set_defaults(&cinfo);jpeg_set_quality(&cinfo, quality, TRUE);jpeg_start_compress(&cinfo, TRUE);row_stride = width * 3;while (cinfo.next_scanline < cinfo.image_height) {int y = cinfo.next_scanline;row_pointer[0] = &data[y][0].r;jpeg_write_scanlines(&cinfo, row_pointer, 1);}jpeg_finish_compress(&cinfo);fclose(outfile);jpeg_destroy_compress(&cinfo); }? ? ? ? 下面是這次生成的JPG格式圖:
? ? ? ? ?下一期,將分享一下白平衡的具體原理,以及伽馬矯正等其他的一些在YUV或者RGB域要做的處理。如果要代碼,請(qǐng)私信我。
? ? ? ? ? 各位大佬有什么需要補(bǔ)充的,或者糾正我的錯(cuò)誤的請(qǐng)?jiān)谠u(píng)論區(qū)留言,我會(huì)立刻改正。
總結(jié)
以上是生活随笔為你收集整理的LINUX下简单实现ISP图像处理从RAW到RGB,BMP算法、RGB到JPEG库的使用(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Outline for Mac(Mac记
- 下一篇: python opencv实现 tiff