C#中使用OpenCV ROI结合线程合并图像的问题咨询
问题:多线程合并OpenCV图像时ROI冲突导致结果异常
我想要将多张小图像合并为一张大图像,因小图像数量较多,希望借助线程提升处理效率。OpenCV支持选择ROI(感兴趣区域),我计划将小图像复制到预定义ROI中,但使用线程时出现问题:ROI会在小图像完成复制到大图前被修改,导致合并结果异常。
测试代码如下:
private void Test() { int size = 256; //Create RGB Image Image<Bgr, byte> R = new Image<Bgr, byte>(size, size, new Bgr(Color.Red)); Image<Bgr, byte> G = new Image<Bgr, byte>(size, size, new Bgr(Color.Green)); Image<Bgr, byte> B = new Image<Bgr, byte>(size, size, new Bgr(Color.Blue)); Image<Bgr, byte>[] imageArray = new Image<Bgr, byte>[3] { R, G, B}; Image<Bgr, byte> merger = new Image<Bgr, byte>(size * 3, size, new Bgr(Color.Black)); // Create final image with thread Parallel.For(0, imageArray.Length, i => { merger.ROI = new Rectangle(i * 256, 0, size, size); imageArray[i].CopyTo(merger); }); merger.ROI = Rectangle.Empty; //Show the image Utils.ShowImage(merger); //Free for (int i = 0; i < 3; i++) imageArray[i].Dispose(); merger.Dispose(); }
我创建了红、绿、蓝3张小图像,预期合并后得到红-绿-蓝排列的大图,但使用线程后结果不符合预期。请问是否有办法在保留线程优势的同时,正确处理多ROI的图像合并?
解决方案
问题根源
你的代码中,多个线程同时修改merger的ROI属性,这是线程不安全的操作:线程A刚设置好ROI,线程B就立刻覆盖了这个ROI,导致线程A的CopyTo操作最终写到了错误的区域,结果自然混乱。
最优解决方法:使用子图像独立操作
避免直接修改全局merger的ROI,而是为每个线程创建对应区域的子图像(通过GetSubRect方法),每个线程独立操作自己的子区域,完全避免线程间的竞争,同时保留多线程的效率优势。
修改后的代码:
private void Test() { int size = 256; //Create RGB Image Image<Bgr, byte> R = new Image<Bgr, byte>(size, size, new Bgr(Color.Red)); Image<Bgr, byte> G = new Image<Bgr, byte>(size, size, new Bgr(Color.Green)); Image<Bgr, byte> B = new Image<Bgr, byte>(size, size, new Bgr(Color.Blue)); Image<Bgr, byte>[] imageArray = new Image<Bgr, byte>[3] { R, G, B}; Image<Bgr, byte> merger = new Image<Bgr, byte>(size * 3, size, new Bgr(Color.Black)); // 多线程合并,每个线程操作独立的子图像 Parallel.For(0, imageArray.Length, i => { // 获取merger中对应位置的子图像,操作该子图像等同于操作原图像的对应区域 using (var roiSubImage = merger.GetSubRect(new Rectangle(i * size, 0, size, size))) { imageArray[i].CopyTo(roiSubImage); } }); //Show the image Utils.ShowImage(merger); //释放资源 foreach (var img in imageArray) img.Dispose(); merger.Dispose(); }
原理说明
GetSubRect方法会返回一个指向原图像对应区域的独立图像对象,每个线程操作自己的子图像时,本质是直接写入原图像的对应内存区域,不需要修改原merger的ROI属性,从根源上消除了线程间的ROI竞争问题,同时保持多线程并行处理的效率。
备选方法:加锁(不推荐)
如果一定要通过修改ROI的方式实现,可以对设置ROI和CopyTo的操作加锁,但这样会让并行操作退化为串行,完全失去多线程的效率优势,仅作参考:
private void Test() { int size = 256; Image<Bgr, byte> R = new Image<Bgr, byte>(size, size, new Bgr(Color.Red)); Image<Bgr, byte> G = new Image<Bgr, byte>(size, size, new Bgr(Color.Green)); Image<Bgr, byte> B = new Image<Bgr, byte>(size, size, new Bgr(Color.Blue)); Image<Bgr, byte>[] imageArray = new Image<Bgr, byte>[3] { R, G, B}; Image<Bgr, byte> merger = new Image<Bgr, byte>(size * 3, size, new Bgr(Color.Black)); object lockObj = new object(); Parallel.For(0, imageArray.Length, i => { // 加锁确保同一时间只有一个线程操作merger的ROI和CopyTo lock(lockObj) { merger.ROI = new Rectangle(i * size, 0, size, size); imageArray[i].CopyTo(merger); } }); merger.ROI = Rectangle.Empty; Utils.ShowImage(merger); foreach (var img in imageArray) img.Dispose(); merger.Dispose(); }
内容的提问来源于stack exchange,提问作者Fedour Traktor
相关产品推荐
相关产品推荐

