Linux 开发 _BMP 图片编程 (翻转、添加水印)
- 2022 年 6 月 24 日
本文字数:6551 字
阅读完需:约 21 分钟
这篇文章主要是以实际案例拓展来理解 BMP 图片编程,理解 BMP 图片如何进行编程操作。实现文字水印添加、图像翻转、图像缩放等操作。
一、摄像头编程扩展
【1】改进拍照程序,至少 BMP 图片数据要正常,还可以将.c 文件分隔成 3 个.c 文件。
24 位的真彩色 BMP 图片的构造:分为 3 个部分。
(1) 存放 BMP 头数据—主要存放 BMP 属性,RGB 数据偏移量
(2) 存放 BMP 信息数据—存放宽度、高度、颜色位数
(3) 存放 BMP 图片源数据—RGB888
注意:
(1) BMP 图片一行(宽度)的数据必须是 4 的倍数。
(2) BMP 图片的源数据是从下到上,从左到右
BMP、JPG、PNG、MP4、MP3
【2】扩展案例: 拍照图片上加水印。比如:时间水印、或者自己的名字
(1) 第一种图片水印: 图片重叠。
(2) 在控制台终端屏幕上使用*号打印自己名字。
要求:
(1) 添加文字水印和数字水印。比如: 添加自己的名字和日期
(2) 实现任意位置添加任意的水印。
(3) 实现缩放功能,将图片可以任意放大和缩小。
【3】精简远程摄像头监控的代码文件。
将网页监控相关的代码文件单独提取出来进行编译,实现远程监控的效果。
使用 gcc 单独编译实现。(uvc)
分析: 程序是依靠 Makefile 文件已经编译。首先从 Makefile 文件入手。
主要修改的文件:mjpg_streamer.c
【A】改进拍照程序,至少 BMP 图片数据要正常,还可以将.c 文件分隔成 3 个.c 文件。
24 位的真彩色 BMP 图片的构造:分为 3 个部分。
(1) 存放 BMP 头数据—主要存放 BMP 属性,RGB 数据偏移量
(2) 存放 BMP 信息数据—存放宽度、高度、颜色位数
(3) 存放 BMP 图片源数据—RGB888
注意:
(1) BMP 图片一行(宽度)的数据必须是 4 的倍数。
(2) BMP 图片的源数据是从下到上,从左到右
BMP、JPG、PNG、MP4、MP3
【B】扩展知识: 拍照图片上加水印。比如:时间水印、或者自己的名字
(1) 第一种图片水印: 图片重叠。
(2) 在控制台终端屏幕上使用*号打印自己名字。
要求:
(1) 添加文字水印和数字水印。比如: 添加自己的名字和日期
(2) 实现任意位置添加任意的水印。
(3) 实现缩放功能,将图片可以任意放大和缩小。
【C】精简远程摄像头监控的代码文件。
将网页监控相关的代码文件单独提取出来进行编译,实现远程监控的效果。
使用 gcc 单独编译实现。(uvc)
分析: 程序是依靠 Makefile 文件已经编译。首先从 Makefile 文件入手。
二、汉字库的制作与使用
需要解决的问题:
【1】如何从汉字库里提取自己想要的点阵数据 。比如: 小龙哥
汉字库编码还是 GBK 编码: 有特定的公式可以计算点阵码的位置。
【2】如何区分中文还有英文
数据大于 0x80 就是中文,其他就是英文数据。 变量必须是: 无符号类型
注意: 写代码需要在中文编码的情况下编写!
【3】英文数据该如何取模?
从空格开始到~号结束 ,连续 95 个数据。
三、BMP 图片案例代码
3.1 BMP 图片缩放
#include <stdio.h>
#include <string.h>
int PicZoom(unsigned char *s_buff,unsigned int s_width,unsigned int s_height,unsigned char *buff,unsigned int width,unsigned int height);
void *my_memcpy(void *v_dst,const void *v_src,unsigned char c);
#pragma pack(1) /* 必须在结构体定义之前使用,这是为了让结构体中各成员按1字节对齐 */
/*需要文件信息头:14个字节 */
struct BITMAPFILEHEADER
{
unsigned short bfType; //保存图片类似。 'BM'
unsigned long bfSize; //图片的大小
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned long bfOffBits; //RGB数据偏移地址
};
/* 位图信息头 */
struct BITMAPINFOHEADER { /* bmih */
unsigned long biSize; //结构体大小
unsigned long biWidth; //宽度
unsigned long biHeight; //高度
unsigned short biPlanes;
unsigned short biBitCount; //颜色位数
unsigned long biCompression;
unsigned long biSizeImage;
unsigned long biXPelsPerMeter;
unsigned long biYPelsPerMeter;
unsigned long biClrUsed;
unsigned long biClrImportant;
};
#define NEW_FILE_NAME "new.bmp" //缩放后的新图片名称
#define SRC_FILE_NAME "src.bmp" //源图片名称
/*
图片放大与缩小示例
*/
int main()
{
struct BITMAPFILEHEADER src_head; //源文件头数据
struct BITMAPINFOHEADER src_info; //源文件参数结构
struct BITMAPFILEHEADER new_head; //新文件头数据
struct BITMAPINFOHEADER new_info; //新文件参数结构
unsigned int new_Width; //缩放后的宽度
unsigned int new_Height; //缩放后的高度
unsigned char *new_buff; //存放新图片的数据
unsigned char *src_buff; //存放源图片的数据
unsigned int cnt=0;
/*1. 打开图片文件*/
FILE *src_file=fopen(SRC_FILE_NAME,"rb");
FILE *new_file=fopen(NEW_FILE_NAME,"wb");
if(src_file==NULL||new_file==NULL)
{
printf("%s 源文件打开失败!\r\n",SRC_FILE_NAME);
return;
}
/*2. 读取源图片参数*/
fread(&src_head,sizeof(struct BITMAPFILEHEADER),1,src_file);
fread(&src_info,sizeof(struct BITMAPINFOHEADER),1,src_file);
printf("源图片尺寸:w=%d h=%d\r\n",src_info.biWidth,src_info.biHeight);
/*3. 获取新图片的尺寸*/
printf("输入新图片的宽度: ");
scanf("%d",&new_Width);
printf("输入新图片的高度: ");
scanf("%d",&new_Height);
printf("新图片尺寸:w=%d h=%d\r\n",new_Width,new_Height);
/*4. 申请存放图片数据的空间*/
src_buff=malloc(src_info.biWidth*src_info.biHeight*3);
new_buff=malloc(new_Width*new_Height*3);
if(new_buff==NULL||src_buff==NULL)
{
printf("malloc申请空间失败!\r\n");
return -1;
}
/*5. 读取源图片RGB数据*/
fseek(src_file,src_head.bfOffBits,SEEK_SET); //移动文件指针到RGB数据位置
fread(src_buff,1,src_info.biWidth*src_info.biHeight*3,src_file); //读取源数据
/*6. 缩放图片*/
if(PicZoom(src_buff,src_info.biWidth,src_info.biHeight,new_buff,new_Width,new_Height))
{
printf("图片缩放处理失败!\r\n");
return -1;
}
/*7. 写入新图片数据*/
//填充文件头
memset(&new_head,0,sizeof(struct BITMAPFILEHEADER));
new_head.bfType=0x4d42;
new_head.bfSize=54+new_Width*new_Height*3;
new_head.bfOffBits=54;
//填充文件参数
memset(&new_info,0,sizeof(struct BITMAPINFOHEADER));
new_info.biSize=sizeof(struct BITMAPINFOHEADER);
new_info.biWidth=new_Width;
new_info.biHeight=new_Height;
new_info.biPlanes=1;
new_info.biBitCount=24;
//写入文件数据
fwrite(&new_head,sizeof(struct BITMAPFILEHEADER),1,new_file);
fwrite(&new_info,sizeof(struct BITMAPINFOHEADER),1,new_file);
fseek(new_file,new_head.bfOffBits,SEEK_SET); //移动文件指针到RGB数据位置
cnt=fwrite(new_buff,1,new_info.biWidth*new_info.biHeight*3,new_file); //写数据
/*8. 关闭图片文件*/
fclose(new_file);
fclose(src_file);
printf("%s 新图片创建成功! 路径:程序运行路径下\r\n",NEW_FILE_NAME);
return 0;
}
/**********************************************************************
* 函数名称: PicZoom
* 功能描述: 近邻取样插值方法缩放图片
* 注意该函数会分配内存来存放缩放后的图片,用完后要用free函数释放掉
* "近邻取样插值"的原理请参考网友"lantianyu520"所著的"图像缩放算法"
* 输入参数: ptOriginPic - 内含原始图片的象素数据
* ptZoomPic - 内含缩放后的图片的象素数据
* 输出参数: 无
* 返 回 值: 0 - 成功, 其他值 - 失败
***********************************************************************/
int PicZoom(unsigned char *ptOriginPic_aucPixelDatas,unsigned int ptOriginPic_iWidth,unsigned int ptOriginPic_iHeight,unsigned char *ptZoomPic_aucPixelDatas,unsigned int ptZoomPic_iWidth,unsigned int ptZoomPic_iHeight)
{
unsigned int ptOriginPic_iLineBytes=ptOriginPic_iWidth*3; //一行的字节数
unsigned int ptZoomPic_iLineBytes=ptZoomPic_iWidth*3; //一行的字节数
unsigned long dwDstWidth=ptZoomPic_iWidth;
unsigned long* pdwSrcXTable;
unsigned long x;
unsigned long y;
unsigned long dwSrcY;
unsigned char *pucDest;
unsigned char *pucSrc;
unsigned long dwPixelBytes=3; //像素字节
pdwSrcXTable=malloc(sizeof(unsigned long) * dwDstWidth);
if(NULL==pdwSrcXTable)
{
return -1;
}
for(x=0; x < dwDstWidth; x++)//生成表 pdwSrcXTable
{
pdwSrcXTable[x]=(x*ptOriginPic_iWidth/ptZoomPic_iWidth);
}
for(y=0; y < ptZoomPic_iHeight; y++)
{
dwSrcY=(y * ptOriginPic_iHeight/ptZoomPic_iHeight);
pucDest=ptZoomPic_aucPixelDatas + y * ptZoomPic_iLineBytes;
pucSrc=ptOriginPic_aucPixelDatas+dwSrcY * ptOriginPic_iLineBytes;
for(x=0; x <dwDstWidth; x++)
{
my_memcpy(pucDest+x*dwPixelBytes,pucSrc+pdwSrcXTable[x]*dwPixelBytes,dwPixelBytes);
}
}
free(pdwSrcXTable);
return 0;
}
/*
函数功能:内存拷贝函数
*/
void *my_memcpy(void *v_dst,const void *v_src,unsigned char c)
{
const char *src=v_src;
char *dst=v_dst;
while(c--)
*dst++=*src++;
return v_dst;
}
3.2 BMP 图片添加水印
#include "savebmp.h"
#include "yuvtorgb.h"
/* 图片的象素数据 */
typedef struct PixelDatas {
int iWidth; /* 宽度: 一行有多少个象素 */
int iHeight; /* 高度: 一列有多少个象素 */
int iBpp; /* 一个象素用多少位来表示 */
int iLineBytes; /* 一行数据有多少字节 */
int iTotalBytes; /* 所有字节数 */
unsigned char *VideoBuf; //存放一帧摄像头的数据
//指向了存放摄像头数据的空间地址
}T_PixelDatas;
T_PixelDatas Pixedata; //存放实际的图像数据
/*
USB摄像头相关参数定义
*/
struct v4l2_buffer tV4l2Buf;
int iFd;
int ListNum;
unsigned char* pucVideBuf[4]; // 视频BUFF空间地址
void camera_pthread(void);
int main(int argc ,char *argv[])
{
if(argc!=2)
{
printf("./app /dev/videoX\n");
return -1;
}
camera_init(argv[1]); //摄像头设备初始化
//开始采集摄像头数据,并编码保存为BMP图片
camera_pthread();
return 0;
}
//摄像头设备的初始化
int camera_init(char *video)
{
int i=0;
int cnt=0;
//定义摄像头驱动的BUF的功能捕获视频
int iType = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* 1、打开视频设备 */
iFd = open(video,O_RDWR);
if(iFd < 0)
{
printf("摄像头设备打开失败!\n");
return 0;
}
struct v4l2_format tV4l2Fmt;
/* 2、 VIDIOC_S_FMT 设置摄像头使用哪种格式 */
memset(&tV4l2Fmt, 0, sizeof(struct v4l2_format));
tV4l2Fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //视频捕获
//设置摄像头输出的图像格式
tV4l2Fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;
/*设置输出的尺寸*/
tV4l2Fmt.fmt.pix.width = 640;
tV4l2Fmt.fmt.pix.height = 480;
tV4l2Fmt.fmt.pix.field = V4L2_FIELD_ANY;
//VIDIOC_S_FMT 设置摄像头的输出参数
ioctl(iFd, VIDIOC_S_FMT, &tV4l2Fmt);
//打印摄像头实际的输出参数
printf("Support Format:%d\n",tV4l2Fmt.fmt.pix.pixelformat);
printf("Support width:%d\n",tV4l2Fmt.fmt.pix.width);
printf("Support height:%d\n",tV4l2Fmt.fmt.pix.height);
/* 3、VIDIOC_REQBUFS 申请buffer */
/* 初始化Pixedata结构体,为转化做准备 */
Pixedata.iBpp =24;
//高度 和宽度的赋值
Pixedata.iHeight = tV4l2Fmt.fmt.pix.height;
Pixedata.iWidth = tV4l2Fmt.fmt.pix.width;
//一行所需要的字节数
Pixedata.iLineBytes = Pixedata.iWidth*Pixedata.iBpp/8;
//一帧图像的字节数
Pixedata.iTotalBytes = Pixedata.iLineBytes * Pixedata.iHeight;
Pixedata.VideoBuf=malloc(Pixedata.iTotalBytes); //申请存放图片数据空间
//v412请求命令
struct v4l2_requestbuffers tV4l2ReqBuffs;
memset(&tV4l2ReqBuffs, 0, sizeof(struct v4l2_requestbuffers));
/* 分配4个buffer:实际上由VIDIOC_REQBUFS获取到的信息来决定 */
tV4l2ReqBuffs.count = 4; /*在内核空间里开辟4个空间*/
/*支持视频捕获功能*/
tV4l2ReqBuffs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/* 表示申请的缓冲是支持MMAP(内存映射) */
tV4l2ReqBuffs.memory = V4L2_MEMORY_MMAP;
/* 为分配buffer做准备 */
ioctl(iFd, VIDIOC_REQBUFS, &tV4l2ReqBuffs);
for (i = 0; i < tV4l2ReqBuffs.count; i++)
{
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = i; // 0 1 2 3
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /*支持视频捕获*/
tV4l2Buf.memory = V4L2_MEMORY_MMAP; /*支持内存映射*/
/* 6、VIDIOC_QUERYBUF 确定每一个buffer的信息 并且 mmap */
ioctl(iFd, VIDIOC_QUERYBUF, &tV4l2Buf);
//映射空间地址
pucVideBuf[i] = mmap( /*返回用户空间的地址*/
0, /*表示系统自己制定地址*/
tV4l2Buf.length, /*映射的长度*/
PROT_READ, /*空间数据只读*/
MAP_SHARED, /*空间支持共享*/
iFd, /*将要映射的文件描述符*/
tV4l2Buf.m.offset /*从哪个位置开始映射,表示起始位置*/
);
printf("mmap %d addr:%p\n",i,pucVideBuf[i]);
}
/* 4、VIDIOC_QBUF 放入队列*/
for (i = 0; i <tV4l2ReqBuffs.count; i++)
{
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = i;
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP;
ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf); //放入队列 queue
}
/*5、启动摄像头开始读数据*/
ioctl(iFd, VIDIOC_STREAMON, &iType);
return 0;
}
/*采集摄像头数据*/
void camera_pthread(void)
{
int error;
int cnt=0;
int i=0;
int ListNum;
/* 8.1、等待是否有数据 */
fd_set readfds;
/* YUV格式的数据<------>在LCD上显示:rgb888 */
initLut();
printf("开始采集数据.......\n");
// while(1)
// {
FD_ZERO(&readfds);
FD_SET(iFd,&readfds);
select(iFd+1,&readfds,NULL,NULL,NULL); /*检测文件描述符是否发生了读写事件*/
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //类型
tV4l2Buf.memory = V4L2_MEMORY_MMAP; //存储空间类型
/* 9、VIDIOC_DQBUF 从队列中取出 */
error = ioctl(iFd, VIDIOC_DQBUF, &tV4l2Buf); //取出一帧数据
ListNum = tV4l2Buf.index; //索引编号 0
//将YUV转换为RGB
Pyuv422torgb32(pucVideBuf[ListNum],Pixedata.iWidth,Pixedata.iHeight,Pixedata.VideoBuf);
//保存BMP
save_bmp(Pixedata.VideoBuf,Pixedata.iWidth,Pixedata.iHeight);
memset(&tV4l2Buf, 0, sizeof(struct v4l2_buffer));
tV4l2Buf.index = ListNum;
tV4l2Buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
tV4l2Buf.memory = V4L2_MEMORY_MMAP;
error = ioctl(iFd, VIDIOC_QBUF, &tV4l2Buf); /*将缓冲区再次放入队列*/
// }
}
版权声明: 本文为 InfoQ 作者【DS小龙哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/192b7adafcb0db241066eaccd】。文章转载请联系作者。
DS小龙哥
之所以觉得累,是因为说的比做的多。 2022.01.06 加入
熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域
评论