1. 概述
在工业检测、质量追溯等应用场景中,经常需要对产品进行拍照并添加相关信息水印。本文将详细介绍如何使用c#实现一个高性能的拍照和水印添加功能,包含完整的代码实现和优化技巧。
2. 功能架构设计
本功能主要分为以下几个模块:
- 相机控制与分辨率设置
- 图像捕获与处理
- 水印信息生成与添加
- 文件保存与ui更新
3. 核心代码实现
3.1 主拍照方法
/// <summary>
/// 拍照并添加水印信息
/// </summary>
/// <returns>保存的图片路径,失败返回空字符串</returns>
private string takepictures()
{
console.writeline("开始拍照");
string filepath = string.empty;
try
{
this.dispatcher.invoke(() =>
{
// 1. 设置ui背景
imgbox.background = new solidcolorbrush(system.windows.media.color.fromrgb(75, 76, 69));
// 2. 设置相机分辨率
if (!setcameraresolutionwithoutstop(3840, 2160))
{
console.writeline("设置分辨率失败");
return;
}
// 3. 检查视频帧有效性
if (player?.getcurrentvideoframe()?.gethbitmap() == intptr.zero)
{
console.writeline("获取视频帧失败");
return;
}
// 4. 获取并处理图像
using (var hbitmap = new safehbitmaphandle(player.getcurrentvideoframe().gethbitmap()))
{
bitmapsource bitmapsource = createbitmapsourcefromhbitmap(hbitmap);
if (bitmapsource == null)
{
console.writeline("创建bitmapsource失败");
return;
}
// 5. 旋转图像(如果需要)
if (shouldrotateimage())
{
bitmapsource = rotateimage(bitmapsource, 180);
}
// 6. 保存图像
filepath = generatefilepath();
if (string.isnullorempty(filepath))
{
console.writeline("生成文件路径失败");
return;
}
saveimagetofile(bitmapsource, filepath);
// 7. 添加水印
addwatermarktoimage(filepath, currentscancoderecord);
// 8. 更新ui显示
updateimagedisplay(filepath);
console.writeline($"照片已保存到: {path.getfullpath(filepath)}");
systeminfo.writefile($"照片已保存到: {path.getfullpath(filepath)}", "生产日志");
}
});
return filepath;
}
catch (exception ex)
{
console.writeline($"照片保存出错: {ex}");
systeminfo.writefile($"照片保存出错: {ex}", "error");
return string.empty;
}
}3.2 安全hbitmap处理类
// 安全释放hbitmap的包装类
private class safehbitmaphandle : idisposable
{
private readonly intptr _hbitmap;
private bool _disposed = false;
public safehbitmaphandle(intptr hbitmap)
{
_hbitmap = hbitmap;
}
public static implicit operator intptr(safehbitmaphandle handle) => handle._hbitmap;
public void dispose()
{
if (!_disposed && _hbitmap != intptr.zero)
{
deleteobject(_hbitmap);
_disposed = true;
}
gc.suppressfinalize(this);
}
~safehbitmaphandle()
{
dispose();
}
[system.runtime.interopservices.dllimport("gdi32.dll")]
private static extern bool deleteobject(intptr hobject);
}4. 关键技术点详解
4.1 相机分辨率设置
private bool setcameraresolutionwithoutstop(int width, int height)
{
try
{
if (player?.videosource is not videocapturedevice videodevice)
return false;
var targetresolution = videodevice.videocapabilities?
.firstordefault(cap => cap.framesize.width == width && cap.framesize.height == height);
if (targetresolution != null)
{
videodevice.videoresolution = targetresolution;
console.writeline($"已设置分辨率为: {width}x{height}");
return true;
}
console.writeline($"未找到 {width}x{height} 分辨率支持");
return false;
}
catch (exception ex)
{
console.writeline($"设置分辨率失败: {ex.message}");
return false;
}
}技术要点:
- 使用
videocapturedevice类控制相机设备 - 通过
videocapabilities枚举设备支持的分辨率 - 采用异常处理确保程序稳定性
4.2 图像处理与旋转
private bitmapsource rotateimage(bitmapsource source, double angle)
{
try
{
rotatetransform rotatetransform = new rotatetransform(angle);
return new transformedbitmap(source, rotatetransform);
}
catch
{
return source;
}
}4.3 文件路径生成策略
private string generatefilepath()
{
try
{
string basepath = configurationmanager.appsettings["picfileload"] ?? string.empty;
if (string.isnullorempty(basepath))
return string.empty;
string datefolder = datetime.now.tostring("yyyy-mm-dd");
string codefolder = string.isnullorempty(oldcode) ? "null" : oldcode;
string directorypath = path.combine(basepath, "photos", "input", datefolder, codefolder);
if (!directory.exists(directorypath))
directory.createdirectory(directorypath);
return path.combine(directorypath, $"photo_{datetime.now:yyyymmddhhmmss}.jpg");
}
catch
{
return string.empty;
}
}目录结构设计:
basepath/ ├── photos/ │ ├── input/ │ │ ├── 2023-11-20/ │ │ │ ├── productcode001/ │ │ │ └── productcode002/ │ │ └── 2023-11-21/
4.4 高质量图像保存
private void saveimagetofile(bitmapsource bitmapsource, string filepath)
{
try
{
using (filestream fs = new filestream(filepath, filemode.create))
{
jpegbitmapencoder encoder = new jpegbitmapencoder { qualitylevel = 95 };
encoder.frames.add(bitmapframe.create(bitmapsource));
encoder.save(fs);
}
}
catch (exception ex)
{
console.writeline($"保存图像失败: {ex.message}");
throw;
}
}5. 智能水印实现
5.1 水印添加核心方法
private void addwatermarktoimage(string imagepath, t_scancoderecord record)
{
try
{
using (var originalimage = image.fromfile(imagepath))
using (var watermarkimage = new bitmap(originalimage.width, originalimage.height))
using (var graphics = graphics.fromimage(watermarkimage))
{
// 设置高质量绘制
graphics.smoothingmode = system.drawing.drawing2d.smoothingmode.highquality;
graphics.interpolationmode = system.drawing.drawing2d.interpolationmode.highqualitybicubic;
graphics.pixeloffsetmode = system.drawing.drawing2d.pixeloffsetmode.highquality;
// 绘制原始图像
graphics.drawimage(originalimage, 0, 0, originalimage.width, originalimage.height);
// 添加水印
string watermarktext = formatshippinginfo(record);
addtextwatermark(graphics, watermarktext, originalimage.width, 80);
// 保存处理后的图像
watermarkimage.save(imagepath, system.drawing.imaging.imageformat.jpeg);
}
}
catch (exception ex)
{
console.writeline($"添加水印失败: {ex.message}");
throw;
}
}5.2 智能文本格式化
public string formatshippinginfo(t_scancoderecord record)
{
if (record == null) return string.empty;
string[] parts =
{
record.shipping_mark ?? string.empty,
record.code ?? string.empty,
$"{record.weight}kg",
$"{record.volume}m³",
datetime.now.tostring("yyyy.mm.dd hh:mm")
};
// 计算显示长度(考虑中英文)
int[] partlengths = parts.select(getdisplaylength).toarray();
int totallength = partlengths.sum();
// 计算间隔
int totalgaps = 126 - totallength;
int gapcount = 4;
int basegapsize = totalgaps / gapcount;
int extraspace = totalgaps % gapcount;
// 构建结果
var result = new stringbuilder();
for (int i = 0; i < parts.length; i++)
{
result.append(parts[i]);
if (i < gapcount)
{
int currentgapsize = basegapsize + (extraspace > 0 ? 1 : 0);
result.append(' ', currentgapsize);
if (extraspace > 0) extraspace--;
}
}
console.writeline($"最终长度: {getdisplaylength(result.tostring())}/126");
return result.tostring();
}
// 计算字符串显示长度(支持中英文混合)
private int getdisplaylength(string text)
{
if (string.isnullorempty(text)) return 0;
return text.sum(c => c >= 0x4e00 && c <= 0x9fa5 ? 2 : 1);
}6. 性能优化技巧
- 资源管理:使用
using语句和safehbitmaphandle确保资源正确释放 - 异常处理:完善的异常处理机制保证程序稳定性
- 配置化:通过配置文件管理路径、旋转设置等参数
- 高质量渲染:设置合适的图形渲染参数保证图像质量
- 日志记录:详细的日志记录便于问题排查
7. 实际应用建议
- 相机选型:根据实际需求选择合适分辨率的工业相机
- 存储规划:考虑图片数量和大小,合理规划存储空间
- 性能监控:监控拍照过程的耗时,优化瓶颈环节
- 错误处理:增加重试机制应对临时性故障
- 内存管理:定期清理不必要的资源,防止内存泄漏
8. 总结
本文详细介绍了c#实现拍照和水印添加功能的完整方案,涵盖了从相机控制、图像处理到文件保存的各个环节。代码中体现了良好的资源管理、异常处理和性能优化实践,可以直接应用于工业检测、质量追溯等实际场景。
通过合理的架构设计和细节处理,这个方案能够稳定高效地完成拍照和水印添加任务,为后续的图像分析和数据处理提供可靠的基础。
以上就是c#实现高性能拍照与水印添加功能完整方案的详细内容,更多关于c#拍照与水印添加功能的资料请关注代码网其它相关文章!
发表评论