OpenCV的影響能夠說是巨大的,不管是傳統算法仍是DL網絡,開源代碼大都難以擺脫OpenCV圖像格式的魔爪。實際上真正上設備的代碼並不須要imread和imshow的功能,讀取圖像也有相應的編解碼方案;另外一方面,OpenCV的模塊不少,版本也不少,配置起來神煩。我只是想imshow一下,爲啥要依賴你OpenCV呢?我想寫純C,爲啥你OpenCV如今也傲嬌的不提供C接口必定要CPP呢?html
前面已經作到:windows下用純C實現一個簡陋的imshow:基於GDI。不過它存在一些問題:c++
使用的是libpng的簡單封裝讀取的圖像,讀取到的data並非BGRBGRBGR的OpenCV風格的數據排布順序git
使用libpng須要自行編譯,若是要處理jpg或其餘格式,不如stb_image這個單文件的庫方便github
今天改進完善了上述兩個方面,修改的代碼也比較容易。一個插曲是,看了大半天的window_w32.cpp
(opencv的highgui模塊下),仍是沒看懂它是如何把image的數據轉換爲bitmap數據的,流程細節比較多。基於先前的工做,我調用的是CreateBitmap函數,只能顯示32位深度的位圖,也就是必定要有alpha通道,於是簡單的作法是把BGRBGRBGR數據排布改爲BGRABGRABGRA便可(也許能夠用CreateBitmap以外的作法,目前主要是簡單粗暴的實現起來):算法
case WM_CREATE: if (fc_window->im_type == FC_IMAGE_BMP) { fc_window->image = (HBITMAP)LoadImage(NULL, "D:/work/libfc/imgs/lena512.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); } else if (fc_window->im_type == FC_IMAGE_PNG || fc_window->im_type == FC_IMAGE_JPG) { assert(fc_window->channel == 3); size_t buf_size = sizeof(unsigned char*)*fc_window->width * fc_window->height * 4; // rgba fc_window->buf = (unsigned char*)malloc(buf_size); memset(fc_window->buf, 0, buf_size); // BGR => BGRA, since CreateBitmap(..,24,..,..) doesn't work for (int nh = 0; nh < fc_window->height; nh++) { for (int nw = 0; nw < fc_window->width; nw++) { for (int nc = 0; nc < 3; nc++) { size_t src_idx = nh * fc_window->width * 3 + nw * 3 + nc; size_t dst_idx = nh * fc_window->width * 4 + nw * 4 + nc; fc_window->buf[dst_idx] = fc_window->im_data[src_idx]; } } } fc_window->image = CreateBitmap(fc_window->width, fc_window->height, 32, 1, fc_window->buf); } if (fc_window->image == NULL) MessageBox(hwnd, "Could not load image!", "Error", MB_OK | MB_ICONEXCLAMATION); break;
image
對stb_image進行了簡單封裝獲得fc_image。借鑑於vision-hw0。windows
fc_image.h
網絡
#ifndef FC_IMAGE_H #define FC_IMAGE_H typedef enum FcImageType { FC_IMAGE_BMP, FC_IMAGE_PNG, FC_IMAGE_JPG } FcImageType; typedef struct FcImage { int w, h, c; unsigned char* data; size_t elem_size; FcImageType type; } FcImage; #ifdef __cplusplus extern "C" { #endif // load image. get BGRBGRBGR (opencv style) data FcImage fc_load_image(const char *filename); #ifdef __cplusplus } #endif #endif
fc_image.c
ide
#include "fc_image.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" //------------------------------------------------------------ // static function declarations //------------------------------------------------------------ static FcImage make_empty_image(int w, int h, int c); static FcImage make_image(int w, int h, int c); static FcImage load_image_stb(const char* filename, int channels); //------------------------------------------------------------ // static function implementations //------------------------------------------------------------ static FcImage make_empty_image(int w, int h, int c) { FcImage out; out.data = 0; out.h = h; out.w = w; out.c = c; return out; } static FcImage make_image(int w, int h, int c) { FcImage out = make_empty_image(w, h, c); out.data = (unsigned char*)calloc(h*w*c, sizeof(float)); return out; } static FcImage load_image_stb(const char* filename, int channels) { int w, h, c; unsigned char *data = stbi_load(filename, &w, &h, &c, channels); if (!data) { fprintf(stderr, "Cannot load image \"%s\"\nSTB Reason: %s\n", filename, stbi_failure_reason()); exit(0); } if (channels) c = channels; int i, j, k; FcImage im = make_image(w, h, c); for (k = 0; k < c; ++k) { for (j = 0; j < h; ++j) { for (i = 0; i < w; ++i) { // RGB => BGR int src_index = k + c * i + c * w*j; int dst_index = (2 - k) + c * i + c * w*j; //cv_im2->imageData[dst_index] = data[src_index]; im.data[dst_index] = data[src_index]; } } } //We don't like alpha channels, #YOLO if (im.c == 4) im.c = 3; free(data); return im; } //------------------------------------------------------------ // function implementations //------------------------------------------------------------ // load image. get BGRBGRBGR (opencv style) data FcImage fc_load_image(const char *filename) { FcImage im = load_image_stb(filename, 0); //TODO: assign im's type inside load_image_stb() return im; }
show image函數
fc_higui.hpost
fc_higui_gdi.c
fc_higui.h
#ifndef FC_HIGUI_H #define FC_HIGUI_H #include "fc_image.h" #ifdef __cplusplus extern "C" { #endif void fc_show_image(const char* winname, const FcImage* im); void fc_wait_key(); #ifdef __cplusplus } #endif #endif
fc_gui_gdi.c
#include <stdio.h> #include <stdbool.h> #include <windows.h> #include <assert.h> #include "fc_image.h" //#pragma comment(lib, "msimg32.lib") // for png's alpha channel static const char* fc_window_class_name = "fc GUI class"; typedef struct FcWindow { HDC dc; HGDIOBJ image; unsigned char* im_data; unsigned char* buf; FcImageType im_type; int width; int height; int channel; int top_left_x; // window position, top left point's x coordinate int top_left_y; // window posttion, top left point's y coordinate bool adjusted; const char* title; //int bit_depth; // each pixel consist of how many bits? int elem_size; } FcWindow; FcWindow* fc_window; //int DEFAULT_WIDTH, DEFAULT_HIGHT; void SetWindowSize(HWND hWnd) { if (fc_window->adjusted == false) { RECT WindowRect; RECT ClientRect; GetWindowRect(hWnd, &WindowRect); GetClientRect(hWnd, &ClientRect); WindowRect.right += (fc_window->width - ClientRect.right); WindowRect.bottom += (fc_window->height - ClientRect.bottom); MoveWindow(hWnd, WindowRect.left, WindowRect.top, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top, true); } fc_window->adjusted = true; } static void FillBitmapInfo(BITMAPINFO* bmi, int width, int height, int bpp, int origin) { assert(bmi && width >= 0 && height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32)); BITMAPINFOHEADER* bmih = &(bmi->bmiHeader); memset(bmih, 0, sizeof(*bmih)); bmih->biSize = sizeof(BITMAPINFOHEADER); bmih->biWidth = width; bmih->biHeight = origin ? abs(height) : -abs(height); bmih->biPlanes = 1; bmih->biBitCount = (unsigned short)bpp; bmih->biCompression = BI_RGB; if (bpp == 8) { RGBQUAD* palette = bmi->bmiColors; int i; for (i = 0; i < 256; i++) { palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = (BYTE)i; palette[i].rgbReserved = 0; } } } // returns TRUE if there is a problem such as ERROR_IO_PENDING. static bool fc_get_bitmap_data(FcWindow* window, SIZE* size, int* channels, void** data) { BITMAP bmp; GdiFlush(); HGDIOBJ h = GetCurrentObject(window->dc, OBJ_BITMAP); if (size) size->cx = size->cy = 0; if (data) *data = 0; if (h == NULL) return true; if (GetObject(h, sizeof(bmp), &bmp) == 0) return true; if (size) { size->cx = abs(bmp.bmWidth); size->cy = abs(bmp.bmHeight); } if (channels) *channels = bmp.bmBitsPixel / 8; if (data) *data = bmp.bmBits; return false; } LRESULT __stdcall WindowProcedure(HWND hwnd, unsigned int msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_CREATE: if (fc_window->im_type == FC_IMAGE_BMP) { fc_window->image = (HBITMAP)LoadImage(NULL, "D:/work/libfc/imgs/lena512.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); } else if (fc_window->im_type == FC_IMAGE_PNG || fc_window->im_type == FC_IMAGE_JPG) { assert(fc_window->channel == 3); size_t buf_size = sizeof(unsigned char*)*fc_window->width * fc_window->height * 4; // rgba fc_window->buf = (unsigned char*)malloc(buf_size); memset(fc_window->buf, 0, buf_size); // BGR => BGRA, since CreateBitmap(..,24,..,..) doesn't work for (int nh = 0; nh < fc_window->height; nh++) { for (int nw = 0; nw < fc_window->width; nw++) { for (int nc = 0; nc < 3; nc++) { size_t src_idx = nh * fc_window->width * 3 + nw * 3 + nc; size_t dst_idx = nh * fc_window->width * 4 + nw * 4 + nc; fc_window->buf[dst_idx] = fc_window->im_data[src_idx]; } } } fc_window->image = CreateBitmap(fc_window->width, fc_window->height, 32, 1, fc_window->buf); } if (fc_window->image == NULL) MessageBox(hwnd, "Could not load image!", "Error", MB_OK | MB_ICONEXCLAMATION); break; case WM_PAINT: { SetWindowSize(hwnd); PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); SetStretchBltMode(hdc, COLORONCOLOR); fc_window->dc = CreateCompatibleDC(hdc); HBITMAP hbmOld = SelectObject(fc_window->dc, fc_window->image); #if 1 //copy original, no stretch BitBlt(hdc, 0, 0, fc_window->width, fc_window->height, fc_window->dc, 0, 0, SRCCOPY); //AlphaBlend(hdc, 100, 0, bm.bmWidth, bm.bmHeight, my_window->dc, 0, 0, bm.bmWidth, bm.bmHeight, bf); #else RECT rcClient; GetClientRect(window, &rcClient); int nWidth = rcClient.right - rcClient.left; int nHeight = rcClient.bottom - rcClient.top; StretchBlt(hdc, 0, 0, nWidth, nHeight, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY); #endif SelectObject(fc_window->dc, hbmOld); DeleteDC(fc_window->dc); EndPaint(hwnd, &ps); } break; case WM_DESTROY: printf("\ndestroying window\n"); PostQuitMessage(0); return 0L; case WM_LBUTTONDOWN: printf("\nmouse left button down at (%d, %d)\n", LOWORD(lp), HIWORD(lp)); // fall thru default: //printf("."); return DefWindowProc(hwnd, msg, wp, lp); } } void fc_destroy_window() { if (fc_window) { if (fc_window->buf) { free(fc_window->buf); fc_window->buf = NULL; } free(fc_window); fc_window = NULL; } } static void MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); /* Win 3.x */ wc.style = CS_DBLCLKS; wc.lpfnWndProc = WindowProcedure; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = GetModuleHandle(0); wc.hIcon = LoadIcon(0, IDI_APPLICATION); wc.hCursor = LoadCursor(0, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = 0; wc.lpszClassName = fc_window_class_name; /* Win 4.0 */ wc.hIconSm = LoadIcon(0, IDI_APPLICATION); RegisterClassEx(&wc); } static BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { // hard code here. since x=600 and y=300 is good for most cases fc_window->top_left_x = 600; fc_window->top_left_y = 300; DWORD defStyle = WS_VISIBLE | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU; HWND hwnd = CreateWindowEx(0, fc_window_class_name, fc_window->title, defStyle, fc_window->top_left_x, fc_window->top_left_y, fc_window->width, fc_window->height, 0, 0, hInstance, 0); if (!hwnd) { return FALSE; } ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); return TRUE; } static void fc_create_window(FcWindow** _my_window) { FcWindow* fc_window = (FcWindow*)malloc(sizeof(FcWindow)); fc_window->dc = NULL; fc_window->im_data = NULL; fc_window->image = NULL; fc_window->adjusted = false; *_my_window = fc_window; // write back } void fc_show_image(const char* title, FcImage* im) { fc_create_window(&fc_window); fc_window->width = im->w; fc_window->height = im->h; fc_window->im_data = im->data; fc_window->im_type = FC_IMAGE_PNG; fc_window->title = title; fc_window->channel = im->c; fc_window->elem_size = im->elem_size * 8; HINSTANCE hInstance = GetModuleHandle(0); int nCmdShow = SW_SHOWDEFAULT; MyRegisterClass(hInstance); if (!InitInstance(hInstance, nCmdShow)) { fprintf(stderr, "Error! failed to init window\n"); } } void fc_wait_key() { MSG msg; while (GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } }
test
測試程序。若是不開USE_OPENCV
宏,保存爲.c文件便可;若是開啓了USE_OPENCV
宏,須要保存爲.cpp文件,而且引入OpenCV庫(用來驗證效果是否一致),推薦用CMake構建。
fc_show_image_test.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "fc_image.h" #include "fc_higui.h" //#define USE_OPENCV #ifdef USE_OPENCV #include <opencv2/opencv.hpp> #endif int main() { //const char* im_pth = "D:/work/libfc/imgs/Lena.png"; const char* im_pth = "D:/work/libfc/imgs/fruits.jpg"; FcImage im = fc_load_image(im_pth); //im.type = FC_IMAGE_PNG; im.type = FC_IMAGE_JPG; fc_show_image("fruits.jpg", &im); fc_wait_key(); #ifdef USE_OPENCV int depth = 8; IplImage* cv_im2 = cvCreateImage(CvSize(im.w, im.h), depth, im.c); cv_im2->imageData = (char*)im.data; cvShowImage("image2", cv_im2); cvWaitKey(0); #endif return 0; }