LINUX下简单实现ISP图像处理从RAW到RGB,BMP算法、RGB到JPEG库的使用(一)
? ? ? ?
????????在這里分享一下相關的ISP的一些基本簡單圖像處理算法。在一般的市面上,相關的ISP算法都是非常復雜,且經過不同serson設備的情況進行固定參數并且固化在芯片內來實現。所以硬件ISP的效率會比軟件算法實現的ISP要高,而且后續開發者所要做的事情比較少。但是缺點就是實現邏輯復雜,而且不同設備并不是完全通用。下面,由我來分享一下最近的干貨。
? ? ? ? 這里實現的是ISP的功能是對圖像的處理,以及像素的變化,一般攝像頭都是自帶硬件ISP,所以你拿到的圖片都是經過處理過的。這里是范例講解ISP內部一些簡單的算法實現!在LINUX上對圖片直接變換。分享一些最簡單的方法幫助大家理解處理的流程,有一些處理比較復雜,所以樓主打算下一篇文章再次補上,點名白平衡要跨域處理。
ISP 處理流程:
Bayer、黑電平補償 (black level compensation)、鏡頭矯正(lens shading correction)、壞像素矯正(bad pixel correction)、顏色插值 (demosaic)、Bayer 噪聲去除、 白平衡(AWB) 矯正、 色彩矯正(color correction)、gamma 矯正、色彩空間轉換(RGB 轉換為 YUV)、在YUV 色彩空間上彩噪去除與邊緣加強、色彩與對比度加強,中間還要進行自動曝光控制等, 然后輸出 YUV(或者RGB) 格式的數據, 再通過 I/O 接口傳輸到 CPU 中處理。
? ? ? ? 首先我們要了解一些圖片的格式:
? ? ? ? 這篇博客主要處理RAW到RGB888的一些步驟,上面介紹的圖像格式中常見的PNG,JPG(JPEG)圖像格式是進行壓縮編碼過后的圖片,PNG一般能壓縮到原像素大小圖片的一半左右,還多出了透明度。JPG格式甚至能壓縮到原圖的百分之二三十。BMP是沒有被壓縮的圖片,不過他經過了色域轉換。RAW格式就是一張灰階圖,它沒經過色彩空間的變換,只是一張每個像素記錄灰度值的圖片。一般有RAW10,RAW8,RAW12,也就是每個像素兩個字節,里面高位或者低位的前后10,8,12個位是有效位。
? ? ? ? 過程一般是RAW轉化為RGB再轉化為BMP,或者RGB再次轉化為YUV格式(這種格式方便傳輸)。也可以由RGB轉化為YCrCb后再變化為JPEG(過程復雜),或者RGB數據解碼為像素數據,再變為PNG。
? ? ? ? 在寫代碼之前我們要弄懂什么叫RAW的顏色和灰階(灰度)。在RAW圖中每個像素實際上都是代表了一種顏色。一般情況下主要有這四種分布情況,這是我們直接拿出四個格子的像素顏色作為參考(例如第一種我們把他叫做GRBG格式的bayer圖),以此類推。每個我們看見的BMP彩色照片每個像素都是由三種顏色RGB組成的,但是bayer圖每個像素只有一種顏色,所以放大來看就是和下面的圖一樣,但是如果一個像素包含三種顏色RGB為例,那他就不是單純的紅、綠、藍,而是三種顏色的混色。RAW格式是沒有混色的格式圖片,它每個像素只有一種顏色。每個顏色的值代表著這個顏色的深淺度。
? ? ? ? 寫代碼前一定要注意!!!溢出!!溢出!!對于十六位加法要很多像素相加的最好用六十四位作為total值!出現什么問題先排除是不是溢出問題!!!還有就是計算無符號數是沒有小數的,比如說陰影矯正如果沒有小數,就會出現每一圈亮度波紋的沒有過度(因為都是整數倍從1直接跳到2,缺少了1和2之間的倍數圖像就會變化的很突兀)看起來亮度就是一圈一圈的。解決這種方法最好就是變大十倍乃至一百倍相乘運算后再次除以十倍乃至一百倍,這樣就能解決無符號數不能計算小數的問題!!!
讀取一張RAW格式圖片:
? ? ? ? 這里不詳細說明其他,只說明對RAW到RGB,RGB到BMP,以及RGB使用libjpeg轉化為jpg格式的過程。這里樓主用的是一張5600*5600像素的低十位有效的RAW圖。記得注意你當前環境的大小端,低位是前十位還是后十位,如果和我不一樣得要把大小端顛倒過來計算。這里提供一個轉換函數:
// 大小端轉換,寫成內聯函數效率高 inline uint16_t swap_endian(uint16_t num) {return (num >> 8) | (num << 8); }? ? ? ? 首先第一步,再Linux下我們要對RAW文件進行讀取。(這里讀取后我直接把他轉成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; }? ? ? ? 兩個字節兩個字節大小的讀取,把讀到的數據放到一個5600*5600的二維向量中。這里用向量有個好處,就是可以忽略數組的越界問題。雖然上面的讀取簡單,但是響應速度比較慢讀一次五六秒,對于一個圖像處理芯片來說肯定是越快越好,所以我對其進行了優化,采用的多線程讀取。下面是多線程讀取raw格式文件部分主要的代碼,這是所有線程都要用到的結構體:
#define THREAD_NUM 24 // 定義線程數 #define PIXEL_SIZE 2 // 定義每個像素占用的字節數為2字節// 線程傳遞消息結構體 struct ThreadArg {int start_row; // 線程處理的起始行號int end_row; // 線程處理的結束行號int image_width; // 圖像的寬度int image_height; // 圖像高度const char *filename; // 文件名vector<vector<uint16_t>> *image_data; // 傳入的圖像數組vector<vector<Pixel>> *rgb_data; // 傳入的圖像數組uint16_t min_p; // 最小值參數// ThreadArg() = default; // 默認構造函數 };? ? ? ? 然后就是線程讀取函數,和線程函數的代碼:
void *read_image(void *arg) {ThreadArg *thread_arg = (ThreadArg *)arg; // 將傳遞給該線程的參數強制轉換為 ThreadArg 指針int start_row = thread_arg->start_row; // 獲取該線程需要處理的起始行號和結束行號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++) // 循環讀取該線程需要處理的所有行{vector<uint16_t> row_data(image_width); // 創建一個臨時的一維矢量,用于存儲當前行的數據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]; // 創建多個線程,每個線程讀取圖像的一部分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++) // 計算該線程需要處理的起始行號和結束行號{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]); // 創建線程并啟動}// 處理最后一個線程需要處理的行數可能不足 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]); // 創建線程并啟動for (i = 0; i < THREAD_NUM; i++) // 等待所有線程執行完畢{pthread_join(threads[i], NULL);}vector<vector<uint16_t>> result(*image_data); // 將指針指向的二維矢量復制到一個新的二維矢量中delete image_data;delete[] thread_args;return result; }? ? ? ?這是讀取到的一張圖片:
? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ??RAW格式原圖? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ?RAW格式放大? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? 放大后清晰的看見一張RAW格式的圖是由紅綠藍三色組成。
壞點矯正:
? ? ? ??壞點是指在數字圖像中出現的一些不正常的像素點,它們可能是傳感器損壞、傳輸過程中出現的錯誤或其他原因引起的。壞點的存在會影響圖像的質量和準確性,因此需要進行壞點處理。比如說serson上某個感光傳感器損壞,導致拍出的那一個點的像素缺失。
? ? ? ? 樓主本人對壞點處理的方法是判斷每個像素是否低于相鄰像素的平均值0x80(這個值是看自己設置)可以調成自己合適的參數。
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];// 尋找壞點for (int i = 0; i < width; i++){for (int j = 0; j < height; j++){// 判斷壞點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出來的時候已經進行過壞點矯正了,所以和原圖沒什么區別(serson一般自帶了ISP)。這是網上搜到的其他矯正方法,感興趣的也可以自己去嘗試以下。
黑電平矯正:
????????由于圖像傳感器中各個像素元件之間的差異,以及電路噪聲等因素,黑電平的基準值可能存在一定的偏移。如果不對黑電平進行校正,圖像中的黑色部分可能會出現灰色或者色偏等不正常的顯示情況。簡單來說是個什么情況呢?就是像素傳感器在完全不感光的情況下,會出現暗電流(漏電)現象,導致在純黑環境,搜集到的像素值也不為0。所以拍出來的照片都是在那不為零的基礎上疊加的,發生了偏移。所以我們要減去這一部分的系數,來讓照片恢復正常。通常,這種暗電流的情況和相機自身的溫度也是由非線性的關系。而且要拿到該相機拍的一張純黑的圖作為參照來處理。但是樓主這里沒有相關的圖像和參數,根據大佬的指導,可以用本相片中最暗的一個像素值作為基準,然后讓整一張圖片偏移這個值。
暗電流是指在相機或圖像傳感器沒有受到光照的情況下,由于材料內部自由電子的熱運動而產生的電流。溫度是影響暗電流的重要因素之一,兩者之間存在一定的關系。 一般來說,溫度越高,暗電流就越大。這是因為在高溫下,材料內部的自由電子熱運動加劇,導致更多的電子穿過PN結并流入電路中。此外,隨著溫度的升高,PN結的導電性能也會發生變化,從而進一步影響暗電流的大小。 因此,在進行高精度的圖像采集和處理時,需要對暗電流進行校準和補償,以減少其對圖像質量的影響。同時,也需要注意相機或圖像傳感器的工作環境和溫度控制,以保證暗電流的穩定性和可控性。
? ? ? ? 以下是這個簡單的范例函數:
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];// 第一遍遍歷整個數組找到最小的像素值作為黑電平的補償數for (i = 0; i < width; i++){for (j = 0; j < heigth; j++){if (image[i][j] < min_pixel){min_pixel = image[i][j];}}}// 第二遍遍歷數組用黑電平補償數對圖片每個像素進行補償for (i = 0; i < width; i++){for (j = 0; j < heigth; j++){image[i][j] -= min_pixel;}}return min_pixel; }陰影矯正:
????????陰影矯正是數字圖像處理中的一項技術,用于去除圖像中的陰影效果,以提高圖像的質量和準確性。陰影效果是指由于光線的遮擋、衰減或反射等原因,在圖像中出現的較暗的區域。陰影效果會影響圖像的亮度、對比度和色彩平衡,降低圖像的可讀性和識別性。因此,陰影矯正成為數字圖像處理中的一項重要技術。 陰影矯正的原理是基于圖像中的光照模型,即光線從光源到物體表面的反射和衰減過程。光線經過物體表面反射后,會遵循一定的光照規律分布到物體表面的不同部位,形成明暗不同的區域。在陰影區域中,光線受到遮擋或衰減,導致反射光強度變弱,因此需要對陰影區域進行矯正。
?網格法
????????鏡頭陰影的漸變曲率從中心到邊緣逐漸增大,增益曲線表現為中心疏,邊緣密。因此將圖像劃分成中間疏、四周密的網格,每個塊內有不同的增益。位于每個塊內的像素點認為具有相同的增益值有相同的增益。網格法如下圖所示,該方法能適應不同的鏡頭模組,陰影校正效果較好。
同心圓法
????????鏡頭陰影從圖像中心到四周越來越嚴重,且基本是呈現中心對稱的,根據鏡頭陰影的這個特點,提出了一種鏡頭陰影校正方法,即根據各像素點與圖像中心的距離R計算出一個校正系數。如下圖所示,該方法簡單、復雜度低、占用內存少,但是鏡頭裝配過程復雜,不存在這種完全對稱的情況,因此,該方法鏡頭陰影校正的效果一般欠佳,不具有實際應用價值。
? ? ? ? 同心圓算式增益可以自己安排但是要注意合理性,增益和R(距離)的關系一定是乘法,而不是加法,因為如果是本來是黑色距離再遠,它也是黑色,如果你把本來是純黑的點變成了其他顏色那就錯了,我們增益的是非黑色的點。最后,要注意溢出和小數倍的存在。
? ? ? ?為了讓初學者能有簡單理解,這里采用的是第二種方法。以下是范例代碼:
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++){// 計算距離R = (float)abs(sqrt(pow(arv_x - i, 2) + pow(arv_y - j, 2)));if (R > 2800){R = 0;}//如何矯正的值這里是自己決定的公式也是可以自己設定,甚至可以非線性,具體要看你照片的效果// 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; // 防止曝光過度}}} }? ? ? ? ?陰影矯正對比圖片:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?處理前? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??處理后
? ? ? ??注意:為了大家更直觀的看清楚我這給大家看的是RGB插值后的BMP圖片的效果!
???????? 可以看出處理后,鏡頭附近的陰影消失了很多,整個鏡頭也顏色鮮艷了,具體要多少參數自己調整。但是使用圓心法的缺陷也暴露出來了,可以看出處理后鏡頭的右下角,以及綠的發藍了,亮度過高了,這也驗證了鏡頭的陰影并不是完全的中心對稱的。而且也能看出處理后上方還是由一些陰影,如果提拉亮度繼續想要消除陰影,那本來明亮的地方就會溢出,過亮變成白色。所以分區處理像素亮度是最好的,對不同區域進行分別處理,但是這樣也相對復雜。
以上的多線程版本:
? ? ? ? 上面的雖然便于理解但是,運行速度比較慢。樓主寫了個多線程版本并且把他們集合在一個函數里面,但是好像沒快特別多。并不是線程越多越快,因為切換線程和恢復上下文也是需要時間了,當線程大于一定數量反而會更慢。如果大家由改進更快的代碼請和樓主分享一下!
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; // 防止曝光過度}}}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); // 壞點矯正并且返回最小值pthread_t *threads = new pthread_t[THREAD_NUM]; // 創建線程數組int chunk_size = width / THREAD_NUM; // 計算每個線程的任務量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插值是數字圖像處理中的一種方法,用于將原始圖像中的缺失像素值進行估計和填充。RGB是紅、綠、藍三種顏色分量的縮寫,對于一張彩色圖像而言,每個像素都有三個分量的值,分別對應紅、綠、藍三種顏色的亮度值。這里采用最近鄰插值法(Nearest Neighbor Interpolation):將缺失像素的值設置為最近鄰像素的值。這里生成的格式是RGB888,因為是低十位有效,這里再次舍去最低位兩個字節,實際上是損失精度的。
? ? ? ? 先給一個簡單插值范例,自己插自己的值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插值,有多種插值方法。因為每個像素只有一種顏色。比如在RAW格式的圖片中,綠色像素它將缺少紅色,藍色的值,以此類推。所以我們要補齊他的值,讓每一個像素都由三種顏色混色在一起,豐富顏色。對此有多種方法,我給出的代碼是用的相鄰插值法。
? ? ? ?這是樓主用的這張RAW圖的格式(GBRG)。
? ? ? ? 對于各種顏色的插值:
| 取值自己 | 取值相鄰四個斜對角 | 取值相鄰上下左右 |
| 取值相鄰四個斜對角 | 取值自己 | 取值相鄰上下左右 |
| 相鄰左右 | 相鄰上下 | 取值自己 |
| 相鄰上下 | 相鄰左右 | 取值自己 |
? ? ? ? 下面給出范例代碼:
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位來讓八位累加!!if (Green){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 ((i % 2 == 0)) // 偶數行紅藍讀取{// 紅色if ((x == i && y == j - 1) || (x == i && y == j + 1)){temporary_R += image[x][y];Rcount++;}// 藍色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++;}// 藍色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++;}// 對藍色四個角插值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++;}// 對四角紅色插值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圖片的效果!
? ? ? ? 插值成功的話整個圖片應該是偏向于一種顏色,不一定是綠色,這也是正常的。而且還要對像素觀察一下,如果成功的插值像素都是趨向于一種顏色,綿密的,而不是每個像素都是分明的。下面是正確插值與錯誤插值的像素對比:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?錯誤? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 正確
生成一個BMP文件:
? ? ? ? BMP是一種通用格式,我們只要把格式頭寫好,然后往里面方RGB數據就行,BMP的寫入方式是倒著寫的,請看下面代碼:
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個字節的報頭uint32_t bmpSize = width * height * sizeof(uint16_t) + 54;uint32_t offset = 54;// 14個字節的格式頭和40個字節的信息頭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庫把RGB轉化為JPG格式:
? ? ? ? 這個庫怎么下載參考這個鏈接:https://blog.csdn.net/qq_62815119/article/details/127709812
?記得色彩空間要設置為3,cinfo.input_components = 3; // R, G, B
?還有輸入的格式要設置成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域要做的處理。如果要代碼,請私信我。
? ? ? ? ? 各位大佬有什么需要補充的,或者糾正我的錯誤的請在評論區留言,我會立刻改正。
總結
以上是生活随笔為你收集整理的LINUX下简单实现ISP图像处理从RAW到RGB,BMP算法、RGB到JPEG库的使用(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Outline for Mac(Mac记
- 下一篇: [linux虚拟机] 使用yum命令时,