图像处理(C++ CImage class)学习笔记

article/2025/10/16 6:00:45

基础篇

A. 图像三原色及灰度值

A1. 彩色图像的三原色

  • 图像三原色
    — R:红色red
    — G:绿色green
    — B:蓝色blue
  • 三原色的取值范围:0(无)~255(满)
    — 红色:R=255 G=0 B=0
    — 紫色:R=112 G=48 B=160
    — 黑色:R=0 G=0 B=0
    — 黄色:R=255 G=255 B=0
    — 粉色:R=255 G=0 B=255

A2. 灰度图像的灰度值

  • 灰度值(gray-scale value):描述黑白图像点的颜色深度
  • 灰度值的取值范围:0(黑)~255(白)
    — 黑色:Gray=0
    — 深灰:Gray=100
    — 浅灰:Gray=200
    — 白色:Gray=255

A3. 用彩色图像表示灰度图像

  • 当图像的R、G、B值相等时,彩色图像表现为灰度图像
    — 深灰 Gray=100 —— R = 100, G = 100, B = 100
    — 白色 Gray=255 —— R = 255, G = 255, B = 255

B. 数字图像的原理和表示方法

B1. 数字图像原理

在这里插入图片描述
在这里插入图片描述

B2. 数字图像的表示方式

  • 用(x, y)表示一个像素点的坐标,有的图像格式以左上角坐标为(0, 0),有的图像格式以左下角坐标为(0, 0)
  • 用两重循环可以遍历访问数字图像的每一个像素点
    在这里插入图片描述

C. 数字图像的分辨率和位深度

C1. 图像的重要参数

  • 空间分辨率:图像在水平和垂直方向的像素个数,值越大图像越清晰
  • 位深度:用来表示一个像素点颜色的位数(bit),值越大图像的颜色区分度越好

C2. 位深度

  • 彩色图像的位深度
    — 16位(不常用),用2个字节表示一个像素点的RGB值
    — 24位,用3个字节表示一个像素点的RGB值
    — 32位,额外的1个字节表示透明度Alpha值
  • 灰度图像的位深度
    — 8位,用1个字节表示一个像素点的灰度值

案例
在这里插入图片描述
(空间)分辨率:726*461
宽度:726像素
高度:461像素
位深度:24

C3. 空间分辨率与数字图像的质量

在这里插入图片描述

C4. 位深度与数字图像的质量

  • 24位深度可以表示2^24=16777216种不同的颜色
  • 8位深度可以表示2^8=256种不同的颜色
  • 1位深度可以表示2^1=2种不同的颜色
    在这里插入图片描述

C5. 数字图像根据位深度的分类

  • 二值图像(1位)
  • 灰度图像(8位)
  • 彩色图像(16/24/32位)

C5.1 二值图像(1位)

  • 像素值为0(黑色)或1(白色)。
    在这里插入图片描述

C5.2 灰度图像(8位)

  • 灰度取值范围是0~255,黑色为0,白色为255
    在这里插入图片描述

C5.3 彩色图像(24位)

  • 每个像素由RGB三个分量构成,取值范围是0~255

  • 如果一个像素的R、G、B值相同,则该像素等同于灰度图像中的像素
    例如:R=G=B=100,表示灰度为100的像素点
    在这里插入图片描述
    C5.4 灰度图像和彩色图像的转换

  • 彩色图像(R,G,B)转换成灰度图像
    Gray = R * 0.299 + G * 0.587 + B * 0.114

  • 灰度图像转换成彩色图像
    — 灰度图像不可能变换为原来的彩色图像
    — 伪彩色处理可用于把灰度值映射成彩色的RGB值,例如在宇宙星空图片上的应用
    在这里插入图片描述

D. C++ CImage class

D1. C++ CImage类概述

  • 支持jpg、bmp、png、gif格式图像文件的读写和操作,可以转换成不同的文件格式
  • 参考:https://docs.microsoft.com/zh-cn/cpp/atl-mfc-shared/reference/cimage-class

CImage类常用方法
在这里插入图片描述
使用CImage类的一般方法

  • 添加CImage类的包含方法:#include<atlimage.h>
  • 定义一个CImage类对象,然后调用CImage::Load方法装载一个外部图像文件
  • 根据题目要求处理图像
  • 调用CImage::Save方法保存图像

如果atlimage.h编译报错提示不存在

  • 检查C++ ATL for xxx是否安装,下图是VS2019的界面
    在这里插入图片描述

D2. COLORREF类

  • COLORREF类(color reference),表示颜色值(RGB),实际上是unsigned long类型
  • 从COLORREF类获取红绿蓝三原色的方法
    — 红色:int GetRValue(COLORREF &)
    — 绿色:int GetGValue(COLORREF &)
    — 蓝色:int GetBValue(COLORREF &)
    — 返回值是0~255之间的整数
  • 利用红绿蓝三原色生成COLORREF类的方法
    — COLORREF RGB(int R, int G, int B)

COLORREF类应用

  • 例:某个像素点的RGB值分别为r、g、b,定义颜色类对象c存储RGB值
    COLORREF c = RGB(r, g, b);
  • 例:已知某点用颜色类对象c存储RGB值,求其中的R、G、B值分别为多少?
    BYTE b = GetBValue(c);
    BYTE g = GetGValue(c);
    BYTE r = GetRValue(c);

D3. BYTE类

  • BYTE类,通常用来表示一个0~255之间的整数,实际上是unsigned char类型

E. 案例

E1. 把图像中所有红色的点改成绿色

在这里插入图片描述
分析:

  • 并不是所有的红点都是纯红(255,0,0)
  • 如何确定某些点偏红?
    R>G && R>B
  • 如何把偏红的点变成变绿的点?
    方法一:设置为(0,255,0)
    方法二:swap(R,G)

流程:

  1. 读图片(要先把源图片放到读路径下)
  2. 根据图片宽度和高度开两重循环
    ①读取指定坐标的像素点颜色
    ②如果颜色中红色分量最大,则红色与绿色交换
    ③设置指定坐标的像素点颜色
  3. 保存图片

代码:

#include<iostream>
#include<atlimage.h>//添加CImage类void swap(BYTE& a, BYTE& b)//交换函数
{BYTE temp;temp = a;a = b;b = temp;
}void redToGreen(COLORREF& c)//E1:把图像中所有红色的点改成绿色
{//获取c中RGB的值BYTE r = GetRValue(c);//获取c中R的值BYTE g = GetGValue(c);//获取c中G的值BYTE b = GetBValue(c);//获取c中B的值//如果红色分量更大,则与绿色交换if (r > g && r > b){swap(r, g);//调用交换函数}c = RGB(r, g, b);//c中存储交换后的rgb值
}int main()
{//1 读取与保存图像路径LPCTSTR srcFilePath = _T("1a.jpg");//源图像路径LPCTSTR destFilePath = _T("1b.jpg");//目标图像路径//2 加载图像CImage srcImage;//新建一个CImage类对象srcImage.Load(srcFilePath);//从源图像路径中加载图像//3 获取图像的高和宽int width, high;width = srcImage.GetWidth();//获取图像的宽度high = srcImage.GetHeight();//获取图像的高度//4 遍历整个图像的像素点,获取整个图像for (int x = 0; x < width; x++)//遍历x轴{for (int y = 0; y < high; y++)//遍历y轴{COLORREF c = srcImage.GetPixel(x, y);//新建一个COLORREF类对象,用来存储得到的图像像素点颜色redToGreen(c);//调用E1函数,处理颜色srcImage.SetPixel(x, y, c);//设置图像像素点的颜色}}//6 保存图像到指定路径srcImage.Save(destFilePath);return 0;
}

E2. 把彩色图像修改成灰色图像

在这里插入图片描述
分析:

  • 通用的彩色转灰度公式
    — Gray = R * 0.299 + G * 0.587 + B * 0.114
  • 可以把图像中每个点的R、G、B值,都分别设置为该点用上述公式计算出的Gray值,则彩色图像变为灰度图像

流程

  1. 读图片(要先把源图片放到读路径下)
  2. 根据图片宽度和高度开两重循环
    ①读取指定坐标的像素点颜色
    ②计算灰度值,并将RGB值都赋值为该灰度值
    ③设置指定坐标的像素点颜色
  3. 保存图片

代码:

#include<iostream>
#include<atlimage.h>void colorToGray(COLORREF& c)//E2:把彩色图像修改成灰度图像
{BYTE r = GetRValue(c);BYTE g = GetGValue(c);BYTE b = GetBValue(c);BYTE gray = r * 0.299 + g * 0.587 + b * 0.114;//彩色转灰度图像公式c = RGB(gray, gray, gray);//c中存储转换后的灰度值
}int main()
{LPCTSTR srcFilePath = _T("1a.jpg");LPCTSTR destFilePath = _T("3c.jpg");CImage srcImage;srcImage.Load(srcFilePath);int width, high;width = srcImage.GetWidth();high = srcImage.GetHeight();for (int x = 0; x < width; x++){for (int y = 0; y < high; y++){COLORREF c = srcImage.GetPixel(x, y);colorToGray(c);//调用E2函数,处理颜色srcImage.SetPixel(x, y, c);}}srcImage.Save(destFilePath);return 0;
}

E3. 反色

在这里插入图片描述
分析:

  • 对于某个点的颜色(R, G, B)而言,对应的反色是(255-R, 255-G, 255-B)

流程:

  1. 读图片(要先把源图片放到读路径下)
  2. 根据图片宽度和高度开两重循环
    ①读取指定坐标的像素点颜色(R, G, B)
    ②把(255-R, 255-G, 255-B)赋值为该点反色值
    ③设置指定坐标的像素点颜色
  3. 保存图片

代码:

#include<iostream>
#include<atlimage.h>void invertColor(COLORREF& c)//E3:反色
{BYTE r = GetRValue(c);BYTE g = GetGValue(c);BYTE b = GetBValue(c);c = RGB(255 - r, 255 - g, 255 - b);//对该像素点的rgb值进行反色处理
}int main()
{LPCTSTR srcFilePath = _T("1a.jpg");LPCTSTR destFilePath = _T("4d.jpg");CImage srcImage;srcImage.Load(srcFilePath);int width, high;width = srcImage.GetWidth();high = srcImage.GetHeight();for (int x = 0; x < width; x++){for (int y = 0; y < high; y++){COLORREF c = srcImage.GetPixel(x, y);invertColor(c);//调用E3函数,处理颜色srcImage.SetPixel(x, y, c);}}srcImage.Save(destFilePath);return 0;
}

E4. 左右颠倒图像

在这里插入图片描述
方法一
分析:

  • 获取源图像的像素点颜色后,新建一个CImage类对象作为目标图像,以免直接修改了源图片,导致重叠
  • 左右颠倒相当于获取颜色从左到右,设置颜色从右到左(x轴正方向开始到负方向开始)

流程:

  1. 读图片(要先把源图片放到读路径下)
  2. 根据图片宽度和高度开两重循环
    ①获取指定坐标的像素点颜色(R, G, B)
    ②把获取的指定坐标像素点颜色设置为目标图像从x轴负方向开始的像素点颜色(获取x = 0,设置x = width - x - 1)(-1的原因是因为循环从0开始)
  3. 保存图片

代码:

#include<iostream>
#include<atlimage.h>int main()
{LPCTSTR srcFilePath = _T("11a.jpg");LPCTSTR destFilePath = _T("22b.jpg");CImage srcImage;//创建源图像的对象CImage srcImage2;//创建目标图像的对象srcImage.Load(srcFilePath);//加载srcImage的源图像srcImage2.Load(srcFilePath);//加载srcImage2的源图像int width, high;width = srcImage.GetWidth();high = srcImage.GetHeight();for (int x = 0; x < width; x++){for (int y = 0; y < high; y++){COLORREF c = srcImage.GetPixel(x, y);//从源图像中获取像素点的颜色srcImage2.SetPixel(width - x - 1, y, c);//从x负方向设置目标图像的像素点的颜色}}srcImage2.Save(destFilePath);//保存目标图像return 0;
}

方法二
分析:

  • 图像x轴最左端坐标(x, y)和x轴右端坐标(width - x - 1, y)互换
  • 高度一样,y轴不变

流程:

  1. 读图片(要先把源图片放到读路径下)
  2. 根据图片宽度和高度开两重循环
    ①获取指定坐标的像素点颜色(R, G, B)
    ②定义一个新COLORREF对象c1
    ③将c和c1的像素点颜色交换
  3. 保存图片

代码:

#include<iostream>
#include<atlimage.h>int main()
{LPCTSTR srcFilePath = _T("11a.jpg");LPCTSTR destFilePath = _T("222b.jpg");CImage srcImage;srcImage.Load(srcFilePath);int width, high;width = srcImage.GetWidth();high = srcImage.GetHeight();for (int x = 0; x < width / 2; x++)//除以2,互换一半即可,若不除以2会导致又重新互换回去{for (int y = 0; y < high; y++)//高度不变,高度需一直遍历{COLORREF c = srcImage.GetPixel(x, y);//从源图像中获取像素点的颜色COLORREF c1 = srcImage.GetPixel(width - x - 1, y);//创建一个新COLORREF对象,用于从源图像x轴最右端获取指定坐标像素点的颜色srcImage.SetPixel(x, y, c1);//将x轴左边图像像素点的颜色换成x轴右边图像像素点的颜色srcImage.SetPixel(width - x - 1, y, c);//将x轴右边图像像素点的颜色换成x轴左边图像像素点的颜色}	}srcImage.Save(destFilePath);return 0;
}

进阶篇–1

A. 图像的快速读写访问

A1. 图像读写存在的问题

  • GetPixel和SetPixel的速度太慢,处理大图时耗时太长
  • 解决方案:利用GetBits方法,获取图像的首字节地址,通过指针访问图像的颜色信息

A2. 图像文件在内存中的存储格式

  • 以24位深度Top-down图像为例
    在这里插入图片描述
    Top-down和Button-up
  • 图像在内存中的两种不同格式
  • Top-down:图像的缓冲区从顶层行的像素开始,图像的底层行是图像缓冲区的最后一行
  • Buttom-up:图像的缓冲区从底层行的像素开始,图像的顶层行是图像缓冲区的最后一行

CImage类快速读写常用方法

  • GetBPP():返回图像位深度,常见的24位jpg图片的返回值是24
  • GetPitch():图像一行的字节数,对于Top-down类型图片,值是正的;对于Buttom-up类型图片,值是负的
  • GetBits():返回指向图像左上角像素点的指针(无论是Top-down还是Buttom-up)

A3. 使用GetBits的细节问题

  • 函数原型
    void * CImage::GetBits()
  • 如何访问图像(x,y)坐标像素点的颜色?
    — 即第y行第x列像素点,假设图像CImage类对象是img
    — 需要利用GetBits、GetPitch、GetBPP这三个方法的返回值
    — Top-Down图像和Buttom-Up图像一样有效

int bpp = img.GetBPP();
int pitch = img.GetPitch();
BYTE *pData = (BYTE *)img.GetBits();
BYTE b = *(pData + y * pitch + x * bpp / 8 + 0);
BYTE g = *(pData + y * pitch + x * bpp / 8 + 1);
BYTE r = *(pData + y * pitch + x * bpp / 8 + 2);

  • 注意:同一个像素点的三原色分量在内存中存放的顺序是B、G、R
  • 通过指针访问,也可以修改颜色,这样就取代了GetPixel和SetPixel

A4. 使用快速读写把彩色图片变成灰度图片

在这里插入图片描述
分析:

  • 600多万像素的图片,如果使用GetPixel和SetPixel方法,需要82秒时间(不同电脑不一样)才能处理完所有像素点。使用图像指针将把运行时间降低至640毫秒。(同一台计算机上运行)

流程:

  1. 读图片(要先把源图片放到读路径下)
  2. 根据图片宽度和高度开两重循环
    ①通过指针访问图像(x,y)坐标像素点的颜色RGB值
    ②通过公式计算灰度值
    ③对指针指向的值进行赋值,从而改变源图像
  3. 保存图片

代码:

#include<iostream>
#include<atlimage.h>int main()
{//1 获取和设置图像LPCTSTR srcFilePath = _T("3.jpg");LPCTSTR destFilePath = _T("3c.jpg");//2 加载图像CImage srcImage;srcImage.Load(srcFilePath);//3 获取图像的宽度和高度int width, high;width = srcImage.GetWidth();high = srcImage.GetHeight();//4 得到位深度和一行字节数int bpp = srcImage.GetBPP();//获取图像像素点的位数;bpp / 8 获取像素点的字节数int pitch = srcImage.GetPitch();//获取一行的字节数//5 获取首字节地址BYTE* pData = (BYTE*)srcImage.GetBits();//获取图像首字节的地址//6 遍历图像for (int x = 0; x < width; x++){for (int y = 0; y < high; y++){//通过指针访问图像(x,y)坐标像素点的颜色RGB值,相邻两个像素点之间相差3个字节BYTE b = *(pData + y * pitch + x * bpp / 8 + 0);//指针访问像素点颜色的B值BYTE g = *(pData + y * pitch + x * bpp / 8 + 1);//指针访问像素点颜色的G值BYTE r = *(pData + y * pitch + x * bpp / 8 + 2);//指针访问像素点颜色的R值BYTE gray = r * 0.299 + g * 0.587 + b * 0.114;//彩色转灰度图像公式//对指针指向的值进行赋值,从而改变源图像*(pData + y * pitch + x * bpp / 8 + 0) = gray;*(pData + y * pitch + x * bpp / 8 + 1) = gray;*(pData + y * pitch + x * bpp / 8 + 2) = gray;}	}srcImage.Save(destFilePath);return 0;
}

B. 图像缩放

  1. 改变图像的分辨率
    在这里插入图片描述

图像缩放的步骤:

  1. 调用Create函数创建一张缩放后尺寸的新图
  2. 调用SetStretchBltMode函数设置清晰的缩放模式
  3. 调用Draw函数或者StretchBlt函数把源图Src的数据缩放写入新图Dest中
  4. 调用Save函数保存图片

代码:

#include<iostream>
#include<atlimage.h>void getBGR(int x, int y, int bpp, int pitch, BYTE* pData)//获取图像像素点颜色的函数
{BYTE b = *(pData + y * pitch + x * bpp / 8 + 0);BYTE g = *(pData + y * pitch + x * bpp / 8 + 1);BYTE p = *(pData + y * pitch + x * bpp / 8 + 2);
}int main()
{LPCTSTR srcFilePath = _T("3.jpg");LPCTSTR destFilePath = _T("4.jpg");CImage srcImage;//创建源图像CImage destImage;//创建目标图像srcImage.Load(srcFilePath);int width, high;width = srcImage.GetWidth();high = srcImage.GetHeight();int bpp, pitch;bpp = srcImage.GetBPP();pitch = srcImage.GetPitch();BYTE* pData = (BYTE*)srcImage.GetBits();//1 调用Create函数创建一张缩放后尺寸的新图destImage.Create(width / 2, high / 2, bpp);//宽度,高度,位深度;创建的目标图片的大小和位深度//2 调用SetStretchBltMode函数设置清晰的缩放模式SetStretchBltMode(destImage.GetDC(), COLORONCOLOR);//COLORONCOLOR——清晰模式;GetDC()——关联srcImage和destImage两个图像的数据destImage.ReleaseDC();//释放一下(因为有GetDC)//3 调用Draw函数或者StretchBlt函数把源图Src的数据缩放写入新图Dest中srcImage.Draw(destImage.GetDC(), 0, 0, width / 2, high / 2);//填充数据:目标图像的填充位置与大小destImage.ReleaseDC();//释放一下(因为有GetDC)//4 调用Save函数保存图片destImage.Save(destFilePath);return 0;
}

注意事项:

  • 每次用GetDC,都要ReleaseDC,否则会报错
  • 使用Create创建图像,需要指定宽度、高度和深度
  • COLORONCOLOR模式保证图像缩放时不会产生失真,必须写在Draw函数之前
  • Draw函数或者StretchBlt函数
    — 第1个参数表示目标图像的句柄(handle)
    — 第2~5个参数分别表示目标图像左上角的x轴坐标、y轴坐标、目标图像的宽度、高度,即定义了要把源图像写到目标图像的哪一块位置
    — 第6~9个参数分别表示源图像左上角的x轴坐标、y轴坐标、源图像的宽度、高度,即定义了要把源图像的哪一块内容写到目标图像中。默认值是写整张源图像

C. 图像截取

  • 把图像中的一部分内容保存成另一幅图
    在这里插入图片描述

图像截取的步骤:

  1. 调用Create函数创建一张新图
  2. 调用Draw函数把源图src的部分数据写入新图dest中
  3. 调用Save函数保存图片

代码:

#include<iostream>
#include<atlimage.h>void getBGR(int x, int y, int bpp, int pitch, BYTE* pData)
{BYTE b = *(pData + y * pitch + x * bpp / 8 + 0);BYTE g = *(pData + y * pitch + x * bpp / 8 + 1);BYTE p = *(pData + y * pitch + x * bpp / 8 + 2);
}int main()
{LPCTSTR srcFilePath = _T("3.jpg");LPCTSTR destFilePath = _T("5.jpg");CImage srcImage;CImage destImage;srcImage.Load(srcFilePath);int width, high;width = srcImage.GetWidth();high = srcImage.GetHeight();int bpp, pitch;bpp = srcImage.GetBPP();//1 调用Create函数创建一张新图destImage.Create(width / 2, high / 2, bpp);//2 调用Draw函数把源图src的部分数据写入新图dest中srcImage.Draw(destImage.GetDC(), 0, 0, width / 2, high / 2, width / 4, high / 8, width / 2, high / 2);//前四个参数表示:填充到目标图像的哪个位置以及填充的大小;后四个参数表示:从源图像哪个位置开始截取以及截取的大小destImage.ReleaseDC();//3 调用Save函数保存图片destImage.Save(destFilePath);return 0;
}

D. 图像拼接

  • 把多张图片拼接在一起,形成一张新图
    在这里插入图片描述

图片拼接的步骤:

  1. 调用Create函数创建一张能够放得下拼接后图像大小的新图
  2. 调用Draw函数多次将原图数据写入到新图中合适的位置,实现图像拼接
  3. 调用Save函数保存图片

代码:

#include<iostream>
#include<atlimage.h>void getBGR(int x, int y, int bpp, int pitch, BYTE* pData)
{BYTE b = *(pData + y * pitch + x * bpp / 8 + 0);BYTE g = *(pData + y * pitch + x * bpp / 8 + 1);BYTE p = *(pData + y * pitch + x * bpp / 8 + 2);
}int main()
{LPCTSTR srcFilePath = _T("5.jpg");LPCTSTR destFilePath = _T("6.jpg");CImage srcImage;CImage destImage;srcImage.Load(srcFilePath);int width, high;width = srcImage.GetWidth();high = srcImage.GetHeight();int bpp, pitch;bpp = srcImage.GetBPP();//1 调用Create函数创建一张新图destImage.Create(width * 2, high, bpp);//2 调用Draw函数把源图src的部分数据写入新图dest中srcImage.Draw(destImage.GetDC(), 0, 0, width, high, 0, 0, width, high);//第一次截取并填充了左半部分destImage.ReleaseDC();srcImage.Draw(destImage.GetDC(), width, 0, width, high, 0, 0, width, high);//第二次截取并填充了右半部分,填充起始点发生变化destImage.ReleaseDC();//3 调用Save函数保存图片destImage.Save(destFilePath);return 0;
}

案例

E1. 分隔

把图片变成m行n列的图,要求彩色图像和灰度图像间隔。m和n由键盘输入。例如当m=4,n=5时:
在这里插入图片描述
方法一
分析:

  • 用两层嵌套循环填充指定的行和列
  • 寻找行与列的填充规律以及彩色图像和灰度图像的填充规律

流程:

  1. 调用Create函数创建一张和源图像大小相等的新图
  2. 先判断总列数是奇数还是偶数,再判断当前列数和行数的奇偶
  3. 调用Draw函数多次将原图数据写入到新图中合适的位置,实现图像拼接
  4. 调用Save函数保存图片

代码:

#include<iostream>
#include<atlimage.h>void colorToGray(int x, int y, int bpp, int pitch, BYTE* pData)
{BYTE b = *(pData + y * pitch + x * bpp / 8 + 0);BYTE g = *(pData + y * pitch + x * bpp / 8 + 1);BYTE r = *(pData + y * pitch + x * bpp / 8 + 2);BYTE gray = r * 0.299 + g * 0.587 + b * 0.114;*(pData + y * pitch + x * bpp / 8 + 0) = gray;*(pData + y * pitch + x * bpp / 8 + 1) = gray;*(pData + y * pitch + x * bpp / 8 + 2) = gray;
}void traversal(int width, int high, int bpp, int pitch, BYTE* pData)
{for (int x = 0; x < width; x++){for (int y = 0; y < high; y++){colorToGray(x, y, bpp, pitch, pData);}}
}int main()
{LPCTSTR srcFilePath = _T("5.jpg");LPCTSTR destFilePath = _T("5c.jpg");CImage srcImage, srcImage2;CImage destImage;srcImage.Load(srcFilePath);srcImage2.Load(srcFilePath);int width, high;width = srcImage.GetWidth();high = srcImage.GetHeight();int bpp, pitch;bpp = srcImage.GetBPP();pitch = srcImage.GetPitch();BYTE* pData = (BYTE*)srcImage.GetBits();int m, n; std::cin >> m >> n;destImage.Create(width, high, bpp);for (int i = 0; i < m; i++){for (int j = 0; j < n; j++){if (n % 2 != 0)//若总列数是奇数{if (j % 2 != 0 && i % 2 == 0)//若列数是奇数且行数为偶数,则位灰度图{traversal(width, high, bpp, pitch, pData);srcImage.Draw(destImage.GetDC(), (j * width) / n, (i * high) / m, width / n, high / m, 0, 0, width, high);destImage.ReleaseDC();}else if (j % 2 == 0 && i % 2 != 0)//若列数为偶数且行数为奇数,则为灰度图{traversal(width, high, bpp, pitch, pData);srcImage.Draw(destImage.GetDC(), (j * width) / n, (i * high) / m, width / n, high / m, 0, 0, width, high);destImage.ReleaseDC();}else//其他均为彩色图{srcImage2.Draw(destImage.GetDC(), (j * width) / n, (i * high) / m, width / n, high / m, 0, 0, width, high);destImage.ReleaseDC();}}else//若总列数是偶数{if (j % 2 != 0)//若列数是奇数{traversal(width, high, bpp, pitch, pData);srcImage.Draw(destImage.GetDC(), (j * width) / n, (i * high) / m, width / n, high / m, 0, 0, width, high);destImage.ReleaseDC();}else//若列数是偶数{srcImage2.Draw(destImage.GetDC(), (j * width) / n, (i * high) / m, width / n, high / m, 0, 0, width, high);destImage.ReleaseDC();}}}}destImage.Save(destFilePath);return 0;
}

方法二
分析:

  • 用两层嵌套循环填充指定的行和列
  • 寻找行与列的填充规律以及彩色图像和灰度图像的填充规律

流程:

  1. 调用Create函数创建一张和源图像大小相等的新图
  2. 判断行和列坐标之和是偶数还是奇数,偶数为彩色图 ,奇数为灰度图
  3. 调用Draw函数多次将原图数据写入到新图中合适的位置,实现图像拼接
  4. 调用Save函数保存图片

代码:

#include<iostream>
#include<atlimage.h>int main()
{//获取和设置保存源图片和目标图像的路径LPCTSTR srcFilePath = _T("5.jpg");LPCTSTR destFilePath = _T("123.jpg");//创建类对象,源图像对象,灰度图对象,目标图像对象;加载源图像和灰度图CImage srcImage, grayImage, destImage;srcImage.Load(srcFilePath);grayImage.Load(srcFilePath); //获取宽度、高度、位深度、一行字节数、首字节地址int width = grayImage.GetWidth();int high = grayImage.GetHeight();int bpp = grayImage.GetBPP();int pitch = grayImage.GetPitch();BYTE* pData = (BYTE*)grayImage.GetBits();//输入m行n列int m, n; std::cin >> m >> n;//将彩色图变为灰度图for (int x = 0; x < width; x++){for (int y = 0; y < high; y++){BYTE b = *(pData + y * pitch + x * bpp / 8 + 0);BYTE g = *(pData + y * pitch + x * bpp / 8 + 1);BYTE r = *(pData + y * pitch + x * bpp / 8 + 2);int gray = r * 0.299 + g * 0.587 + b * 0.114;*(pData + y * pitch + x * bpp / 8 + 0) = gray;*(pData + y * pitch + x * bpp / 8 + 1) = gray;*(pData + y * pitch + x * bpp / 8 + 2) = gray;}}//创建一个“存放”目标图像的新窗口destImage.Create(n * width, m * high, bpp);按照输入的行和列遍历每个目标图像的位置for (int x = 0; x < m; x++)//行{for (int y = 0; y < n; y++)//列{if ((x + y) % 2 == 0)//如果“行+列”的坐标为偶数,绘制源图{srcImage.Draw(destImage.GetDC(), y * width, x * high, width, high, 0, 0, width, high);//从源图像中获取彩色像素点destImage.ReleaseDC();//释放}else//如果“行+列”的坐标为奇数,绘制灰度图{grayImage.Draw(destImage.GetDC(), y * width, x * high, width, high, 0, 0, width, high);//从灰度图中获取彩色像素点destImage.ReleaseDC();}}}destImage.Save(destFilePath);//保存目标图像到指定路径return 0;
}

E2. 平铺

将下图平铺成5000*5000分辨率的图像
在这里插入图片描述
分析:

  • 用两层嵌套循环填充计算最大的行和列
  • 宽度和高度随着填充图像的个数而变化

流程:

  1. 调用Create函数创建一张分辨率为5000*5000的新图
  2. 用总宽度、高度除以一张目标图像的宽度、高度,计算出指定窗口的容纳量
  3. 调用Draw函数循环将原图数据写入到新图中合适的位置,实现图像拼接
  4. 调用Save函数保存图片

代码:

#include<iostream>
#include<atlimage.h>int main()
{//读取和保存源图像和目标图像路径LPCTSTR srcFilePath = _T("5.jpg");LPCTSTR destFilePath = _T("56.jpg");//创建CImage类对象及加载图像CImage srcImage, destImage;srcImage.Load(srcFilePath);//获取图像的宽度、高度及位深度int width, high, bpp;width = srcImage.GetWidth();high = srcImage.GetHeight();bpp = srcImage.GetBPP();//创建一个目标图像窗口destImage.Create(5000, 5000, bpp);//创建一个分辨率为5000*5000的窗口//遍历图像for (int x = 0; x <= ceil(5000 / width); x++)//ceil()——取整函数{for (int y = 0; y <= ceil(5000 / high) + 1; y++){srcImage.Draw(destImage.GetDC(), width * x, high * y, width, high, 0, 0, width, high);//目标图像的绘制位置随x,y(已填充个数)的变化而变化destImage.ReleaseDC();}}destImage.Save(destFilePath);return 0;
}

E3. 变色

将图中所有红色的花变成蓝色的花
在这里插入图片描述
分析:

  • 找出图中偏红的颜色
  • 将红色R值与蓝色B值交换

步骤:

  1. 读图片(要先把源图片放到读路径下)
  2. 根据图片宽度和高度开两重循环
    ①通过指针访问图像(x,y)坐标像素点的颜色RGB值
    ②判断偏红的像素点
    ③对指针指向的值进行赋值,从而改变源图像,交换红色和蓝色
  3. 保存图片

代码:

#include<iostream>
#include<atlimage.h>void colorChange(int width, int high, int bpp, int pitch, BYTE* pData)//变色函数
{for (int x = 0; x < width; x++){for (int y = 0; y < high; y++){//获取每个坐标像素点的颜色BYTE b = *(pData + y * pitch + x * bpp / 8 + 0);BYTE g = *(pData + y * pitch + x * bpp / 8 + 1);BYTE r = *(pData + y * pitch + x * bpp / 8 + 2);if (r > g && r > b)//偏红的像素点{//R值和B值交换*(pData + y * pitch + x * bpp / 8 + 0) = r;*(pData + y * pitch + x * bpp / 8 + 1) = g;*(pData + y * pitch + x * bpp / 8 + 2) = b;}}}
}int main()
{LPCTSTR srcFilePath = _T("Flower.jpg");LPCTSTR destFilePath = _T("Flower2.jpg");CImage srcImage;srcImage.Load(srcFilePath);int width, high;width = srcImage.GetWidth();high = srcImage.GetHeight();int bpp, pitch;bpp = srcImage.GetBPP();pitch = srcImage.GetPitch();BYTE* PData = (BYTE*)srcImage.GetBits();colorChange(width, high, bpp, pitch, PData);srcImage.Save(destFilePath);return 0;
}

进阶篇–2

A. RGB和HSV

像素颜色的两种表示方式

  • RGB:使用红、绿、蓝三种颜色表示,范围都在0~255之间
  • HSV:
    — H:色度,取值范围[0,360)
    — S:饱和度,取值范围[0,1]
    — V:亮度,取值范围]0,1]
    在这里插入图片描述

RGB到HSV的转换公式

在这里插入图片描述

HSV到RGB的转换公式

在这里插入图片描述

HSV和RGB使用的类

在这里插入图片描述

B. 两个转换函数

RGB到HSV转换函数

函数声明:
HSV RGB2HSV(COLORREF c)

函数功能:
把COLORREF类对象c的颜色信息转换成一个HSV类对象返回。

代码:

#include<iostream>
#include<atlimage.h>
#include<cmath>
#include<algorithm>//RGB->HSV
class HSV//HSV类
{
public:double H, S, V;//定义成员变量HSV() {}//无参构造函数HSV(double h, double s, double v) :H(h), S(s), V(v) {}//有参构造函数且初始化
};HSV RGB2HSV(COLORREF c)//RGB至HSV转换函数
{//从COLORREF类对象c中获取颜色RGB值BYTE R = GetRValue(c);BYTE G = GetGValue(c);BYTE B = GetBValue(c);double H, S, V;//转换公式R = R / 255, G = G / 255, B = B / 255;V = max(R, G, B);if (V != 0){S = (V - min(R, G, B)) / V;if (V = R){H = (60 * (G - B)) / V - min(R, G, B);if (H < 0){H = H + 360;return HSV(H, S, V);}return HSV(H, S, V);}if (V = G){H = 120 + ((60 * (B - R)) / (V - min(R, G, B)));if (H < 0){H = H + 360;return HSV(H, S, V);}return HSV(H, S, V);}if (V = B){H = 240 + ((60 * (R - G)) / (V - min(R, G, B)));if (H < 0){H = H + 360;return HSV(H, S, V);}return HSV(H, S, V);}}else//V = 0,此时RGB均为0{S = 0;H = 0;return HSV(H, S, V);}
}

HSV到RGB转换函数

函数声明:
COLORREF HSV2RGB(HSV hsv)

函数功能:
把HSV类对象hsv的颜色信息转换成一个COLORREF类对象返回。

代码:

//HSV->RGB
COLORREF HSV2RGB(HSV hsv)
{//定义变量BYTE R, G, B;double H, S, V;H = hsv.H;S = hsv.S;V = hsv.V;//转换公式double C, X, m;C = V * S;X = C * (1 - (abs(((int)H / 60) % 2 - 1)));m = V - C;if (H >= 0 && H < 60){R = C;G = X;B = 0;R = (R + m) * 255;G = (G + m) * 255;B = (B + m) * 255;//返回COLORREF类型的值COLORREF c;c = RGB(R, G, B);return c;}if (H >= 60 && H < 120){R = X;G = C;B = 0;R = (R + m) * 255;G = (G + m) * 255;B = (B + m) * 255;//返回COLORREF类型的值COLORREF c;c = RGB(R, G, B);return c;}if (H >= 120 && H < 180){R = 0;G = C;B = X;R = (R + m) * 255;G = (G + m) * 255;B = (B + m) * 255;//返回COLORREF类型的值COLORREF c;c = RGB(R, G, B);return c;}if (H >= 180 && H < 240){R = 0;G = X;B = C;R = (R + m) * 255;G = (G + m) * 255;B = (B + m) * 255;//返回COLORREF类型的值COLORREF c;c = RGB(R, G, B);return c;}if (H >= 240 && H < 300){R = X;G = 0;B = C;R = (R + m) * 255;G = (G + m) * 255;B = (B + m) * 255;//返回COLORREF类型的值COLORREF c;c = RGB(R, G, B);return c;}if (H >= 300 && H < 360){R = C;G = 0;B = X;R = (R + m) * 255;G = (G + m) * 255;B = (B + m) * 255;//返回COLORREF类型的值COLORREF c;c = RGB(R, G, B);return c;}
}
/*
提取信息:HSV——对象名.成员函数RGB——GetRVaule(对象名)
*/

http://chatgpt.dhexx.cn/article/iQvTkaNY.shtml

相关文章

Cimage

本系列文章由zhmxy555编写&#xff0c;转载请注明出处。 http://blog.csdn.net/zhmxy555/article/details/7422922 作者&#xff1a;毛星云 邮箱&#xff1a; happylifemxyqq.com 欢迎邮件交流编程心得 我们知道&#xff0c;Visual C中的CBitmap类的功能简直太弱小了&am…

【无标题】c++ MFC图像处理CImage类常用操作代码

原文作者&#xff1a;aircraft 原文地址&#xff1a;https://www.cnblogs.com/DOMLX/p/9598974.html 我看了一下发现关于c下的CImage图像处理类 的图像处理相关的介绍真的是比较少&#xff0c;因为我要做大二的数据结构的课程设计&#xff0c;要用纯c语言去实现&#xff08;老…

C++,CImage类的建立方法(可以打开图像和保存)

建立CImage类&#xff08;以vs2015为例&#xff09; 一&#xff0c; 新建一个MFC项目&#xff1a;名字为 image3 二&#xff0c; 单个文档&#xff0c;MFC标准&#xff0c;然后完成。 三&#xff0c;打开应用程序的 stdafx.h 文件添加 CImage 类的包含文件&#xff1a; #incl…

VB.net 进程通信中FindWindow、FindWindowEX、SendMessage函数的理解

目录 一、代码背景 二、主要工具 三、函数解析 1、FindWindow&#xff1a; 2、 FindWindowEx&#xff1a; 3、SendMessage&#xff1a; 四、具体代码示例&#xff1a; 1、第一部分功能&#xff1a; A、接收端&#xff1a; B、发送端 C、运行测试 2.第二部分功能&…

C#-FindWindow的用法

C# FindWindow用法 函数功能&#xff1a;该函数获得一个顶层窗口的句柄&#xff0c;该窗口的类名和窗口名与给定的字符串相匹配。 这个函数不查找子窗口。在查找时不区分大小写。 函数型&#xff1a;HWND FindWindow&#xff08;LPCTSTR IpClassName&#xff0c;LPCTSTR IpWi…

vb.net中FindWindow方法的使用

问题描述 遇到的问题是&#xff0c;需要判断MsgBox是否已经弹出&#xff0c;如果已经弹出就不要重复弹出了。 解决方案&#xff1a; 利用FindWindow方法判断MsgBox是否已经出现 MsgBox的本质就是一个窗体&#xff0c;有标题和内容&#xff0c;可以使用FindWindow这个API函数去…

C# FindWindow的用法

找了一大堆C#怎么用FindWindowAPI函数不多说,请看步骤. 创建好WinForm窗口,如果不会创建的话,在图下面有. 项目名字和位置这个自己设置,下面那个框架,目前现在出5.0了,为了演示实例,就4.6吧. 创建完成后,把自己窗口设置一下

FindWindow ,GetWindowThreadProcessId , OpenProcess 和ReadProcessMemory

文章目录 FindWindow函数功能&#xff1a;函数声明&#xff1a;第一个参数第二个参数返回值注意&#xff1a;GetWindowThreadProcessId函数功能函数声明第一个参数&#xff1a;第二个参数&#xff1a;返回值代码实现OpenProcess函数功能&#xff1a;函数声明&#xff1a;第一个…

【CV系列】主动轮廓模型snake及其应用

DATE: 2019.5.30 前言 主动轮廓模型(Active Contour Model)&#xff0c;又被称为Snake&#xff0c;是由Andrew Blake教授提出的一种目标轮廓描述方法&#xff0c;主要应用于基于形状的目标分割。该模型的优越之处在于它对于范围广泛的一系列视觉问题给出了统一的解决方法,在最…

图像分割之Snake主动轮廓模型(Matlab代码)

示例演示 如果在中文搜索的话&#xff0c;一般会找到《数字图像处理-图像分割&#xff1a;Snake主动轮廓模型 Matlab代码及运行结果》。里面有句代码&#xff0c;千万别用&#xff0c;否则出不来效果。&#xff08;别问我怎么知道的&#xff09; % 转化为双精度型 %I im2doub…

Snake活动轮廓模型Matlab实现

1. Snake模型 人为地在图像感兴趣的区域&#xff08;ROI&#xff09;上给出初始轮廓曲线&#xff0c;最小化一个能量函数&#xff0c;使轮廓曲线在图像中运动&#xff08;变形&#xff09;&#xff0c;最终逼近该区域的边界。 设v(s)[x(s),y(s)]为活动轮廓线&#xff0c;s∈[0,…

snake主动轮廓模型

模型&#xff1a;一条可变形的参数曲线及相应的能量函数&#xff0c;以最小化能量函数为目标&#xff0c;控制参数曲线变形&#xff0c;具有最小能量的闭合曲线即是目标轮廓。 snake模型调和了上层知识和底层图像特征矛盾。 上层知识指物体形状。表示内部力。 底层图像特征是局…

Snake活动轮廓模型

1. Snake模型 人为地在图像感兴趣的区域&#xff08;ROI&#xff09;上给出初始轮廓曲线&#xff0c;最小化一个能量函数&#xff0c;使轮廓曲线在图像中运动&#xff08;变形&#xff09;&#xff0c;最终逼近该区域的边界。 设v(s)[x(s),y(s)]为活动轮廓线&#xff0c;s∈[0,…

基于边缘的主动轮廓模型——从零到一用python实现snake

从零到一实现snake算法 1、Snake算法原理2、基于曲线演化的实现方法2.1演化方程推导2.2离散化过程2.3 代码实现 3、基于水平集的实现方法4、讨论与分析源码地址[snake](https://github.com/woshimami/snake) 1、Snake算法原理 Kass等人1最早于1988年提出了主动轮廓模型&#x…

主动轮廓模型snake

原理概述 snake模型将图像分割问题转换为求解能量泛函最小值的问题。主要思路是构造能量函数进行迭代后&#xff0c;轮廓曲线由初始位置逐渐向使能量函数最小&#xff08;局部极小&#xff09;的图像边缘逼近&#xff0c;最终分割出目标。 曲线理论 假设一条光滑封闭曲线 C …

腾讯电脑管家,vs安装文件报成木马,还能信吗?

今天在公司安装vs2013&#xff0c;安装过程中腾讯公司的产品“电脑管家”提示有新版本&#xff0c;没有犹豫的点了升级&#xff0c;完成后直接在管家主界面上点了“全面体检”按钮&#xff0c;这一点不要紧&#xff0c;报告有一个木马&#xff0c;看紧看一下“详情”&#xff0…

计算机windows8黑屏怎么办,Win8电脑开机黑屏只有鼠标光标怎么解决

有些win8系统用户在开机的时候&#xff0c;遇到了黑屏的情况&#xff0c; 整个屏幕上面只有一个闪烁的鼠标光标&#xff0c;导致无法进入到系统桌面&#xff0c;遇到这样的情况该怎么解决呢&#xff1f;现在给大家分享一下Win8电脑开机黑屏只有鼠标光标的具体解决方法吧。 Win8…

解决ValueError: Cannot run multiple SparkContexts at once; existing SparkContext

一、问题描述 创建sparkcontext和SparkSession&#xff0c;连接spark集群时报错&#xff0c;如题ValueError: Cannot run multiple SparkContexts at once; existing SparkContext。 from pyspark.sql import SparkSession from pyspark.sql import functions as F from pysp…

1、Qt线程(二):继承QThread,重写run

一、功能说明 1、通过继承QThread&#xff0c;重写run的方式实现多线程 2、点击“开始”按钮启动子线程&#xff0c;同时通过信号槽的方式给子线程发送“开始”字符串&#xff1b; 3、子线程每隔1秒向主线程发送累加数&#xff1b; 4、点击"停止"按钮&#xff0c…

诡异的RunOnce病毒启动项和神奇的URL Protocol

整理磁盘发现之前有个有趣的流氓招数忘记分享了,每次看到新鲜的东东都感慨黑暗势力的层出不穷的招数,比某些安全厂商是不是自相残杀好多了.电脑日常使用过程中我们经常输入开头为http ftp,点击诸如ed2k的链接,每个链接的背后都会执行相应的功能.如http 通过iexplore.exe,ed2k通…