OpenCV的影响可以说是巨大的,无论是传统算法还是DL网络,开源代码大都难以摆脱OpenCV图像格式的魔爪。实际上真正上设备的代码并不需要imread和imshow的功能,读取图像也有相应的编解码方案;另一方面,OpenCV的模块很多,版本也很多,配置起来神烦。我只是想imshow一下,为啥要依赖你OpenCV呢?我想写纯C,为啥你OpenCV现在也傲娇的不提供C接口一定要CPP呢?
前面已经做到:windows下用纯C实现一个简陋的imshow:基于GDI。不过它存在一些问题:
使用的是libpng的简单封装读取的图像,读取到的data并不是BGRBGRBGR的OpenCV风格的数据排布顺序
使用libpng需要自行编译,如果要处理jpg或其他格式,不如stb_image这个单文件的库方便
今天改进完善了上述两个方面,修改的代码也比较容易。一个插曲是,看了大半天的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。
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
#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.h
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;
}
基于GDI的imshow:使用stb_image读取图像并修正绘制
原文:https://www.cnblogs.com/zjutzz/p/10928442.html