V4L2影象採集+圖片格式轉換(YUYV、RGB、JPEG)
本篇轉自博友https://blog.csdn.net/xuyangwyw/article/details/40476653文章,感謝分享。
廢話不多說,直接開始流程。
1、驅動支援
在那位法國牙醫的無私奉獻下,Linux核心幾乎支援所有的USB攝像頭,不過要想自己的Linux核心支援USB免驅攝像頭,還需要先配置核心,Device Drivers --->
<*> Multimedia support --->
<*> Video For Linux
[ ] Enable Video For Linux API 1 (DEPRECATED)
[*] Video capture adapters --->
[*] V4L USB devices --->
<*> USB Video Class (UVC)
[*] UVC input events device support這樣在板子上插入攝像頭後終端就會有顯示:
[[email protected] /]# usb 1-1.1: new full speed USB device using s3c2410-ohci and a ddress 4
uvcvideo: Found UVC 1.00 device Webcam C110 (046d:0829)
input: Webcam C110 as /class/input/input2同時輸入命令:lsusb 也會有相應資訊,在此不就不詳細展開了,網上有很多資料。最主要的是此時進入/dev 目錄下,ls 會新增加一個裝置,我的是video0,不同情況下需自己確認,這個裝置名很重要。至此,Linux核心對攝像頭的驅動支援就沒問題了。
2、開始操作攝像頭
經典操作v4l2的方法一共也就那麼步,大致為:開啟裝置->檢視裝置功能->設定圖片格式->申請幀緩衝->記憶體對映->幀緩衝入列->開始採集->讀資料(包括處理資料)->幀緩衝重新入列->關閉裝置。看著名字挺霸氣的,其實每一步都是呼叫核心驅動提供的出來的介面就可以了。
2.1 開啟裝置
fd = open(dev_name, O_RDWR, 0 );//開啟裝置檔案,阻塞模式 if (fd < 0){ perror("open /dev/video0 fialed! "); return -1; }
開啟一個open就OK了,注意此處用的是阻塞模式,如果是非阻塞模式(O_NONBLOCK)的話,即使攝像頭尚未捕獲到資訊,驅動依舊會把快取(DQBUFF)裡的東西返回給應用程式,感覺這樣有點不合理,也懂核心為何要這樣設計。
2.2 檢視裝置功能
struct v4l2_capability cap; ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);//檢視裝置功能 if (ret < 0) { perror("requre VIDIOC_QUERYCAP fialed! \n"); return -1; } printf("driver:%s\n",cap.driver); printf("card:%s\n",cap.card); printf("bus_info:%s\n",cap.bus_info); printf("version:%d\n",cap.version); printf("capabilities:%x\n",cap.capabilities); if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE) { printf("Device %s: supports capture.\n",dev_name); } if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING) { printf("Device %s: supports streaming.\n",dev_name); }
檢視裝置功能也沒什麼好說的,看程式碼就OK啦。
2.3 設定圖片格式
struct v4l2_format fmt; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = WIDTH; fmt.fmt.pix.height = HEIGHT; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; if(-1 == ioctl(fd, VIDIOC_S_FMT, &fmt)){//設定圖片格式 perror("set format failed!"); return -1; } if(-1 == ioctl(fd, VIDIOC_G_FMT, &fmt)){//得到圖片格式 perror("set format failed!"); return -1; } printf("fmt.type:\t\t%d\n",fmt.type); printf("pix.pixelformat:\t%c%c%c%c\n", \ fmt.fmt.pix.pixelformat & 0xFF,\ (fmt.fmt.pix.pixelformat >> 8) & 0xFF, \ (fmt.fmt.pix.pixelformat >> 16) & 0xFF,\ (fmt.fmt.pix.pixelformat >> 24) & 0xFF); printf("pix.width:\t\t%d\n",fmt.fmt.pix.width); printf("pix.height:\t\t%d\n",fmt.fmt.pix.height); printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);
也是一個命令就完成:VIDIOC_S_FMT,其中WIDTH,HEGHT 是定義的巨集,後面很多地方都要用這兩個引數,定義成巨集比傳參方便。V4L2_PIX_FMT_YUYV 指定輸出格式為YUYV,關於YUYV,RGB等等什麼什麼格式,網上也有很詳細的介紹,比如這篇:談談RGB、YUY2、YUYV、YVYU、UYVY、AYUV。
當然,圖片格式設定也不只這3個,還有像幀率什麼的也是可以設定的。
需要注意的是,對於不用的攝像頭,核心有不一樣的支援,並不是你設定了就一定能用,如果核心中該視訊裝置驅動不支援你所設定的影象格式,視訊驅動會重新修改struct v4l2_format結構體變數的值為該視訊裝置所支援的影象格式,所以在程式設計中,設定完所有的視訊格式後,要獲取實際的視訊格式,要重新讀取 struct v4l2_format結構體變數。
2.3.1 檢視圖片格式
if(-1 == ioctl(fd, VIDIOC_G_FMT, &fmt)){//得到圖片格式 perror("set format failed!"); return -1; } printf("fmt.type:\t\t%d\n",fmt.type); printf("pix.pixelformat:\t%c%c%c%c\n", \ fmt.fmt.pix.pixelformat & 0xFF,\ (fmt.fmt.pix.pixelformat >> 8) & 0xFF, \ (fmt.fmt.pix.pixelformat >> 16) & 0xFF,\ (fmt.fmt.pix.pixelformat >> 24) & 0xFF); printf("pix.width:\t\t%d\n",fmt.fmt.pix.width); printf("pix.height:\t\t%d\n",fmt.fmt.pix.height); printf("pix.field:\t\t%d\n",fmt.fmt.pix.field); <span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;"> </span>
對於我們組的攝像頭來說,設定的WIDTH=320,HEGHT=240,但是重新讀取圖片格式後,得到的卻是 WIDTH=176,HEGHT=144,核心只支援這樣,沒辦法......
2.4 申請幀緩衝
req.count = 4;//申請緩衝數量 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; ioctl(fd, VIDIOC_REQBUFS, &req);//申請緩衝 if (req.count < 2) { perror("buffer memory is Insufficient! \n"); return -1; }
需要注意的是設定申請幀緩衝數量=4,但不一定就一定有4,一般要求幀緩衝數量大於2,因此有個判斷。測試過最多能申請的數量為23。
2.5 對映使用者空間
yuyv_buffers0 = calloc(req.count, sizeof(*yuyv_buffers0));//記憶體中建立對應空間 for (n_buffers = 0; n_buffers < req.count; ++n_buffers){ struct v4l2_buffer buf;//驅動中的一幀 CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = n_buffers; if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf)){//對映使用者空間 perror("VIDIOC_QUERYBUF error!\n"); return -1; } yuyv_buffers0[n_buffers].length = buf.length; yuyv_buffers0[n_buffers].start =(char*) mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (MAP_FAILED == yuyv_buffers0[n_buffers].start){ close(fd); perror("mmap faild! \n"); return -1; } printf("Frame buffer %d: address = 0x%x, length = %d \n",req.count, (unsigned int)yuyv_buffers0[n_buffers].start, yuyv_buffers0[n_buffers].length); }
2.6 申請到的緩衝進入佇列
for (i=0; i<n_buffers; ++i) { struct v4l2_buffer buf; CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; //申請到的緩衝進入佇列 if ( -1 == ioctl(fd, VIDIOC_QBUF, &buf)) { close(fd); perror("VIDIOC_QBUF failed! \n"); return -1; } }
2.7 開始捕捉影象資料
type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //開始捕捉影象資料 if (-1 == ioctl(fd, VIDIOC_STREAMON, &type)) { close(fd); perror("VIDIOC_STREAMON failed! "); exit(-1); }
函式執行成功後,攝像頭開始採資料,一般來說可以用一個select判斷一幀視訊資料是否採集完成,當視訊裝置驅動完成一幀視訊資料採集並儲存到視訊緩衝區中時,select函式返回,應用程式接著可以讀取視訊資料;否則select函式阻塞直到視訊資料採集完成。
enum v4l2_buf_type type; type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fd_set fds; struct timeval tv; int r; FD_ZERO(&fds);//將指定檔案描述符集清空 FD_SET(fd, &fds);//在檔案描述符集合中增加一個新的檔案描述符 tv.tv_sec = 2;//time out tv.tv_usec = 0; r = select(fd+1, &fds, NULL, NULL, &tv);//判斷攝像頭是否準備好,tv是定時 if(-1 == r){ if(EINTR == errno){ printf("select erro! \n"); } } else if(0 == r){ printf("select timeout! \n");//超時 return 1; //exit(EXIT_FAILURE); } read_frame(); //處理一幀資料
2.8 讀資料
file_fd = fopen(path1, "w");//yuyv圖片 if (file_fd < 0){ perror("open test_mmap.jpg fialed! \n"); exit(-1); } CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd, VIDIOC_DQBUF, &buf);//出列採集的幀緩衝,成功返回0 //將攝像頭採集得到的yuyv資料寫入檔案中 if(ret == 0) { ret = fwrite(yuyv_buffers0[buf.index].start, yuyv_buffers0[buf.index].length, 1,file_fd); }
這裡有個幀緩衝出列的概念,也就是讀取裡面的資料並儲存置檔案中,不過儲存的檔案是yuyv圖片,直接開啟自然是不行的,需要用工具YUVViewer.exe,並且工具軟體裡面的引數配置也必須符合你設定的圖片格式,這樣才能看到真正的效果。到此為止,恭喜你,已經成功的邁出第一步了。是的,你沒聽錯,才第一步,一張圖片要直接在LCD上顯示,要遠端傳送,是不能直接用yuyv資料的,還得經過一系列轉換。
2.9 幀緩衝入列ret = ioctl(fd, VIDIOC_QBUF,&buf);//幀緩衝入列 if(0 != ret){ printf("VIDIOC_QBUF failed!\n"); exit(-1); }
讀取完幀緩衝裡面的資料,別忘了將其入列,方便下次使用。
2.10 關閉裝置
static void v4l2_close(void) { int i=0; for(i=0; i<n_buffers; ++i){ if(-1 == munmap(yuyv_buffers0[i].start, yuyv_buffers0[i].length)){ printf("munmap error! \n"); exit(-1); } } close(fd); exit(EXIT_SUCCESS); }
關閉裝置一個close就可以了,不過需要注意的是還有一個解除記憶體對映的工作需要完成。
3.1 yuyv 轉RGB
以上2.1 - 2.10步驟 只是完成一幀資料採集的過程,實際運用中我們需要的是攝像頭不停的工作,同時還要對資料進行轉換等工作,因此,2.1 - 2.7步只需初始化一次就OK,使用VIDIOC_STREAMON,開始採集資料後2.8,.29要不停地迴圈進行,最後才是2.10,關閉裝置。
資料的轉換等處理主要集中在2.8步,讀出資料後就進行相應的轉換。
首先是完成yuyv轉RGB。void yuyv_to_rgb(unsigned char* yuv,unsigned char* rgb) { unsigned int i; unsigned char* y0 = yuv + 0; unsigned char* u0 = yuv + 1; unsigned char* y1 = yuv + 2; unsigned char* v0 = yuv + 3; unsigned char* r0 = rgb + 0; unsigned char* g0 = rgb + 1; unsigned char* b0 = rgb + 2; unsigned char* r1 = rgb + 3; unsigned char* g1 = rgb + 4; unsigned char* b1 = rgb + 5; float rt0 = 0, gt0 = 0, bt0 = 0, rt1 = 0, gt1 = 0, bt1 = 0; for(i = 0; i <= (WIDTH * HEIGHT) / 2 ;i++) { bt0 = 1.164 * (*y0 - 16) + 2.018 * (*u0 - 128); gt0 = 1.164 * (*y0 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128); rt0 = 1.164 * (*y0 - 16) + 1.596 * (*v0 - 128); bt1 = 1.164 * (*y1 - 16) + 2.018 * (*u0 - 128); gt1 = 1.164 * (*y1 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128); rt1 = 1.164 * (*y1 - 16) + 1.596 * (*v0 - 128); if(rt0 > 250) rt0 = 255; if(rt0< 0) rt0 = 0; if(gt0 > 250) gt0 = 255; if(gt0 < 0) gt0 = 0; if(bt0 > 250) bt0 = 255; if(bt0 < 0) bt0 = 0; if(rt1 > 250) rt1 = 255; if(rt1 < 0) rt1 = 0; if(gt1 > 250) gt1 = 255; if(gt1 < 0) gt1 = 0; if(bt1 > 250) bt1 = 255; if(bt1 < 0) bt1 = 0; *r0 = (unsigned char)rt0; *g0 = (unsigned char)gt0; *b0 = (unsigned char)bt0; *r1 = (unsigned char)rt1; *g1 = (unsigned char)gt1; *b1 = (unsigned char)bt1; yuv = yuv + 4; rgb = rgb + 6; if(yuv == NULL) break; y0 = yuv; u0 = yuv + 1; y1 = yuv + 2; v0 = yuv + 3; r0 = rgb + 0; g0 = rgb + 1; b0 = rgb + 2; r1 = rgb + 3; g1 = rgb + 4; b1 = rgb + 5; } }
出列幀緩衝後就可以呼叫該函式,rgb 需要先開闢大小為WIDTH * HEIGTH * 3的空間,因為我們用的RGB是24位格式,3個位元組分別代表一個畫素點的R、G、B,根據公式轉換就好了。
這裡解釋一下為什麼我們要定義:float rt0 = 0, gt0 = 0, bt0 = 0, rt1 = 0, gt1 = 0, bt1 = 0; for 開始後的前6個公式是對2個畫素點的轉換,結果是浮點數,而且也會大於255,小於0,但是每個畫素點的每一位範圍又只有0到255, 因此緊跟著有6個if判斷,做一個範圍判定。網上有些程式是直接用unsigned char 型變數做變換計算,完了也有個判斷,但是感覺這樣的話因為unsigned char都直接帶強制轉換了,那麼後面的if根本就不會執行,不過怎樣選擇大家可以自己考慮。同時我們的if判斷還有個修正,顏色大於250的都直接置為255,這樣出來的效果明顯要好一點,但是不同的攝像頭,結果肯定有不一樣,請慎重考慮,多實踐。
說到這裡就不得不吐槽一下我們組領的那個攝像頭了,羅技的,小巧,效果還不錯,但是,別人家的資料出來的顏色排列的是RGB,我們的出來的排列偏偏是BGR,BGR!!!還半天沒反應過來!!!!!!!3.2 RGB 轉BMP
void rgb_to_bmp(unsigned char* pdata, FILE* bmp_fd) { //分別為rgb資料,要儲存的bmp檔名 int size = WIDTH * HEIGHT * 3 * sizeof(char); // 每個畫素點3個位元組 // 點陣圖第一部分,檔案資訊 BMPFILEHEADER_T bfh; bfh.bfType = (unsigned short)0x4d42; //bm bfh.bfSize = size // data size + sizeof( BMPFILEHEADER_T ) // first section size + sizeof( BMPINFOHEADER_T ) // second section size ; bfh.bfReserved1 = 0; // reserved bfh.bfReserved2 = 0; // reserved bfh.bfOffBits = sizeof( BMPFILEHEADER_T )+ sizeof( BMPINFOHEADER_T );//真正的資料的位置 // printf("bmp_head== %ld\n", bfh.bfOffBits); // 點陣圖第二部分,資料資訊 BMPINFOHEADER_T bih; bih.biSize = sizeof(BMPINFOHEADER_T); bih.biWidth = WIDTH; bih.biHeight = -HEIGHT;//BMP圖片從最後一個點開始掃描,顯示時圖片是倒著的,所以用-height,這樣圖片就正了 bih.biPlanes = 1;//為1,不用改 bih.biBitCount = 24; bih.biCompression = 0;//不壓縮 bih.biSizeImage = size; bih.biXPelsPerMeter = 0;//畫素每米 bih.biYPelsPerMeter = 0; bih.biClrUsed = 0;//已用過的顏色,為0,與bitcount相同 bih.biClrImportant = 0;//每個畫素都重要 fwrite( &bfh, 8, 1, bmp_fd); fwrite(&bfh.bfReserved2, sizeof(bfh.bfReserved2), 1, bmp_fd); fwrite(&bfh.bfOffBits, sizeof(bfh.bfOffBits), 1, bmp_fd); fwrite(&bih, sizeof(BMPINFOHEADER_T), 1, bmp_fd); fwrite(pdata, size, 1, bmp_fd); }
說是轉,其實就是一個另存為,只是加個格式頭而已,需要注意的也只有size, biWidth, biHeight 這幾個引數而已,如果你發現你生成的BMP圖是倒立的,改下bih.biHeight = -HEIGHT就OK了。
需要注意的是對於我們組來說儲存點陣圖只是一個測試方法而已,因為這個時候LCD模組還未準備好,儲存點陣圖就可以直接在windows上檢視結果了,也不需要再用YUVViewer.exe去檢視,很方便。但是由於檔案操作比較耗時,每讀取一幀資料就要操作一次檔案的話,幀率下降也非常明顯,加上儲存點陣圖對於我們這個專案來說明顯沒什麼必要,因此後期的程式中rgb_to_bmp()函式的呼叫是註釋掉了的 ,特此宣告,免得挨磚頭。3.3 RGB放縮演算法
前面說過,我們的攝像頭出來的影象解析度只有176 * 144大小,核心自己優化成這樣,花了一下午時間研究核心中的有關v4l2解析度設定的原始碼,奈何自己太菜了,豈是原始碼的對手?還是老老實實用軟體實現圖片放大吧。
影象放縮演算法有很多種,各有優點,也難免有各自的缺點,要選擇最合適的,實踐才是好辦法。我們組使用的是最臨近插值演算法。關於影象放縮演算法的討論,這裡又有一篇資料:影象放大演算法,這位博主好像也是轉載的,那誰原創的呢?void rgb_stretch(char* src_buf, char* dest_buf, int des_width, int des_hight) { //最臨近插值演算法 //雙線性內插值演算法放大後馬賽克很嚴重 而且幀率下降嚴重 printf("des_width = %d, des_hight = %d \n ",des_width, des_hight); double rate_w = (double) WIDTH / des_width;//橫向放大比 double rate_h = (double) HEIGHT / des_hight;//軸向放大比 int dest_line_size = ((des_width * BITCOUNT +31) / 32) * 4; int src_line_size = BITCOUNT * WIDTH / 8; int i = 0, j = 0, k = 0; for (i = 0; i < des_hight; i++)//desH 目標高度 { //選取最鄰近的點 int t_src_h = (int)(rate_h * i + 0.5);//rateH (double)srcH / desH; for (j = 0; j < des_width; j++)//desW 目標寬度 { int t_src_w = (int)(rate_w * j + 0.5); memcpy(&dest_buf[i * dest_line_size] + j * BITCOUNT / 8, \ &src_buf[t_src_h * src_line_size] + t_src_w * BITCOUNT / 8,\ BITCOUNT / 8); } } }
也嘗試了雙線性內插值演算法,從原理上分析,應該是雙線性內插值演算法效果更好,結果卻不是這樣,為什麼是這樣,我這種菜鳥也弄不明白......
放大後的資料就適合320 * 240 的LCD屏了,LCD顯示的話,直接將RGB資料放入LCD的幀緩衝就OK了,這裡也不詳說,畢竟是另一位組員的勞動成果嘛。有了Linux系統,就是方便啊,再也不用苦逼的去寫裸機驅動了。
這裡補充一個第2.8步的詳細呼叫:int numb = 0; static int read_frame(char *rgb_buffers) { struct v4l2_buffer buf; int ret =0; static char path1[30]; static char path2[30]; FILE *file_fd;//yuyv 圖片檔案流 FILE *bmp_fd;//bmp 圖片檔案流 numb ++; sprintf(path1, "./test_mmap%d.jpg", numb);//檔名 sprintf(path2, "./image%d.bmp", numb); printf("path1=%s, path2=%s %d\n", path1, path2, numb); file_fd = fopen(path1, "w");//yuyv圖片 if (file_fd < 0){ perror("open test_mmap.jpg fialed! \n"); exit(-1); } bmp_fd = fopen(path2, "w");//bmp圖片 if (bmp_fd < 0){ perror("open image.bmp failed!"); exit(-1); } CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd, VIDIOC_DQBUF, &buf);//出列採集的幀緩衝,成功返回0 if(0 != ret){ printf("VIDIOC_DQBUF failed!\n"); exit(-1); } yuyv_to_rgb(yuyv_buffers0[buf.index].start, rgb_buffers);//yuyv -> rgb24 rgb_stretch(rgb_buffers, dest_buffers, DEST_WIDTH, DEST_HEIGHT);//176 X 144 -> 320 X 240 rgb24_to_rgb565(dest_buffers, rgb565_buffers);//rgb24 -> rgb565 ret = fwrite(yuyv_buffers0[buf.index].start, yuyv_buffers0[buf.index].length, 1, file_fd);//將攝像頭採集得到的yuyv資料寫入檔案中 if(ret <= 0){ printf("write yuyv failed!\n"); exit(-1); } rgb_to_bmp(rgb565_buffers, bmp_fd);//rgb -> bmp ret = ioctl(fd, VIDIOC_QBUF,&buf);//幀緩衝入列 if(0 != ret){ printf("VIDIOC_QBUF failed!\n"); exit(-1); } fclose(file_fd); fclose(bmp_fd); return 1; }
這個函式,呼叫了以上3.1 - 3.3 列舉出來的所有函式,當然了,是先放大還是先儲存怎樣怎樣的,大家都可以自己調整。
3.4 RGB 轉JPEG如果本文以上所有部分對您來說,都沒有任何價值,那既然都到這裡了,或許可以繼續翻翻?說不定接下來的內容您會很感興趣哦!
眾說周知,jpeglib 是一個強大的jpeg類庫,直接呼叫裡面的一些介面函式,就能直接實現一些異常複雜的jpeg處理。但老版jpeglib 最不好的地方就是隻支援檔案流的輸入輸出,不支援從記憶體中解壓或者壓縮至記憶體中,實際工程中哪會處處都是檔案操作???因此用起來挺麻煩的,也照著網上的資料,修改了jpeglib的原始碼,結果渣渣技術,一執行就報段錯誤,搞了2天,放棄了,請教老師,結果,一句程式碼就解決了我2天的麻煩。哪句程式碼?
首先是編譯原始碼和準備開發環境。
1.感覺編譯jpeglib ,準備環境也沒有網上那些資料說得那麼簡單樣,因此在此還是簡單介紹下jpeglib的編譯以及使用環境的準備。
下載原始碼,注意最好是用新版的jpeglib-8b 版,因為老版本貌似是不支援記憶體中操作的(當然也沒下載到老版原始碼,沒實驗,有興趣的同學可以試試),這裡有個連結,自己的資源:jpegsrc.v8b.tar.gz。
2.解壓。
3.配置:
./configure CC=/opt/arm-cortex_a8/bin/arm-cortex_a8-linux-gnueabi-gcc LD=/opt/arm-cortex_a8/bin/arm-cortex_a8-linux-gnueabi-ld --host=arm-linux --prefix=/opt/arm-cortex_a8/arm-cortex_a8-linux-gnueabi --exec-prefix=/opt/arm-cortex_a8/arm-cortex_a8-linux-gnueabi --enable-shared --enable-static
配置好後,make,沒報錯,就make install , 千萬不能忘記make install !!!
make install 成功後會在我的/opt/arm-cortex_a8/arm-cortex_a8-linux-gnueabi/lib下生成5個庫:libjpeg.a lalibjpeg.la libjpeg.so libjpeg.so.8 libjpeg.so.8.0.2 ,會在我的/opt/arm-cortex_a8/arm-cortex_a8-linux-gnueabi/include下生成4個頭檔案:jconfig.h jerror.h jmorecfg.h jpeglib.h, 這幾個庫和標頭檔案是後面會用到。
注:(1)針對ARM下使用的jpeglib,CC:交叉編譯工具鏈,CC 後面接的路徑就是我的交叉編譯器的路徑,你的在哪裡自己才清楚啊; LD:連結用,同上; --host:指定主機,得是arm-linux,網上有資料說是arm-unkown-linux, 實測不行;--prefix:生成的標頭檔案存放目錄; --exec-prefix:生成的動態庫靜態庫存放目錄,這個很關鍵,必須得是arm-cortex_a8/arm-cortex_a8-linux-gnueabi;--enable-shared : 用GNU libtool編譯成動態連結庫 。想強調的一點是以上幾個引數,大家最好都配置上,注意嚴格檢查路徑,"="前後不要留空格,網上有些資料只說了要配置CC,LD,沒說配置host,結果自然還是使用不了。
(2)如果ARM板子是掛載的根檔案系統,那還必須把那5個庫拷貝到rootfs/lib 目錄下。
(3)如果你是要在PC機上使用jpeglib 庫的話,那麼只需把生成庫的路徑改為/lib 或者 /usr/lib 就行了。
4.所謂的準備開發環境,一是上面剛剛說的在正確的位置下準備好那5個庫,二是還需要把那4個頭檔案放到你的工程目錄下,這樣基本上就沒問題了。
心裡沒底的同學可以先測試下jpeglib庫能否使用,只需在一個最簡單的.C中,包含<jpeglib.h>,編譯時加上-ljpeg ,編譯(這個編譯器一定得是你上面配置的CC後面跟的那編譯器哦!)不報錯,那就是真正的沒問題了。反之,報XXXXlib 找不到,XXXX.h 找不到,那就得好好檢查上面幾步了。
庫準備好了就可以直接用了:long rgb_to_jpeg(const char *rgb, char *jpeg) { long jpeg_size; struct jpeg_compress_struct jcs; struct jpeg_error_mgr jem; JSAMPROW row_pointer[1]; int row_stride; jcs.err = jpeg_std_error(&jem); jpeg_create_compress(&jcs); jpeg_mem_dest(&jcs, jpeg, &jpeg_size);//就是這個函式!!!!!!! jcs.image_width = WIDTH; jcs.image_height = HEIGHT; jcs.input_components = 3;//1; jcs.in_color_space = JCS_RGB;//JCS_GRAYSCALE; jpeg_set_defaults(&jcs); jpeg_set_quality(&jcs, 180, TRUE); jpeg_start_compress(&jcs, TRUE); row_stride =jcs.image_width * 3; while(jcs.next_scanline < jcs.image_height){//對每一行進行壓縮 row_pointer[0] = &rgb[jcs.next_scanline * row_stride]; (void)jpeg_write_scanlines(&jcs, row_pointer, 1); } jpeg_finish_compress(&jcs); jpeg_destroy_compress(&jcs); #ifdef JPEG //jpeg 儲存,測試用 FILE *jpeg_fd; sprintf(path3, "./jpeg%d.jpg", numb); jpeg_fd = fopen(path3,"w"); if(jpeg_fd < 0 ){ perror("open jpeg.jpg failed!\n"); exit(-1); } fwrite(jpeg, jpeg_size, 1, jpeg_fd); close(jpeg_fd); #endif return jpeg_size; }
上面的程式碼註釋不是很詳細,jpeglib 詳細使用方法看這篇部落格就OK:利用jpeglib壓縮影象為jpg格式,想必大家也發現了,是的,如果你用的是jpeg_stdio_dest()函式,那麼就是檔案操作,最後壓縮完成的結果直接儲存在檔案中,反之,如果你用的是jpeg_mem_dest()函式,那麼壓縮完成的結果就儲存在記憶體中。
注:再次宣告,我用的jpeglib 是-8b 版本,老版本是否支援jpeg_mem_dest() 函式,我沒驗證過..........這裡有一個以上功能的完整版程式,完整版與上面的程式碼片段有稍許不同,請注意。Linux 下V4l2攝像頭採集圖片,實現yuyv轉RGB,RGB轉BMP,RGB伸縮,RGB轉JPEG(儲存到記憶體中),JPEG經UDP傳送功能,或者您也可以直接到我的資源中去下載。
到此為止,大功告成!!!