-
Notifications
You must be signed in to change notification settings - Fork 165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Sobel and Scharr operators #392
Conversation
6c2f289
to
ec75792
Compare
yes, yes, I still plan to refactor the code so the |
Okay, so if @simmplecoder doesn't prefer to do it now, then we can split |
fe91129
to
f4f50bf
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@simmplecoder LGTM.
However, please ask and wait for @stefanseefeld 's review and consider his review superior to mine.
@stefanseefeld , would you like me to unify the interface of all kernel generator functions in this PR? |
@simmplecoder , sorry, I don't entirely understand the question. What do you mean by "unify" ? |
@stefanseefeld, earlier functions that generate Gaussian and mean kernels return |
Ah, yes, I think that would be preferable. Thanks, |
I agree that it's better to (create and) use dedicated types for job than trying to (bend to) re-use existing types where they don't necessarily fit, neither conceptually nor physically. |
f4d7ca4
to
3b33f9b
Compare
@miralshah365 , could you please check if I broke your code? I migrated small part of your code that relied on old |
The output of Harris and Hessian seem to be differ from before the migration to |
@simmplecoder If there is anything to update in @miralshah365 's code and she's offline, then don't hesitate to nudge me. I will try to take care of updating Miral's code. AFAIS, current failures are in the kernel tests, see https://dev.azure.com/boostorg/gil/_build/results?buildId=701&view=logs&jobId=2517ed61-6924-508d-087f-7c02f775cbba
|
@mloskot, I made a lot of changes after your approval, could you please have a look at new changes? |
@stefanseefeld , I believe I did all the changes I wanted. The PR is ready for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thank you!
@@ -42,7 +44,7 @@ void compute_harris_responses( | |||
" tensor from the same image"); | |||
} | |||
|
|||
auto const window_length = weights.dimensions().x; | |||
auto const window_length = weights.size(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor naming remark: wouldn't width
or x_size
fit better as name here than length
?
inline void compute_hessian_responses( | ||
GradientView ddxx, | ||
GradientView dxdy, | ||
GradientView ddyy, | ||
Weights weights, | ||
const kernel_2d<T, Allocator>& weights, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor stylistic remark: since in C++ this reads from right to left as "weights
is a reference to const kernel_2d
object", as we do it for pointers, this should be the East Const way: kernel_2d<T, Allocator> const& weights
You may want to join the revolution! 😉
/// \brief Generate mean kernel | ||
/// \ingroup ImageProcessingMath | ||
/// | ||
/// Fills supplied view with normalized mean | ||
/// in which all entries will be equal to | ||
/// \code 1 / (dst.size()) \endcode | ||
inline void generate_normalized_mean(boost::gil::gray32f_view_t dst) | ||
template <typename T = float, typename Allocator = std::allocator<T>> | ||
inline kernel_2d<T, Allocator> generate_normalized_mean(std::size_t side_length) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, wouldn't width
or x_size
fit better as name here than length
?
} | ||
} | ||
} | ||
|
||
/// \brief Generate mean kernel | ||
/// \ingroup ImageProcessingMath | ||
/// | ||
/// Fills supplied view with normalized mean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps you could run find and replace for /// Fills supplied view
changing to /// Fills kernel
. This could be done even after merge as single commit with [ci skip]
tag in subject line. No need to CI this kind of change.
case 1: | ||
{ | ||
kernel_2d<T, Allocator> result(3, 1, 1); | ||
std::copy(dx_sobel.begin(), dx_sobel.end(), result.begin()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we have compile-time known std::array<float, 9> dx_sobel
, this place seems like a good opportunity to add static_assert
verifying dx_sobel.size()
and BOOST_ASSERT
verifying dx_sobel.size() == result.size()
.
Such assertions would also serve as code comment for a reader who doesn't have to jump to dx_sobel
definition.
case 1: | ||
{ | ||
kernel_2d<T, Allocator> result(3, 1, 1); | ||
std::copy(dx_scharr.begin(), dx_scharr.end(), result.begin()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, I'd add some assertions.
case 1: | ||
{ | ||
kernel_2d<T, Allocator> result(3, 1, 1); | ||
std::copy(dy_sobel.begin(), dy_sobel.end(), result.begin()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And assertions here.
case 1: | ||
{ | ||
kernel_2d<T, Allocator> result(3, 1, 1); | ||
std::copy(dy_scharr.begin(), dy_scharr.end(), result.begin()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And assertions here too 😄
[](gray32f_pixel_t pixel) -> float {return pixel.at(std::integral_constant<int, 0>{}); } | ||
); | ||
|
||
kernel_2d<float> kernel = generate_gaussian_kernel(kernel_size, 1.0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kernel_2d<float> const kernel
?
This simple addition immediately clears potential reader's question "can a thing be modified by function call that follows?".
Harris and Hessian examples now use new interface for kernel generation
simple_kernels are now using kernel_2d interface
Normalized mean generation had missing return at the end of the function
This commit reacts to kernel_2d, convolve_2d being moved to namespace detail
42a3ea0
to
d2479c4
Compare
@simmplecoder I was using the sobel operator for gradient along x direction for the photo attached below. The sobel operator is of degree 1. To my surprise, after applying the sobel operator, the pixel values are coming of the magnitude of 65000 in the gradient image. Is it possible? I used the sobel_scharr.cpp file in the example folder of boost gil and simply passed the name of the operator and the name of the picture and the other arguments as mentioned. I tested the same picture by applying sobel in opencv in python and found the pixel values to be as it should be. If you can kindly help me regarding this issue,I will be highly grateful |
@Sayan-Chaudhuri perhaps you are using unsigned images and views? IIRC the read functions cannot read signed images, but you can convert them into signed or use |
I will look into it tomorrow and let you know. Perhaps I forgot to update
the example or something
|
Thanks for taking out time to look into the matter |
@simmplecoder @Sayan-Chaudhuri , I believe the issue was caused due to |
@simmplecoder , perhaps we can add a check inside |
Yes, I also found that but why did this happen? |
Without doing an in depth analysis, my initial thoughts suggest that we are probably looking at some kind of an overflow inside |
Should I make a pull request if i find out the reason and solve it?@meshtag |
I am waiting for feedback on this. If we all agree on the root cause of this problem and my suggestion to solve it, I would like to create a PR myself. Lets wait until we hear from more experienced developers. |
ok. |
@meshtag I believe @Scramjet911 didn't have much luck with channel traits. It seems like whatever it returns is not what we need. The problem with the example is that it uses unsigned type where negative values will be present. The fix to use |
@simmplecoder Channel traits was working fine, but the problem was that the destination image of that case was of floating type, giving me the wrong result. So I had to use the source channel traits for checking the constraints. |
@Sayan-Chaudhuri @meshtag unfortunately I didn't get time to write saturate cast, but you can tweak the equation inside
The main problem now is that the values are incorrectly scaled. I bet opencv uses |
Thanks @simmplecoder I shall look into it |
@simmplecoder I wanted to confirm that sobel and scharr currently dont work for multichannel images right? |
You’re correct. You could use channel views to get it to work for multi
channel images.
…On Thu, 8 Apr 2021 at 22:42 Sayan-Chaudhuri ***@***.***> wrote:
@simmplecoder <https://github.com/simmplecoder> I wanted to confirm that
sobel and scharr currently dont work for multichannel images right?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#392 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/ADGZGQC2K6GISVJATSKLNATTHXMHTANCNFSM4I2DNP4Q>
.
|
@simmplecoder I think for proper scaling we should be using this formula |
@simmplecoder Also,how does the convolution operator in boost deal with pixels at boundaries? |
I think your scaling is not correct. If you move values such that min is 0 then you just cannot scale by max. Also I think it is better to scale based on the range of image instead of the range of pixel type [-32768, 32767]. Imagine your range of your gradient is [-10, 10]. You would barely see them if you subtract -32768 and divide by 32767 . But if you map -10 -> 0 and 10 -> 255 then you can see them clearly. So my idea was to stretch the range of your gradients to max range of output type. Here is mine version to write to png files template<typename InputView>
void write_view(const InputView& img, const std::string& filename) {
gil::gray8_image_t output_image(img.dimensions());
auto output = gil::view(output_image);
const auto [minPix, maxPix] = std::minmax_element(img.begin(), img.end());
const auto minVal = minPix[0];
const auto maxVal = maxPix[0];
gil::transform_pixels(img, output, [&minVal, &maxVal](const auto& pix) {
double value = pix[0];
return gil::gray8_pixel_t((value - minVal) / (maxVal - minVal) * 255);
});
gil::write_view(filename, output, gil::png_tag{});
} Now you have nice gradients (this image has been used):
However, the dx and dy is still switched because of a bug. Please see this PR #723. |
Description
This commit adds Sobel and Scharr
operators with support for 0th and 1st
degrees with other degrees planned for
later
References
https://www.researchgate.net/publication/239398674_An_Isotropic_3_3_Image_Gradient_Operator
https://www.researchgate.net/profile/Hanno_Scharr/publication/220955743_Optimal_Filters_for_Extended_Optical_Flow/links/004635151972eda98f000000/Optimal-Filters-for-Extended-Optical-Flow.pdf
Tasklist