The definition I like the most is that DI is simply about separating object creation from business logic. Object creation - wiring the application - is a special type of code, different from the rest and it is useful to keep it separated. This is similar to how we remove hardcoded magic constants from our code into config files, it can be thought as a Object Oriented extension of that practice. We remove magic constants because we need to change them more frequently than the rest of the code. We do DI because we need to change the object wiring much more frequently than we change business logic and in particular we need to change it in the tests - without that unit tests would not be possible. But it is also different - because the object wiring code is much more complex than configuration files.
But this is not all - DI is also about keeping all object collaborators in it's attributes instead of reaching out for global objects (or signletons which are globals in disguise or class attributes). It thus improves object's encapsulation, makes them more self-reliant and testable. Or maybe this part is not DI - but simply writing Object Oriented code?
On the other hand, the separated out object factories are hard to test because they depend on all the objects classes they create and you want to keep them as small and simple as possible. How many such factories you need? If we have something that has a http request object as attribute - then we cannot build it until the http request arrives from the user. If we keep all collaborators in objects attributes - then we cannot build them until we have all information needed to build all these collaborators first. We thus need one factory per scope.