方案有问题,总有些无法检测,线扫相机,相机端给的建议是,拼图,需要拼图,但是甲方说图涉密,所以随机拿了几张图测试。
记录粘在下方。感谢DEEPSEKK,具体正确的问题,就可以得到这答案。
但是正儿八经的,是从回调拿的图,需要写一个队列存储。空了需要实现
原图大小:
函数1:
//第一张图的后2000(AddHeight )行,替换第二张图的前2000(AddHeight )行
void ProcessImagesold2()
{
// 初始化GDI+
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
int AddHeight = 2000;
int numImages = 5; // 假设有5张图像,根据实际情况调整
for (int i = 0; i < numImages - 1; ++i)
{
CImage srcImg, destImg;
CString srcPath, destPath;
// 假设图像文件名为image1.bmp, image2.bmp,... image5.bmp
srcPath.Format(_T("%d.bmp"), i + 1);
destPath.Format(_T("%d.bmp"), i + 2);
// 加载源图像和目标图像
if (srcImg.Load(srcPath) != S_OK || destImg.Load(destPath) != S_OK)
{
AfxMessageBox(_T("Failed to load image"));
continue;
}
// 检查图像参数是否匹配
if (srcImg.GetWidth() != destImg.GetWidth() ||
srcImg.GetBPP() != destImg.GetBPP())
{
AfxMessageBox(_T("Image parameters mismatch"));
continue;
}
// 检查图像高度是否足够
if (srcImg.GetHeight() < AddHeight)
{
AfxMessageBox(_T("Source or destination image height is insufficient"));
continue;
}
// 获取像素数据和每行字节数
BYTE* srcData = (BYTE*)srcImg.GetBits();
BYTE* destData = (BYTE*)destImg.GetBits();
int srcPitch = srcImg.GetPitch();
int destPitch = destImg.GetPitch();
if (!srcData || !destData)
{
AfxMessageBox(_T("Failed to get image bits"));
continue;
}
// 将源图像的最后200行复制到目标图像的前200行
int srcStartY = srcImg.GetHeight() - AddHeight; // 源图像的最后200行起始位置
for (int y = 0; y < AddHeight; ++y)
{
memcpy(destData + y * destPitch, srcData + (srcStartY + y) * srcPitch, srcImg.GetWidth() * (srcImg.GetBPP() / 8));
}
destPath.Format(_T("output_image%d.bmp"), i + 1);
// 保存处理后的图像
if (destImg.Save(destPath, Gdiplus::ImageFormatBMP) != S_OK)
{
AfxMessageBox(_T("Failed to save image"));
}
}
// 关闭GDI+
Gdiplus::GdiplusShutdown(gdiplusToken);
}
效果如图
此处的output1图是由图像1的最后2000行,作为起始的2000行,后面的用图像2保持不变填充得到一个等大的图。其他类似。
函数2:
目的:前一张图的固定行高数,直接和第二张图合并在一起。
#include <atlimage.h>
#include <gdiplus.h>
#pragma comment(lib, “gdiplus.lib”)
void ProcessImages()
{
// 初始化GDI+
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
int AddHeight = 2000;
int numImages = 5; // 假设有5张图像,根据实际情况调整
for (int i = 0; i < numImages - 1; ++i)
{
CImage srcImg1, srcImg2, destImg;
CString srcPath1, srcPath2, destPath;
// 假设图像文件名为image1.bmp, image2.bmp,... image5.bmp
srcPath1.Format(_T("%d.bmp"), i + 1);
srcPath2.Format(_T("%d.bmp"), i + 2);
destPath.Format(_T("output_image%d.bmp"), i + 1);
// 加载第一张图像和第二张图像
if (srcImg1.Load(srcPath1) != S_OK || srcImg2.Load(srcPath2) != S_OK)
{
AfxMessageBox(_T("Failed to load image"));
continue;
}
// 获取图像尺寸和位深度
int width1 = srcImg1.GetWidth();
int height1 = srcImg1.GetHeight();
int width2 = srcImg2.GetWidth();
int height2 = srcImg2.GetHeight();
int bpp = srcImg1.GetBPP();
// 检查图像参数是否匹配
if (width1 != width2 || bpp != srcImg2.GetBPP())
{
AfxMessageBox(_T("Image parameters mismatch"));
continue;
}
// 检查图像高度是否足够
if (height1 < AddHeight)
{
AfxMessageBox(_T("Source image 1 height is less than 200"));
continue;
}
// 创建新图像(高度为 height2 + 200,宽度与输入图像一致)
if (!destImg.Create(width2, height2 + AddHeight, bpp))
{
AfxMessageBox(_T("Failed to create destination image"));
continue;
}
// 复制调色板(对于索引色图像)
if (srcImg1.IsIndexed())
{
int numColors = srcImg1.GetMaxColorTableEntries();
RGBQUAD* colorTable = new RGBQUAD[numColors];
srcImg1.GetColorTable(0, numColors, colorTable);
destImg.SetColorTable(0, numColors, colorTable);
delete[] colorTable;
}
// 获取像素数据和每行字节数
BYTE* srcData1 = (BYTE*)srcImg1.GetBits();
BYTE* srcData2 = (BYTE*)srcImg2.GetBits();
BYTE* destData = (BYTE*)destImg.GetBits();
int srcPitch1 = srcImg1.GetPitch();
int srcPitch2 = srcImg2.GetPitch();
int destPitch = destImg.GetPitch();
if (!srcData1 || !srcData2 || !destData)
{
AfxMessageBox(_T("Failed to get image bits"));
continue;
}
// 将第一张图像的最后200行复制到新图像的前200行
int srcStartY1 = height1 - AddHeight; // 第一张图像的最后200行起始位置
for (int y = 0; y < AddHeight; ++y)
{
memcpy(destData + y * destPitch, srcData1 + (srcStartY1 + y) * srcPitch1, width1 * (bpp / 8));
}
// 将第二张图像的全部行复制到新图像的剩余部分(200到height2 + 200行)
for (int y = 0; y < height2; ++y)
{
memcpy(destData + (AddHeight + y) * destPitch, srcData2 + y * srcPitch2, width2 * (bpp / 8));
}
// 保存新图像
if (destImg.Save(destPath, Gdiplus::ImageFormatBMP) != S_OK)
{
AfxMessageBox(_T("Failed to save image"));
}
}
// 关闭GDI+
Gdiplus::GdiplusShutdown(gdiplusToken);
}
效果如图
此处的output1图是由图像1的最后2000行,作为起始的2000行,后面的用图像2保持不变,两者合并在一起,得到一个大图。其他类似。
函数3:
假设有以下图像:
image1.bmp:宽度 5472,高度 3648。
image2.bmp:宽度 5472,高度 3648。
处理过程:
将 image1.bmp 的第 500 行起始的 2000 行复制到新图像的前 2000 行。
将 image2.bmp 的剩余部分(2000 行之后)复制到新图像的剩余部分。
保存新图像为 output_image1.bmp。
最终输出的 output_image1.bmp 高度为 3648,宽度为 5472。
代码说明
动态获取图像尺寸:
使用 CImage::GetWidth() 和 CImage::GetHeight() 获取图像的宽度和高度。
支持任意尺寸的图像(如宽度 5472,高度 3648)。
创建新图像:
新图像的高度为 下一张图像的高度,宽度与输入图像一致。
复制像素数据:
将前一张图像从第 500 行起始的 2000 行复制到下一张图像的开始 2000 行。
将下一张图像的剩余部分(2000 行之后)保持不变。
保存新图像:
使用 CImage::Save 保存生成的新图像。
#include <atlimage.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
void ProcessImages()
{
// 初始化GDI+
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
int numImages = 5; // 假设有5张图像,根据实际情况调整
for (int i = 0; i < numImages - 1; ++i)
{
CImage srcImg1, srcImg2, destImg;
CString srcPath1, srcPath2, destPath;
// 假设图像文件名为image1.bmp, image2.bmp,... image5.bmp
srcPath1.Format(_T("%d.bmp"), i + 1);
srcPath2.Format(_T("%d.bmp"), i + 2);
destPath.Format(_T("output_image%d.bmp"), i + 1);
// 加载前一张图像和下一张图像
if (srcImg1.Load(srcPath1) != S_OK || srcImg2.Load(srcPath2) != S_OK)
{
AfxMessageBox(_T("Failed to load image"));
continue;
}
// 获取图像尺寸和位深度
int width1 = srcImg1.GetWidth();
int height1 = srcImg1.GetHeight();
int width2 = srcImg2.GetWidth();
int height2 = srcImg2.GetHeight();
int bpp = srcImg1.GetBPP();
// 检查图像参数是否匹配
if (width1 != width2 || bpp != srcImg2.GetBPP())
{
AfxMessageBox(_T("Image parameters mismatch"));
continue;
}
// 检查前一张图像高度是否足够
if (height1 < 500 + 2000)
{
AfxMessageBox(_T("Source image 1 height is insufficient"));
continue;
}
// 检查下一张图像高度是否足够
if (height2 < 2000)
{
AfxMessageBox(_T("Source image 2 height is insufficient"));
continue;
}
// 创建新图像(高度为 height2,宽度与输入图像一致)
if (!destImg.Create(width2, height2, bpp))
{
AfxMessageBox(_T("Failed to create destination image"));
continue;
}
// 复制调色板(对于索引色图像)
if (srcImg1.IsIndexed())
{
int numColors = srcImg1.GetMaxColorTableEntries();
RGBQUAD* colorTable = new RGBQUAD[numColors];
srcImg1.GetColorTable(0, numColors, colorTable);
destImg.SetColorTable(0, numColors, colorTable);
delete[] colorTable;
}
// 获取像素数据和每行字节数
BYTE* srcData1 = (BYTE*)srcImg1.GetBits();
BYTE* srcData2 = (BYTE*)srcImg2.GetBits();
BYTE* destData = (BYTE*)destImg.GetBits();
int srcPitch1 = srcImg1.GetPitch();
int srcPitch2 = srcImg2.GetPitch();
int destPitch = destImg.GetPitch();
if (!srcData1 || !srcData2 || !destData)
{
AfxMessageBox(_T("Failed to get image bits"));
continue;
}
// 将前一张图像从第500行起始的2000行复制到新图像的前2000行
int srcStartY1 = 500; // 前一张图像的起始行
for (int y = 0; y < 2000; ++y)
{
memcpy(destData + y * destPitch, srcData1 + (srcStartY1 + y) * srcPitch1, width1 * (bpp / 8));
}
// 将下一张图像的剩余部分(2000行之后)复制到新图像的剩余部分
for (int y = 2000; y < height2; ++y)
{
memcpy(destData + y * destPitch, srcData2 + y * srcPitch2, width2 * (bpp / 8));
}
// 保存新图像
if (destImg.Save(destPath, Gdiplus::ImageFormatBMP) != S_OK)
{
AfxMessageBox(_T("Failed to save image"));
}
}
// 关闭GDI+
Gdiplus::GdiplusShutdown(gdiplusToken);
}
效果如图:
outimage1是由图像1的从第500行开始的2000行,作为第一部分,第二部分是图像2的2000行之后的其余部分,一起组合所成。图像大小和原图一致。
又问了一遍DEEPSEEK:如果图是来自回调函数
如果图像数据来自相机的回调函数,而不是从本地路径读取的,我们需要直接从回调函数中获取图像数据,并进行处理。以下是实现方法和代码示例。
实现思路
获取图像数据:
在相机的回调函数中,图像数据通常以 BYTE* 指针的形式提供,同时会提供图像的宽度、高度和位深度等信息。
创建新图像:
使用 CImage 创建一个新图像,用于存储处理后的结果。
复制像素数据:
将前一张图像从第 100 行起始的 2000 行复制到下一张图像的开始 2000 行。
将下一张图像的剩余部分(2000 行之后)保持不变。
保存或显示新图像:
将处理后的图像保存到文件或显示在界面上。
实现代码
存储8位图函数
#undef WIDTHBYTES
#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)
void CGlobal::SaveBmp8(CString strName, LPBYTE pdata, int nWidth, int nHeight)
{
BITMAPFILEHEADER bmfh;
CFile File(strName, CFile::modeCreate | CFile::modeWrite);
// 设置文件头中文件类型为"BM"
bmfh.bfType = 0x4d42;
int dwSizeImage = WIDTHBYTES(nWidth * 8) * nHeight;//WIDTHBYTES( nWidth * 8 ) *
// 计算信息头和调色板的大小尺寸
int nSizeHdr = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256;
// 设置文件头信息
bmfh.bfSize = sizeof(BITMAPFILEHEADER) + nSizeHdr + dwSizeImage;
bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + nSizeHdr;
BITMAPINFO* m_pbitmapInforherder;
m_pbitmapInforherder = (LPBITMAPINFO) new
char[sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256];
m_pbitmapInforherder->bmiHeader.biBitCount = 8;
m_pbitmapInforherder->bmiHeader.biClrImportant = 0;
m_pbitmapInforherder->bmiHeader.biClrUsed = 0;
m_pbitmapInforherder->bmiHeader.biCompression = BI_RGB;
m_pbitmapInforherder->bmiHeader.biPlanes = 1;
m_pbitmapInforherder->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
m_pbitmapInforherder->bmiHeader.biXPelsPerMeter = 0;
m_pbitmapInforherder->bmiHeader.biYPelsPerMeter = 0;
m_pbitmapInforherder->bmiHeader.biSizeImage = 0;
m_pbitmapInforherder->bmiHeader.biWidth = nWidth;
m_pbitmapInforherder->bmiHeader.biHeight = -nHeight;
for (int i = 0; i < 256; i++)
{
m_pbitmapInforherder->bmiColors[i].rgbBlue = i;
m_pbitmapInforherder->bmiColors[i].rgbGreen = i;
m_pbitmapInforherder->bmiColors[i].rgbRed = i;
m_pbitmapInforherder->bmiColors[i].rgbReserved = 0;
}
// 进行写操作
File.Write((LPVOID)&bmfh, sizeof(BITMAPFILEHEADER));
File.Write((LPVOID)m_pbitmapInforherder, nSizeHdr);
for (int i = 0; i < nHeight; i++)
{
File.Write((pdata + i * nWidth), WIDTHBYTES(nWidth * 8));
//File.Write((pdata + i * nWidth),nWidth);
}
// File.Write((LPVOID) pdata, dwSizeImage);
delete m_pbitmapInforherder;
File.Close();
}
- 相机回调函数
假设相机的回调函数如下:
前提函数:
需要SAVEBMP8函数
也就是得给Buffer
调用
{
CString csFileName = "2.bmp";
CA2W converter(csFileName);//converter为宽字符
ReadImage(&g_HalconZM.ho_Image1, converter.m_szBuffer);
HTuple pointer, type;
HTuple width, height;
GetImagePointer1(g_HalconZM.ho_Image1, &pointer, &type, &width, &height); // 获取图像指针和图像大小
unsigned char* buffer = new unsigned char[width[0].I() * height[0].I()];
memset(buffer, 0, width[0].I() * height[0].I()); // 将图像数据复制到缓冲区
//memcpy(buffer, (unsigned char*)pointer[0].L(), width[0].I() * height[0].I()); // 将图像数据复制到缓冲区
memcpy(buffer, (BYTE*)pointer[0].L(), width.I() * height.I());
//g_ImageAnddProcessDataBuffer1.AppendImg(buffer, "1", 1, width[0].I(), height[0].I(), 1);
//g_DlgMain->PhotoImg1(g_ImageAnddProcessDataBuffer1.Buffers[0]);
ProcessFrame(buffer, width.I(), height.I(), 8);
}
// 全局变量,用于存储前一张图像的数据
BYTE* g_pPrevImageData = nullptr;
BYTE* g_pPrevImageData1 = nullptr;
int g_prevWidth = 0;
int g_prevHeight = 0;
int g_prevBpp = 0;
void ProcessFrame(BYTE* pImageData, int width, int height, int bpp)
{
// 如果没有前一张图像,直接保存当前图像并返回
if (!g_pPrevImageData)
{
g_pPrevImageData = new BYTE[width * height * (bpp / 8)];
g_pPrevImageData1= new BYTE[width * height * (bpp / 8)];
memcpy(g_pPrevImageData, pImageData, width * height * (bpp / 8));
g_prevWidth = width;
g_prevHeight = height;
g_prevBpp = bpp;
g_CGlobal.SaveBmp8("12345678.bmp", g_pPrevImageData, width , height);
return;
}
// 检查图像参数是否匹配
if (g_prevWidth != width || g_prevHeight != height || g_prevBpp != bpp)
{
AfxMessageBox(_T("Image parameters mismatch"));
return;
}
// 检查前一张图像高度是否足够
if (g_prevHeight < 100 + 2000)
{
AfxMessageBox(_T("Previous image height is insufficient"));
return;
}
// 检查当前图像高度是否足够
if (height < 2000)
{
AfxMessageBox(_T("Current image height is insufficient"));
return;
}
// 将前一张图像从第100行起始的2000行复制到新图像的前2000行
int srcStartY1 = 500; // 前一张图像的起始行
for (int y = 0; y < 2000; ++y)
{
memcpy(g_pPrevImageData1 + y * width, g_pPrevImageData + (srcStartY1 + y) * width, width * (bpp / 8));
}
// 保存新图像
CString destPath;
destPath.Format(_T("output_image_%d.bmp"), GetTickCount()); // 使用时间戳生成唯一文件名
g_CGlobal.SaveBmp8(destPath, g_pPrevImageData1, width, height);
// 将当前图像的剩余部分(2000行之后)复制到新图像的剩余部分
int srcPitch2 = width * (bpp / 8); // 当前图像的每行字节数
for (int y = 2000; y < height; ++y)
{
memcpy(g_pPrevImageData1 + y * width, pImageData + y * width, width * (bpp / 8));
}
// 保存新图像
//CString destPath;
destPath.Format(_T("output_image_%d.bmp"), GetTickCount()); // 使用时间戳生成唯一文件名
g_CGlobal.SaveBmp8(destPath, g_pPrevImageData1, width, height);
// 更新前一张图像数据
memcpy(g_pPrevImageData, pImageData, width * height * (bpp / 8));
}
效果如下图
第一幅图的从100起的2000行,和第二幅图的后半部分组合成一幅图
代码说明
全局变量:
使用全局变量 g_pPrevImageData 存储前一张图像的数据。
使用 g_prevWidth、g_prevHeight 和 g_prevBpp 存储前一张图像的参数。
图像处理逻辑:
如果没有前一张图像,直接保存当前图像并返回。
检查图像参数是否匹配(宽度、高度、位深度)。
检查前一张图像和当前图像的高度是否足够。
创建新图像,并将前一张图像从第 100 行起始的 2000 行复制到新图像的前 2000 行。
将当前图像的剩余部分复制到新图像的剩余部分。
保存新图像:
使用时间戳生成唯一的文件名,避免文件冲突。
更新前一张图像数据:
在处理完成后,更新全局变量 g_pPrevImageData 为当前图像的数据。
示例
假设相机的回调函数每帧调用一次,图像数据为 pImageData,宽度为 5472,高度为 3648,位深度为 8 位。
处理过程:
第一帧:保存图像数据到 g_pPrevImageData。
第二帧:
将 g_pPrevImageData 的第 100 行起始的 2000 行复制到新图像的前 2000 行。
将第二帧图像的剩余部分复制到新图像的剩余部分。
保存新图像为 output_image_.bmp。
更新 g_pPrevImageData 为第二帧图像的数据。
重复上述过程。
注意事项
内存管理:
确保在程序退出时释放 g_pPrevImageData 的内存。
如果处理大量图像,注意内存使用情况,避免内存泄漏。
性能优化:
如果相机帧率较高,可以考虑使用多线程或异步处理,避免阻塞回调函数。
文件保存:
如果需要实时保存大量图像,确保磁盘 I/O 不会成为性能瓶颈。
通过以上代码,你可以直接从相机回调函数中获取图像数据,并将前一张图像从第 100 行起始的 2000 行替换到下一张图像的开始 2000 行,同时保持下一张图像的剩余部分不变。
===========再进一步:
上一张图的固定200行,和第二张图整图合在一起输出
函数如下:
// 全局变量,用于存储前一张图像的数据
BYTE* g_pPrevImageData = nullptr;
BYTE* g_pPrevImageData1 = nullptr;
BYTE* g_pPrevImageData2 = nullptr;
int g_prevWidth = 0;
int g_prevHeight = 0;
int g_prevBpp = 0;
void ProcessFrame(BYTE* pImageData, int width, int height, int bpp)
{
// 如果没有前一张图像,直接保存当前图像并返回
if (!g_pPrevImageData)
{
g_pPrevImageData = new BYTE[width * height * (bpp / 8)];
g_pPrevImageData1= new BYTE[width * height * (bpp / 8)];
g_pPrevImageData2 = new BYTE[width *( height+2000) * (bpp / 8)];
memcpy(g_pPrevImageData, pImageData, width * height * (bpp / 8));
g_prevWidth = width;
g_prevHeight = height;
g_prevBpp = bpp;
g_CGlobal.SaveBmp8("12345678.bmp", g_pPrevImageData, width , height);
return;
}
// 检查图像参数是否匹配
if (g_prevWidth != width || g_prevHeight != height || g_prevBpp != bpp)
{
AfxMessageBox(_T("Image parameters mismatch"));
return;
}
// 检查前一张图像高度是否足够
if (g_prevHeight < 100 + 2000)
{
AfxMessageBox(_T("Previous image height is insufficient"));
return;
}
// 检查当前图像高度是否足够
if (height < 2000)
{
AfxMessageBox(_T("Current image height is insufficient"));
return;
}
// 将前一张图像从第100行起始的2000行复制到新图像的前2000行
int srcStartY1 = 500; // 前一张图像的起始行
for (int y = 0; y < 2000; ++y)
{
memcpy(g_pPrevImageData2 + y * width, g_pPrevImageData + (srcStartY1 + y) * width, width * (bpp / 8));
}
// 保存新图像
CString destPath;
destPath.Format(_T("output_image_%d.bmp"), GetTickCount()); // 使用时间戳生成唯一文件名
g_CGlobal.SaveBmp8(destPath, g_pPrevImageData2, width, (height + 2000));
// 将当前图像的剩余部分(2000行之后)复制到新图像的剩余部分
int srcPitch2 = width * (bpp / 8); // 当前图像的每行字节数
for (int y = 0; y < height; ++y)
{
memcpy(g_pPrevImageData2 + (2000+y )* width, pImageData + y * width, width * (bpp / 8));
}
// 保存新图像
//CString destPath;
destPath.Format(_T("output_image_%d.bmp"), GetTickCount()); // 使用时间戳生成唯一文件名
g_CGlobal.SaveBmp8(destPath, g_pPrevImageData2, width,( height+2000));
// 更新前一张图像数据
memcpy(g_pPrevImageData, pImageData, width * height * (bpp / 8));
}
调用如下:
void CDlgMain::OnBnClickedButton25()
{
if (1)
{
g_DlgMain->m_nCallNumOCR1.nImageIsStart++;
CString csFileName = "1.bmp";
CA2W converter(csFileName);//converter为宽字符
ReadImage(&g_HalconZM.ho_Image1, converter.m_szBuffer);
HTuple pointer, type;
HTuple width, height;
GetImagePointer1(g_HalconZM.ho_Image1, &pointer, &type, &width, &height); // 获取图像指针和图像大小
unsigned char* buffer = new unsigned char[width[0].I() * height[0].I()];
memset(buffer, 0, width[0].I() * height[0].I()); // 将图像数据复制到缓冲区
//memcpy(buffer, (unsigned char*)pointer[0].L(), width[0].I() * height[0].I()); // 将图像数据复制到缓冲区
memcpy(buffer, (BYTE*)pointer[0].L(), width.I() * height.I());
g_ImageAnddProcessDataBuffer1.AppendImg(buffer, "1", 1, width[0].I(), height[0].I(), 1);
//g_DlgMain->PhotoImg1(g_ImageAnddProcessDataBuffer1.Buffers[0]);
ProcessFrame(buffer, width.I(), height.I(), 8);
csFileName = "2.bmp";
CA2W converter1(csFileName);//converter为宽字符
ReadImage(&g_HalconZM.ho_Image1, converter1.m_szBuffer);
GetImagePointer1(g_HalconZM.ho_Image1, &pointer, &type, &width, &height); // 获取图像指针和图像大小
memset(buffer, 0, width[0].I() * height[0].I()); // 将图像数据复制到缓冲区
//memcpy(buffer, (unsigned char*)pointer[0].L(), width[0].I() * height[0].I()); // 将图像数据复制到缓冲区
memcpy(buffer, (BYTE*)pointer[0].L(), width.I() * height.I());
//g_ImageAnddProcessDataBuffer1.AppendImg(buffer, "1", 1, width[0].I(), height[0].I(), 1);
//g_DlgMain->PhotoImg1(g_ImageAnddProcessDataBuffer1.Buffers[0]);
ProcessFrame(buffer, width.I(), height.I(), 8);
}
}
效果如图
===============================再进一步:
如何用多线程实现以上功能
以下未作验证,可以得空验证下。。
DEEPSEEK:
使用多线程处理相机回调函数中的图像拼接问题可以有效避免阻塞主线程,提高程序的响应性和性能。以下是实现方法和代码示例。
实现思路
主线程:
负责接收相机回调函数中的图像数据。
将图像数据放入线程安全的队列中,供工作线程处理。
工作线程:
从队列中取出图像数据。
执行图像拼接逻辑。
保存或显示处理后的图像。
线程安全队列:
使用 std::queue 和 std::mutex 实现线程安全的队列。
同步机制:
使用 std::condition_variable 通知工作线程有新数据到达。
实现代码
- 线程安全队列
cpp
复制
#include <queue>
#include <mutex>
#include <condition_variable>
template <typename T>
class ThreadSafeQueue
{
public:
void Push(const T& value)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_queue.push(value);
lock.unlock();
m_condition.notify_one();
}
bool Pop(T& value)
{
std::unique_lock<std::mutex> lock(m_mutex);
m_condition.wait(lock, [this] { return !m_queue.empty(); });
value = m_queue.front();
m_queue.pop();
return true;
}
bool Empty() const
{
std::unique_lock<std::mutex> lock(m_mutex);
return m_queue.empty();
}
private:
std::queue<T> m_queue;
mutable std::mutex m_mutex;
std::condition_variable m_condition;
};
- 图像数据结构
cpp
复制
struct ImageData
{
BYTE* pData;
int width;
int height;
int bpp;
ImageData(BYTE* pData, int width, int height, int bpp)
: pData(pData), width(width), height(height), bpp(bpp) {}
~ImageData()
{
if (pData)
delete[] pData;
}
};
- 全局变量
cpp
复制
ThreadSafeQueue<ImageData> g_imageQueue; // 线程安全队列
BYTE* g_pPrevImageData = nullptr; // 前一张图像数据
int g_prevWidth = 0; // 前一张图像宽度
int g_prevHeight = 0; // 前一张图像高度
int g_prevBpp = 0; // 前一张图像位深度
std::mutex g_prevImageMutex; // 保护前一张图像数据的互斥锁
- 工作线程
cpp
复制
#include <thread>
#include <atomic>
std::atomic<bool> g_running(true); // 控制工作线程运行
void WorkerThread()
{
while (g_running)
{
ImageData imageData(nullptr, 0, 0, 0);
if (g_imageQueue.Pop(imageData))
{
// 处理图像数据
ProcessFrame(imageData.pData, imageData.width, imageData.height, imageData.bpp);
}
}
}
- 图像处理函数
cpp
复制
#include <atlimage.h>
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
void ProcessFrame(BYTE* pImageData, int width, int height, int bpp)
{
// 初始化GDI+
static bool gdiplusInitialized = false;
if (!gdiplusInitialized)
{
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
gdiplusInitialized = true;
}
// 锁定前一张图像数据
std::unique_lock<std::mutex> lock(g_prevImageMutex);
// 如果没有前一张图像,直接保存当前图像并返回
if (!g_pPrevImageData)
{
g_pPrevImageData = new BYTE[width * height * (bpp / 8)];
memcpy(g_pPrevImageData, pImageData, width * height * (bpp / 8));
g_prevWidth = width;
g_prevHeight = height;
g_prevBpp = bpp;
return;
}
// 检查图像参数是否匹配
if (g_prevWidth != width || g_prevHeight != height || g_prevBpp != bpp)
{
AfxMessageBox(_T("Image parameters mismatch"));
return;
}
// 检查前一张图像高度是否足够
if (g_prevHeight < 100 + 2000)
{
AfxMessageBox(_T("Previous image height is insufficient"));
return;
}
// 检查当前图像高度是否足够
if (height < 2000)
{
AfxMessageBox(_T("Current image height is insufficient"));
return;
}
// 创建新图像(高度为 height,宽度与输入图像一致)
CImage destImg;
if (!destImg.Create(width, height, bpp))
{
AfxMessageBox(_T("Failed to create destination image"));
return;
}
// 获取新图像的像素数据
BYTE* destData = (BYTE*)destImg.GetBits();
int destPitch = destImg.GetPitch();
if (!destData)
{
AfxMessageBox(_T("Failed to get destination image bits"));
return;
}
// 将前一张图像从第100行起始的2000行复制到新图像的前2000行
int srcStartY1 = 100; // 前一张图像的起始行
int srcPitch1 = g_prevWidth * (g_prevBpp / 8); // 前一张图像的每行字节数
for (int y = 0; y < 2000; ++y)
{
memcpy(destData + y * destPitch, g_pPrevImageData + (srcStartY1 + y) * srcPitch1, width * (bpp / 8));
}
// 将当前图像的剩余部分(2000行之后)复制到新图像的剩余部分
int srcPitch2 = width * (bpp / 8); // 当前图像的每行字节数
for (int y = 2000; y < height; ++y)
{
memcpy(destData + y * destPitch, pImageData + y * srcPitch2, width * (bpp / 8));
}
// 保存新图像
CString destPath;
destPath.Format(_T("output_image_%d.bmp"), GetTickCount()); // 使用时间戳生成唯一文件名
if (destImg.Save(destPath, Gdiplus::ImageFormatBMP) != S_OK)
{
AfxMessageBox(_T("Failed to save image"));
}
// 更新前一张图像数据
memcpy(g_pPrevImageData, pImageData, width * height * (bpp / 8));
}
- 相机回调函数
cpp
复制
void OnCameraFrame(BYTE* pImageData, int width, int height, int bpp)
{
// 复制图像数据到堆中
BYTE* pCopy = new BYTE[width * height * (bpp / 8)];
memcpy(pCopy, pImageData, width * height * (bpp / 8));
// 将图像数据放入队列
g_imageQueue.Push(ImageData(pCopy, width, height, bpp));
}
- 启动和停止工作线程
cpp
复制
#include <thread>
std::thread g_workerThread;
void StartWorkerThread()
{
g_running = true;
g_workerThread = std::thread(WorkerThread);
}
void StopWorkerThread()
{
g_running = false;
if (g_workerThread.joinable())
g_workerThread.join();
}
代码说明
线程安全队列:
使用 std::queue 和 std::mutex 实现线程安全的队列。
使用 std::condition_variable 通知工作线程有新数据到达。
工作线程:
从队列中取出图像数据并调用 ProcessFrame 进行处理。
相机回调函数:
将图像数据复制到堆中,并放入线程安全队列。
启动和停止工作线程:
在程序启动时调用 StartWorkerThread,在程序退出时调用 StopWorkerThread。
注意事项
内存管理:
确保在程序退出时释放 g_pPrevImageData 的内存。
在 ImageData 的析构函数中释放图像数据。
性能优化:
如果相机帧率较高,可以增加多个工作线程并行处理。
线程安全:
确保所有共享资源(如 g_pPrevImageData)的访问是线程安全的。
通过以上代码,你可以使用多线程处理相机回调函数中的图像拼接问题,避免阻塞主线程,提高程序的响应性和性能。