在Windows系统上使用Golang操作摄像头设备
在Windows系统上使用Golang操作摄像头设备 大家好,我遇到了以下代码的一些问题,该代码旨在使用 Windows API 拍摄照片。当我运行这段代码时,我的网络摄像头指示灯会亮起几秒钟(表明摄像头已被调用),但保存的 PNG 图像却全是黑色像素。
我找不到问题所在。有人能帮我找到解决方案吗?
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"syscall"
"unsafe"
)
var (
avicap32 = syscall.NewLazyDLL("avicap32.dll")
proccapCreateCaptureWindowA = avicap32.NewProc("capCreateCaptureWindowA")
user32 = syscall.NewLazyDLL("user32.dll")
procSendMessageA = user32.NewProc("SendMessageA")
)
func CaptureWebcam() {
var name = "WebcamCapture"
handle, _, _ := proccapCreateCaptureWindowA.Call(uintptr(unsafe.Pointer(&name)), 0, 0, 0, 320, 240, 0, 0)
procSendMessageA.Call(handle, 0x40A, 0, 0) //WM_CAP_DRIVER_CONNECT
procSendMessageA.Call(handle, 0x432, 30, 0) //WM_CAP_SET_PREVIEW
procSendMessageA.Call(handle, 0x43C, 0, 0) //WM_CAP_GRAB_FRAME
procSendMessageA.Call(handle, 0x41E, 0, 0) //WM_CAP_EDIT_COPY
procSendMessageA.Call(handle, 0x40B, 0, 0) //WM_CAP_DRIVER_DISCONNECT
camera, err := os.Create("Image.png")
if err != nil {
fmt.Println(err)
return
}
clip, err := readClipboard()
if err != nil {
fmt.Println(err)
return
}
_, err = io.Copy(camera, clip)
if err != nil {
fmt.Println(err)
return
}
camera.Close()
}
func readClipboard() (io.Reader, error) {
f, err := ioutil.TempFile("", "")
if err != nil {
fmt.Println(err)
return nil, err
}
f.Close()
_, err = exec.Command("PowerShell", "-Command", "Add-Type", "-AssemblyName", fmt.Sprintf("System.Windows.Forms;$clip=[Windows.Forms.Clipboard]::GetImage();if ($clip -ne $null) { $clip.Save('%s') };", f.Name())).CombinedOutput()
if err != nil {
fmt.Println(err)
return nil, err
}
r := new(bytes.Buffer)
file, err := os.Open(f.Name())
if err != nil {
fmt.Println(err)
return nil, err
}
if _, err := io.Copy(r, file); err != nil {
fmt.Println(err)
return nil, err
}
file.Close()
os.Remove(f.Name())
return r, nil
}
func main() {
CaptureWebcam()
}
更多关于在Windows系统上使用Golang操作摄像头设备的实战教程也可以访问 https://www.itying.com/category-94-b0.html
2 回复
我开发了一个用于在Windows上访问摄像头的Go库,它基于Windows Media Foundation API。它通过DLL工作,不需要CGO。
更多关于在Windows系统上使用Golang操作摄像头设备的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
问题出在代码没有给摄像头足够的时间来捕获和传输帧数据。WM_CAP_GRAB_FRAME 是同步操作,但需要等待帧数据就绪。以下是修正后的代码:
package main
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"syscall"
"time"
"unsafe"
)
var (
avicap32 = syscall.NewLazyDLL("avicap32.dll")
proccapCreateCaptureWindowA = avicap32.NewProc("capCreateCaptureWindowA")
user32 = syscall.NewLazyDLL("user32.dll")
procSendMessageA = user32.NewProc("SendMessageA")
procSetWindowPos = user32.NewProc("SetWindowPos")
)
const (
WM_CAP_DRIVER_CONNECT = 0x40A
WM_CAP_DRIVER_DISCONNECT = 0x40B
WM_CAP_SET_PREVIEW = 0x432
WM_CAP_SET_PREVIEWRATE = 0x434
WM_CAP_GRAB_FRAME = 0x43C
WM_CAP_EDIT_COPY = 0x41E
WM_CAP_SET_SCALE = 0x433
WM_CAP_DLG_VIDEOFORMAT = 0x419
WM_CAP_DLG_VIDEOSOURCE = 0x41A
WM_CAP_DLG_VIDEODISPLAY = 0x41B
WM_CAP_FILE_SAVEDIB = 0x419
SWP_NOZORDER = 0x4
SWP_NOMOVE = 0x2
SWP_NOSIZE = 0x1
)
func CaptureWebcam() error {
// 创建捕获窗口
windowName := "WebcamCapture\x00"
hwnd, _, _ := proccapCreateCaptureWindowA.Call(
uintptr(unsafe.Pointer(&[]byte(windowName)[0])),
0,
0, 0, 640, 480,
0, 0,
)
if hwnd == 0 {
return fmt.Errorf("无法创建捕获窗口")
}
// 隐藏窗口
procSetWindowPos.Call(hwnd, 0, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOMOVE|SWP_NOSIZE)
// 连接摄像头驱动
ret, _, _ := procSendMessageA.Call(hwnd, WM_CAP_DRIVER_CONNECT, 0, 0)
if ret == 0 {
return fmt.Errorf("无法连接摄像头驱动")
}
// 设置预览
procSendMessageA.Call(hwnd, WM_CAP_SET_PREVIEW, 1, 0)
procSendMessageA.Call(hwnd, WM_CAP_SET_PREVIEWRATE, 30, 0)
procSendMessageA.Call(hwnd, WM_CAP_SET_SCALE, 1, 0)
// 等待摄像头初始化
time.Sleep(2 * time.Second)
// 抓取帧
ret, _, _ = procSendMessageA.Call(hwnd, WM_CAP_GRAB_FRAME, 0, 0)
if ret == 0 {
procSendMessageA.Call(hwnd, WM_CAP_DRIVER_DISCONNECT, 0, 0)
return fmt.Errorf("无法抓取帧")
}
// 复制到剪贴板
ret, _, _ = procSendMessageA.Call(hwnd, WM_CAP_EDIT_COPY, 0, 0)
if ret == 0 {
procSendMessageA.Call(hwnd, WM_CAP_DRIVER_DISCONNECT, 0, 0)
return fmt.Errorf("无法复制到剪贴板")
}
// 等待剪贴板操作完成
time.Sleep(500 * time.Millisecond)
// 断开连接
procSendMessageA.Call(hwnd, WM_CAP_DRIVER_DISCONNECT, 0, 0)
// 从剪贴板读取图像
imgData, err := readClipboardImage()
if err != nil {
return fmt.Errorf("读取剪贴板失败: %v", err)
}
// 保存图像
if err := os.WriteFile("capture.png", imgData, 0644); err != nil {
return fmt.Errorf("保存文件失败: %v", err)
}
fmt.Println("图像已保存为 capture.png")
return nil
}
func readClipboardImage() ([]byte, error) {
// 创建临时文件
tmpFile, err := os.CreateTemp("", "clipboard_*.png")
if err != nil {
return nil, err
}
tmpPath := tmpFile.Name()
tmpFile.Close()
defer os.Remove(tmpPath)
// 使用PowerShell从剪贴板读取图像
psCmd := fmt.Sprintf(
`Add-Type -AssemblyName System.Windows.Forms;
$img = [Windows.Forms.Clipboard]::GetImage();
if ($img -ne $null) {
$img.Save('%s', [System.Drawing.Imaging.ImageFormat]::Png);
Write-Output "OK";
} else {
Write-Output "ERROR: No image in clipboard";
}`,
tmpPath,
)
cmd := exec.Command("powershell", "-Command", psCmd)
output, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("PowerShell执行失败: %v, 输出: %s", err, output)
}
if !bytes.Contains(output, []byte("OK")) {
return nil, fmt.Errorf("剪贴板中没有图像数据")
}
// 读取图像数据
imgData, err := os.ReadFile(tmpPath)
if err != nil {
return nil, fmt.Errorf("读取临时文件失败: %v", err)
}
// 验证PNG文件头
if len(imgData) < 8 || string(imgData[:8]) != "\x89PNG\r\n\x1a\n" {
return nil, fmt.Errorf("无效的PNG数据")
}
return imgData, nil
}
func main() {
if err := CaptureWebcam(); err != nil {
fmt.Printf("捕获失败: %v\n", err)
os.Exit(1)
}
}
主要修改:
- 增加了窗口隐藏和摄像头初始化等待时间
- 添加了错误检查,确保每个步骤都成功执行
- 改进了剪贴板读取逻辑,直接保存为PNG格式
- 增加了PNG文件头验证
- 将分辨率从320x240提高到640x480以获得更好的图像质量
运行这个修正版本应该能正确捕获摄像头图像。如果仍然得到黑色图像,可能是摄像头需要更长的初始化时间,可以尝试增加第53行的等待时间。

